mirror of
https://github.com/lubosz/overte.git
synced 2025-04-19 17:03:43 +02:00
Merge branch 'master' into 21866
This commit is contained in:
commit
5021778dc0
106 changed files with 2049 additions and 1448 deletions
|
@ -548,16 +548,21 @@ void Agent::setIsAvatar(bool isAvatar) {
|
|||
if (_isAvatar && !_avatarIdentityTimer) {
|
||||
// set up the avatar timers
|
||||
_avatarIdentityTimer = new QTimer(this);
|
||||
_avatarQueryTimer = new QTimer(this);
|
||||
|
||||
// connect our slot
|
||||
connect(_avatarIdentityTimer, &QTimer::timeout, this, &Agent::sendAvatarIdentityPacket);
|
||||
connect(_avatarQueryTimer, &QTimer::timeout, this, &Agent::queryAvatars);
|
||||
|
||||
static const int AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS = 1000;
|
||||
static const int AVATAR_VIEW_PACKET_SEND_INTERVAL_MSECS = 1000;
|
||||
|
||||
// start the timers
|
||||
_avatarIdentityTimer->start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS); // FIXME - we shouldn't really need to constantly send identity packets
|
||||
_avatarQueryTimer->start(AVATAR_VIEW_PACKET_SEND_INTERVAL_MSECS);
|
||||
|
||||
// tell the avatarAudioTimer to start ticking
|
||||
QMetaObject::invokeMethod(&_avatarAudioTimer, "start");
|
||||
|
||||
}
|
||||
|
||||
if (!_isAvatar) {
|
||||
|
@ -567,6 +572,10 @@ void Agent::setIsAvatar(bool isAvatar) {
|
|||
delete _avatarIdentityTimer;
|
||||
_avatarIdentityTimer = nullptr;
|
||||
|
||||
_avatarQueryTimer->stop();
|
||||
delete _avatarQueryTimer;
|
||||
_avatarQueryTimer = nullptr;
|
||||
|
||||
// The avatar mixer never times out a connection (e.g., based on identity or data packets)
|
||||
// but rather keeps avatars in its list as long as "connected". As a result, clients timeout
|
||||
// when we stop sending identity, but then get woken up again by the mixer itself, which sends
|
||||
|
@ -585,6 +594,7 @@ void Agent::setIsAvatar(bool isAvatar) {
|
|||
nodeList->sendPacket(std::move(packet), *node);
|
||||
});
|
||||
}
|
||||
|
||||
QMetaObject::invokeMethod(&_avatarAudioTimer, "stop");
|
||||
}
|
||||
}
|
||||
|
@ -597,6 +607,31 @@ void Agent::sendAvatarIdentityPacket() {
|
|||
}
|
||||
}
|
||||
|
||||
void Agent::queryAvatars() {
|
||||
auto scriptedAvatar = DependencyManager::get<ScriptableAvatar>();
|
||||
|
||||
ViewFrustum view;
|
||||
view.setPosition(scriptedAvatar->getWorldPosition());
|
||||
view.setOrientation(scriptedAvatar->getHeadOrientation());
|
||||
view.calculate();
|
||||
ConicalViewFrustum conicalView { view };
|
||||
|
||||
auto avatarPacket = NLPacket::create(PacketType::AvatarQuery);
|
||||
auto destinationBuffer = reinterpret_cast<unsigned char*>(avatarPacket->getPayload());
|
||||
auto bufferStart = destinationBuffer;
|
||||
|
||||
uint8_t numFrustums = 1;
|
||||
memcpy(destinationBuffer, &numFrustums, sizeof(numFrustums));
|
||||
destinationBuffer += sizeof(numFrustums);
|
||||
|
||||
destinationBuffer += conicalView.serialize(destinationBuffer);
|
||||
|
||||
avatarPacket->setPayloadSize(destinationBuffer - bufferStart);
|
||||
|
||||
DependencyManager::get<NodeList>()->broadcastToNodes(std::move(avatarPacket),
|
||||
{ NodeType::AvatarMixer });
|
||||
}
|
||||
|
||||
void Agent::processAgentAvatar() {
|
||||
if (!_scriptEngine->isFinished() && _isAvatar) {
|
||||
auto scriptedAvatar = DependencyManager::get<ScriptableAvatar>();
|
||||
|
|
|
@ -97,6 +97,7 @@ private:
|
|||
void setAvatarSound(SharedSoundPointer avatarSound) { _avatarSound = avatarSound; }
|
||||
|
||||
void sendAvatarIdentityPacket();
|
||||
void queryAvatars();
|
||||
|
||||
QString _scriptContents;
|
||||
QTimer* _scriptRequestTimeout { nullptr };
|
||||
|
@ -106,6 +107,7 @@ private:
|
|||
int _numAvatarSoundSentBytes = 0;
|
||||
bool _isAvatar = false;
|
||||
QTimer* _avatarIdentityTimer = nullptr;
|
||||
QTimer* _avatarQueryTimer = nullptr;
|
||||
QHash<QUuid, quint16> _outgoingScriptAudioSequenceNumbers;
|
||||
|
||||
AudioGate _audioGate;
|
||||
|
|
|
@ -36,10 +36,11 @@ enum class BakedAssetType : int {
|
|||
Undefined
|
||||
};
|
||||
|
||||
// ATTENTION! If you change the current version for an asset type, you will also
|
||||
// need to update the function currentBakeVersionForAssetType() inside of AssetServer.cpp.
|
||||
// ATTENTION! Do not remove baking versions, and do not reorder them. If you add
|
||||
// a new value, it will immediately become the "current" version.
|
||||
enum class ModelBakeVersion : BakeVersion {
|
||||
Initial = INITIAL_BAKE_VERSION,
|
||||
MetaTextureJson,
|
||||
|
||||
COUNT
|
||||
};
|
||||
|
@ -47,6 +48,7 @@ enum class ModelBakeVersion : BakeVersion {
|
|||
// ATTENTION! See above.
|
||||
enum class TextureBakeVersion : BakeVersion {
|
||||
Initial = INITIAL_BAKE_VERSION,
|
||||
MetaTextureJson,
|
||||
|
||||
COUNT
|
||||
};
|
||||
|
|
|
@ -47,7 +47,7 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) :
|
|||
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
|
||||
packetReceiver.registerListener(PacketType::AvatarData, this, "queueIncomingPacket");
|
||||
packetReceiver.registerListener(PacketType::AdjustAvatarSorting, this, "handleAdjustAvatarSorting");
|
||||
packetReceiver.registerListener(PacketType::ViewFrustum, this, "handleViewFrustumPacket");
|
||||
packetReceiver.registerListener(PacketType::AvatarQuery, this, "handleAvatarQueryPacket");
|
||||
packetReceiver.registerListener(PacketType::AvatarIdentity, this, "handleAvatarIdentityPacket");
|
||||
packetReceiver.registerListener(PacketType::KillAvatar, this, "handleKillAvatarPacket");
|
||||
packetReceiver.registerListener(PacketType::NodeIgnoreRequest, this, "handleNodeIgnoreRequestPacket");
|
||||
|
@ -517,15 +517,13 @@ void AvatarMixer::handleAdjustAvatarSorting(QSharedPointer<ReceivedMessage> mess
|
|||
}
|
||||
|
||||
|
||||
void AvatarMixer::handleViewFrustumPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
void AvatarMixer::handleAvatarQueryPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
auto start = usecTimestampNow();
|
||||
getOrCreateClientData(senderNode);
|
||||
|
||||
if (senderNode->getLinkedData()) {
|
||||
AvatarMixerClientData* nodeData = dynamic_cast<AvatarMixerClientData*>(senderNode->getLinkedData());
|
||||
if (nodeData != nullptr) {
|
||||
nodeData->readViewFrustumPacket(message->getMessage());
|
||||
}
|
||||
AvatarMixerClientData* nodeData = dynamic_cast<AvatarMixerClientData*>(senderNode->getLinkedData());
|
||||
if (nodeData) {
|
||||
nodeData->readViewFrustumPacket(message->getMessage());
|
||||
}
|
||||
|
||||
auto end = usecTimestampNow();
|
||||
|
@ -685,7 +683,7 @@ void AvatarMixer::sendStatsPacket() {
|
|||
incomingPacketStats["handleNodeIgnoreRequestPacket"] = TIGHT_LOOP_STAT_UINT64(_handleNodeIgnoreRequestPacketElapsedTime);
|
||||
incomingPacketStats["handleRadiusIgnoreRequestPacket"] = TIGHT_LOOP_STAT_UINT64(_handleRadiusIgnoreRequestPacketElapsedTime);
|
||||
incomingPacketStats["handleRequestsDomainListDataPacket"] = TIGHT_LOOP_STAT_UINT64(_handleRequestsDomainListDataPacketElapsedTime);
|
||||
incomingPacketStats["handleViewFrustumPacket"] = TIGHT_LOOP_STAT_UINT64(_handleViewFrustumPacketElapsedTime);
|
||||
incomingPacketStats["handleAvatarQueryPacket"] = TIGHT_LOOP_STAT_UINT64(_handleViewFrustumPacketElapsedTime);
|
||||
|
||||
singleCoreTasks["incoming_packets"] = incomingPacketStats;
|
||||
singleCoreTasks["sendStats"] = (float)_sendStatsElapsedTime;
|
||||
|
|
|
@ -46,7 +46,7 @@ public slots:
|
|||
private slots:
|
||||
void queueIncomingPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer node);
|
||||
void handleAdjustAvatarSorting(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
void handleViewFrustumPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
void handleAvatarQueryPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
void handleAvatarIdentityPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
void handleKillAvatarPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
void handleNodeIgnoreRequestPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
|
|
|
@ -19,8 +19,6 @@
|
|||
AvatarMixerClientData::AvatarMixerClientData(const QUuid& nodeID) :
|
||||
NodeData(nodeID)
|
||||
{
|
||||
_currentViewFrustum.invalidate();
|
||||
|
||||
// in case somebody calls getSessionUUID on the AvatarData instance, make sure it has the right ID
|
||||
_avatar->setID(nodeID);
|
||||
}
|
||||
|
@ -129,11 +127,27 @@ void AvatarMixerClientData::removeFromRadiusIgnoringSet(SharedNodePointer self,
|
|||
}
|
||||
|
||||
void AvatarMixerClientData::readViewFrustumPacket(const QByteArray& message) {
|
||||
_currentViewFrustum.fromByteArray(message);
|
||||
_currentViewFrustums.clear();
|
||||
|
||||
auto sourceBuffer = reinterpret_cast<const unsigned char*>(message.constData());
|
||||
|
||||
uint8_t numFrustums = 0;
|
||||
memcpy(&numFrustums, sourceBuffer, sizeof(numFrustums));
|
||||
sourceBuffer += sizeof(numFrustums);
|
||||
|
||||
for (uint8_t i = 0; i < numFrustums; ++i) {
|
||||
ConicalViewFrustum frustum;
|
||||
sourceBuffer += frustum.deserialize(sourceBuffer);
|
||||
|
||||
_currentViewFrustums.push_back(frustum);
|
||||
}
|
||||
}
|
||||
|
||||
bool AvatarMixerClientData::otherAvatarInView(const AABox& otherAvatarBox) {
|
||||
return _currentViewFrustum.boxIntersectsKeyhole(otherAvatarBox);
|
||||
return std::any_of(std::begin(_currentViewFrustums), std::end(_currentViewFrustums),
|
||||
[&](const ConicalViewFrustum& viewFrustum) {
|
||||
return viewFrustum.intersects(otherAvatarBox);
|
||||
});
|
||||
}
|
||||
|
||||
void AvatarMixerClientData::loadJSONStats(QJsonObject& jsonObject) const {
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
#include <PortableHighResolutionClock.h>
|
||||
#include <SimpleMovingAverage.h>
|
||||
#include <UUIDHasher.h>
|
||||
#include <ViewFrustum.h>
|
||||
#include <shared/ConicalViewFrustum.h>
|
||||
|
||||
const QString OUTBOUND_AVATAR_DATA_STATS_KEY = "outbound_av_data_kbps";
|
||||
const QString INBOUND_AVATAR_DATA_STATS_KEY = "inbound_av_data_kbps";
|
||||
|
@ -110,7 +110,7 @@ public:
|
|||
bool getRequestsDomainListData() { return _requestsDomainListData; }
|
||||
void setRequestsDomainListData(bool requesting) { _requestsDomainListData = requesting; }
|
||||
|
||||
ViewFrustum getViewFrustum() const { return _currentViewFrustum; }
|
||||
const ConicalViewFrustums& getViewFrustums() const { return _currentViewFrustums; }
|
||||
|
||||
uint64_t getLastOtherAvatarEncodeTime(QUuid otherAvatar) const;
|
||||
void setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, uint64_t time);
|
||||
|
@ -150,7 +150,7 @@ private:
|
|||
|
||||
SimpleMovingAverage _avgOtherAvatarDataRate;
|
||||
std::unordered_set<QUuid> _radiusIgnoredOthers;
|
||||
ViewFrustum _currentViewFrustum;
|
||||
ConicalViewFrustums _currentViewFrustums;
|
||||
|
||||
int _recentOtherAvatarsInView { 0 };
|
||||
int _recentOtherAvatarsOutOfView { 0 };
|
||||
|
|
|
@ -222,8 +222,8 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
};
|
||||
|
||||
// prepare to sort
|
||||
ViewFrustum cameraView = nodeData->getViewFrustum();
|
||||
PrioritySortUtil::PriorityQueue<SortableAvatar> sortedAvatars(cameraView,
|
||||
const auto& cameraViews = nodeData->getViewFrustums();
|
||||
PrioritySortUtil::PriorityQueue<SortableAvatar> sortedAvatars(cameraViews,
|
||||
AvatarData::_avatarSortCoefficientSize,
|
||||
AvatarData::_avatarSortCoefficientCenter,
|
||||
AvatarData::_avatarSortCoefficientAge);
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
//
|
||||
// EntityPriorityQueue.cpp
|
||||
// assignment-client/src/entities
|
||||
//
|
||||
// Created by Andrew Meadows 2017.08.08
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "EntityPriorityQueue.h"
|
||||
|
||||
const float PrioritizedEntity::DO_NOT_SEND = -1.0e-6f;
|
||||
const float PrioritizedEntity::FORCE_REMOVE = -1.0e-5f;
|
||||
const float PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY = 1.0f;
|
||||
|
||||
void ConicalView::set(const ViewFrustum& viewFrustum) {
|
||||
// The ConicalView has two parts: a central sphere (same as ViewFrustum) and a circular cone that bounds the frustum part.
|
||||
// Why? Because approximate intersection tests are much faster to compute for a cone than for a frustum.
|
||||
_position = viewFrustum.getPosition();
|
||||
_direction = viewFrustum.getDirection();
|
||||
|
||||
// We cache the sin and cos of the half angle of the cone that bounds the frustum.
|
||||
// (the math here is left as an exercise for the reader)
|
||||
float A = viewFrustum.getAspectRatio();
|
||||
float t = tanf(0.5f * viewFrustum.getFieldOfView());
|
||||
_cosAngle = 1.0f / sqrtf(1.0f + (A * A + 1.0f) * (t * t));
|
||||
_sinAngle = sqrtf(1.0f - _cosAngle * _cosAngle);
|
||||
|
||||
_radius = viewFrustum.getCenterRadius();
|
||||
}
|
||||
|
||||
float ConicalView::computePriority(const AACube& cube) const {
|
||||
glm::vec3 p = cube.calcCenter() - _position; // position of bounding sphere in view-frame
|
||||
float d = glm::length(p); // distance to center of bounding sphere
|
||||
float r = 0.5f * cube.getScale(); // radius of bounding sphere
|
||||
if (d < _radius + r) {
|
||||
return r;
|
||||
}
|
||||
// We check the angle between the center of the cube and the _direction of the view.
|
||||
// If it is less than the sum of the half-angle from center of cone to outer edge plus
|
||||
// the half apparent angle of the bounding sphere then it is in view.
|
||||
//
|
||||
// The math here is left as an exercise for the reader with the following hints:
|
||||
// (1) We actually check the dot product of the cube's local position rather than the angle and
|
||||
// (2) we take advantage of this trig identity: cos(A+B) = cos(A)*cos(B) - sin(A)*sin(B)
|
||||
if (glm::dot(p, _direction) > sqrtf(d * d - r * r) * _cosAngle - r * _sinAngle) {
|
||||
const float AVOID_DIVIDE_BY_ZERO = 0.001f;
|
||||
return r / (d + AVOID_DIVIDE_BY_ZERO);
|
||||
}
|
||||
return PrioritizedEntity::DO_NOT_SEND;
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
//
|
||||
// EntityPriorityQueue.h
|
||||
// assignment-client/src/entities
|
||||
//
|
||||
// Created by Andrew Meadows 2017.08.08
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_EntityPriorityQueue_h
|
||||
#define hifi_EntityPriorityQueue_h
|
||||
|
||||
#include <queue>
|
||||
|
||||
#include <AACube.h>
|
||||
#include <EntityTreeElement.h>
|
||||
|
||||
const float SQRT_TWO_OVER_TWO = 0.7071067811865f;
|
||||
const float DEFAULT_VIEW_RADIUS = 10.0f;
|
||||
|
||||
// ConicalView is an approximation of a ViewFrustum for fast calculation of sort priority.
|
||||
class ConicalView {
|
||||
public:
|
||||
ConicalView() {}
|
||||
ConicalView(const ViewFrustum& viewFrustum) { set(viewFrustum); }
|
||||
void set(const ViewFrustum& viewFrustum);
|
||||
float computePriority(const AACube& cube) const;
|
||||
private:
|
||||
glm::vec3 _position { 0.0f, 0.0f, 0.0f };
|
||||
glm::vec3 _direction { 0.0f, 0.0f, 1.0f };
|
||||
float _sinAngle { SQRT_TWO_OVER_TWO };
|
||||
float _cosAngle { SQRT_TWO_OVER_TWO };
|
||||
float _radius { DEFAULT_VIEW_RADIUS };
|
||||
};
|
||||
|
||||
// PrioritizedEntity is a placeholder in a sorted queue.
|
||||
class PrioritizedEntity {
|
||||
public:
|
||||
static const float DO_NOT_SEND;
|
||||
static const float FORCE_REMOVE;
|
||||
static const float WHEN_IN_DOUBT_PRIORITY;
|
||||
|
||||
PrioritizedEntity(EntityItemPointer entity, float priority, bool forceRemove = false) : _weakEntity(entity), _rawEntityPointer(entity.get()), _priority(priority), _forceRemove(forceRemove) {}
|
||||
EntityItemPointer getEntity() const { return _weakEntity.lock(); }
|
||||
EntityItem* getRawEntityPointer() const { return _rawEntityPointer; }
|
||||
float getPriority() const { return _priority; }
|
||||
bool shouldForceRemove() const { return _forceRemove; }
|
||||
|
||||
class Compare {
|
||||
public:
|
||||
bool operator() (const PrioritizedEntity& A, const PrioritizedEntity& B) { return A._priority < B._priority; }
|
||||
};
|
||||
friend class Compare;
|
||||
|
||||
private:
|
||||
EntityItemWeakPointer _weakEntity;
|
||||
EntityItem* _rawEntityPointer;
|
||||
float _priority;
|
||||
bool _forceRemove;
|
||||
};
|
||||
|
||||
using EntityPriorityQueue = std::priority_queue< PrioritizedEntity, std::vector<PrioritizedEntity>, PrioritizedEntity::Compare >;
|
||||
|
||||
#endif // hifi_EntityPriorityQueue_h
|
|
@ -103,48 +103,41 @@ void EntityTreeSendThread::preDistributionProcessing() {
|
|||
void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData,
|
||||
bool viewFrustumChanged, bool isFullScene) {
|
||||
if (viewFrustumChanged || _traversal.finished()) {
|
||||
ViewFrustum viewFrustum;
|
||||
nodeData->copyCurrentViewFrustum(viewFrustum);
|
||||
EntityTreeElementPointer root = std::dynamic_pointer_cast<EntityTreeElement>(_myServer->getOctree()->getRoot());
|
||||
|
||||
|
||||
DiffTraversal::View newView;
|
||||
newView.viewFrustums = nodeData->getCurrentViews();
|
||||
|
||||
int32_t lodLevelOffset = nodeData->getBoundaryLevelAdjust() + (viewFrustumChanged ? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST);
|
||||
startNewTraversal(viewFrustum, root, lodLevelOffset, nodeData->getUsesFrustum());
|
||||
newView.lodScaleFactor = powf(2.0f, lodLevelOffset);
|
||||
|
||||
startNewTraversal(newView, root);
|
||||
|
||||
// When the viewFrustum changed the sort order may be incorrect, so we re-sort
|
||||
// and also use the opportunity to cull anything no longer in view
|
||||
if (viewFrustumChanged && !_sendQueue.empty()) {
|
||||
EntityPriorityQueue prevSendQueue;
|
||||
_sendQueue.swap(prevSendQueue);
|
||||
_entitiesInQueue.clear();
|
||||
std::swap(_sendQueue, prevSendQueue);
|
||||
assert(_sendQueue.empty());
|
||||
|
||||
// Re-add elements from previous traversal if they still need to be sent
|
||||
float lodScaleFactor = _traversal.getCurrentLODScaleFactor();
|
||||
glm::vec3 viewPosition = _traversal.getCurrentView().getPosition();
|
||||
while (!prevSendQueue.empty()) {
|
||||
EntityItemPointer entity = prevSendQueue.top().getEntity();
|
||||
bool forceRemove = prevSendQueue.top().shouldForceRemove();
|
||||
prevSendQueue.pop();
|
||||
if (entity) {
|
||||
if (!forceRemove) {
|
||||
bool success = false;
|
||||
AACube cube = entity->getQueryAACube(success);
|
||||
if (success) {
|
||||
if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) {
|
||||
float priority = _conicalView.computePriority(cube);
|
||||
if (priority != PrioritizedEntity::DO_NOT_SEND) {
|
||||
float distance = glm::distance(cube.calcCenter(), viewPosition) + MIN_VISIBLE_DISTANCE;
|
||||
float angularDiameter = cube.getScale() / distance;
|
||||
if (angularDiameter > MIN_ENTITY_ANGULAR_DIAMETER * lodScaleFactor) {
|
||||
_sendQueue.push(PrioritizedEntity(entity, priority));
|
||||
_entitiesInQueue.insert(entity.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));
|
||||
_entitiesInQueue.insert(entity.get());
|
||||
}
|
||||
float priority = PrioritizedEntity::DO_NOT_SEND;
|
||||
|
||||
if (forceRemove) {
|
||||
priority = PrioritizedEntity::FORCE_REMOVE;
|
||||
} else {
|
||||
_sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::FORCE_REMOVE, true));
|
||||
_entitiesInQueue.insert(entity.get());
|
||||
const auto& view = _traversal.getCurrentView();
|
||||
priority = view.computePriority(entity);
|
||||
}
|
||||
|
||||
if (priority != PrioritizedEntity::DO_NOT_SEND) {
|
||||
_sendQueue.emplace(entity, priority, forceRemove);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -215,10 +208,9 @@ bool EntityTreeSendThread::addDescendantsToExtraFlaggedEntities(const QUuid& fil
|
|||
return hasNewChild || hasNewDescendants;
|
||||
}
|
||||
|
||||
void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTreeElementPointer root, int32_t lodLevelOffset,
|
||||
bool usesViewFrustum) {
|
||||
void EntityTreeSendThread::startNewTraversal(const DiffTraversal::View& view, EntityTreeElementPointer root) {
|
||||
|
||||
DiffTraversal::Type type = _traversal.prepareNewTraversal(view, root, lodLevelOffset, usesViewFrustum);
|
||||
DiffTraversal::Type type = _traversal.prepareNewTraversal(view, root);
|
||||
// there are three types of traversal:
|
||||
//
|
||||
// (1) FirstTime = at login --> find everything in view
|
||||
|
@ -226,171 +218,80 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree
|
|||
// (3) Differential = view has changed --> find what has changed or in new view but not old
|
||||
//
|
||||
// The "scanCallback" we provide to the traversal depends on the type:
|
||||
//
|
||||
// The _conicalView is updated here as a cached view approximation used by the lambdas for efficient
|
||||
// computation of entity sorting priorities.
|
||||
//
|
||||
_conicalView.set(_traversal.getCurrentView());
|
||||
|
||||
switch (type) {
|
||||
case DiffTraversal::First:
|
||||
// When we get to a First traversal, clear the _knownState
|
||||
_knownState.clear();
|
||||
if (usesViewFrustum) {
|
||||
float lodScaleFactor = _traversal.getCurrentLODScaleFactor();
|
||||
glm::vec3 viewPosition = _traversal.getCurrentView().getPosition();
|
||||
_traversal.setScanCallback([=](DiffTraversal::VisibleElement& next) {
|
||||
next.element->forEachEntity([=](EntityItemPointer entity) {
|
||||
// Bail early if we've already checked this entity this frame
|
||||
if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) {
|
||||
return;
|
||||
}
|
||||
bool success = false;
|
||||
AACube cube = entity->getQueryAACube(success);
|
||||
if (success) {
|
||||
if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) {
|
||||
// Check the size of the entity, it's possible that a "too small to see" entity is included in a
|
||||
// larger octree cell because of its position (for example if it crosses the boundary of a cell it
|
||||
// pops to the next higher cell. So we want to check to see that the entity is large enough to be seen
|
||||
// before we consider including it.
|
||||
float distance = glm::distance(cube.calcCenter(), viewPosition) + MIN_VISIBLE_DISTANCE;
|
||||
float angularDiameter = cube.getScale() / distance;
|
||||
if (angularDiameter > MIN_ENTITY_ANGULAR_DIAMETER * lodScaleFactor) {
|
||||
float priority = _conicalView.computePriority(cube);
|
||||
_sendQueue.push(PrioritizedEntity(entity, priority));
|
||||
_entitiesInQueue.insert(entity.get());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));
|
||||
_entitiesInQueue.insert(entity.get());
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
_traversal.setScanCallback([this](DiffTraversal::VisibleElement& next) {
|
||||
next.element->forEachEntity([this](EntityItemPointer entity) {
|
||||
// Bail early if we've already checked this entity this frame
|
||||
if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) {
|
||||
return;
|
||||
}
|
||||
_sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));
|
||||
_entitiesInQueue.insert(entity.get());
|
||||
});
|
||||
});
|
||||
}
|
||||
break;
|
||||
case DiffTraversal::Repeat:
|
||||
if (usesViewFrustum) {
|
||||
float lodScaleFactor = _traversal.getCurrentLODScaleFactor();
|
||||
glm::vec3 viewPosition = _traversal.getCurrentView().getPosition();
|
||||
_traversal.setScanCallback([=](DiffTraversal::VisibleElement& next) {
|
||||
uint64_t startOfCompletedTraversal = _traversal.getStartOfCompletedTraversal();
|
||||
if (next.element->getLastChangedContent() > startOfCompletedTraversal) {
|
||||
next.element->forEachEntity([=](EntityItemPointer entity) {
|
||||
// Bail early if we've already checked this entity this frame
|
||||
if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) {
|
||||
return;
|
||||
}
|
||||
auto knownTimestamp = _knownState.find(entity.get());
|
||||
if (knownTimestamp == _knownState.end()) {
|
||||
bool success = false;
|
||||
AACube cube = entity->getQueryAACube(success);
|
||||
if (success) {
|
||||
if (next.intersection == ViewFrustum::INSIDE || _traversal.getCurrentView().cubeIntersectsKeyhole(cube)) {
|
||||
// See the DiffTraversal::First case for an explanation of the "entity is too small" check
|
||||
float distance = glm::distance(cube.calcCenter(), viewPosition) + MIN_VISIBLE_DISTANCE;
|
||||
float angularDiameter = cube.getScale() / distance;
|
||||
if (angularDiameter > MIN_ENTITY_ANGULAR_DIAMETER * lodScaleFactor) {
|
||||
float priority = _conicalView.computePriority(cube);
|
||||
_sendQueue.push(PrioritizedEntity(entity, priority));
|
||||
_entitiesInQueue.insert(entity.get());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));
|
||||
_entitiesInQueue.insert(entity.get());
|
||||
}
|
||||
} else if (entity->getLastEdited() > knownTimestamp->second
|
||||
|| entity->getLastChangedOnServer() > knownTimestamp->second) {
|
||||
// it is known and it changed --> put it on the queue with any priority
|
||||
// TODO: sort these correctly
|
||||
_sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));
|
||||
_entitiesInQueue.insert(entity.get());
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
_traversal.setScanCallback([this](DiffTraversal::VisibleElement& next) {
|
||||
uint64_t startOfCompletedTraversal = _traversal.getStartOfCompletedTraversal();
|
||||
if (next.element->getLastChangedContent() > startOfCompletedTraversal) {
|
||||
next.element->forEachEntity([this](EntityItemPointer entity) {
|
||||
// Bail early if we've already checked this entity this frame
|
||||
if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) {
|
||||
return;
|
||||
}
|
||||
auto knownTimestamp = _knownState.find(entity.get());
|
||||
if (knownTimestamp == _knownState.end()
|
||||
|| entity->getLastEdited() > knownTimestamp->second
|
||||
|| entity->getLastChangedOnServer() > knownTimestamp->second) {
|
||||
_sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));
|
||||
_entitiesInQueue.insert(entity.get());
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
case DiffTraversal::Differential:
|
||||
assert(usesViewFrustum);
|
||||
float lodScaleFactor = _traversal.getCurrentLODScaleFactor();
|
||||
glm::vec3 viewPosition = _traversal.getCurrentView().getPosition();
|
||||
float completedLODScaleFactor = _traversal.getCompletedLODScaleFactor();
|
||||
glm::vec3 completedViewPosition = _traversal.getCompletedView().getPosition();
|
||||
_traversal.setScanCallback([=] (DiffTraversal::VisibleElement& next) {
|
||||
next.element->forEachEntity([=](EntityItemPointer entity) {
|
||||
_traversal.setScanCallback([this](DiffTraversal::VisibleElement& next) {
|
||||
next.element->forEachEntity([&](EntityItemPointer entity) {
|
||||
// Bail early if we've already checked this entity this frame
|
||||
if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) {
|
||||
if (_sendQueue.contains(entity.get())) {
|
||||
return;
|
||||
}
|
||||
const auto& view = _traversal.getCurrentView();
|
||||
float priority = view.computePriority(entity);
|
||||
|
||||
if (priority != PrioritizedEntity::DO_NOT_SEND) {
|
||||
_sendQueue.emplace(entity, priority);
|
||||
}
|
||||
});
|
||||
});
|
||||
break;
|
||||
case DiffTraversal::Repeat:
|
||||
_traversal.setScanCallback([this](DiffTraversal::VisibleElement& next) {
|
||||
uint64_t startOfCompletedTraversal = _traversal.getStartOfCompletedTraversal();
|
||||
if (next.element->getLastChangedContent() > startOfCompletedTraversal) {
|
||||
next.element->forEachEntity([&](EntityItemPointer entity) {
|
||||
// Bail early if we've already checked this entity this frame
|
||||
if (_sendQueue.contains(entity.get())) {
|
||||
return;
|
||||
}
|
||||
float priority = PrioritizedEntity::DO_NOT_SEND;
|
||||
|
||||
auto knownTimestamp = _knownState.find(entity.get());
|
||||
if (knownTimestamp == _knownState.end()) {
|
||||
const auto& view = _traversal.getCurrentView();
|
||||
priority = view.computePriority(entity);
|
||||
|
||||
} else if (entity->getLastEdited() > knownTimestamp->second ||
|
||||
entity->getLastChangedOnServer() > knownTimestamp->second) {
|
||||
// it is known and it changed --> put it on the queue with any priority
|
||||
// TODO: sort these correctly
|
||||
priority = PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY;
|
||||
}
|
||||
|
||||
if (priority != PrioritizedEntity::DO_NOT_SEND) {
|
||||
_sendQueue.emplace(entity, priority);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
break;
|
||||
case DiffTraversal::Differential:
|
||||
assert(view.usesViewFrustums());
|
||||
_traversal.setScanCallback([this] (DiffTraversal::VisibleElement& next) {
|
||||
next.element->forEachEntity([&](EntityItemPointer entity) {
|
||||
// Bail early if we've already checked this entity this frame
|
||||
if (_sendQueue.contains(entity.get())) {
|
||||
return;
|
||||
}
|
||||
float priority = PrioritizedEntity::DO_NOT_SEND;
|
||||
|
||||
auto knownTimestamp = _knownState.find(entity.get());
|
||||
if (knownTimestamp == _knownState.end()) {
|
||||
bool success = false;
|
||||
AACube cube = entity->getQueryAACube(success);
|
||||
if (success) {
|
||||
if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) {
|
||||
// See the DiffTraversal::First case for an explanation of the "entity is too small" check
|
||||
float distance = glm::distance(cube.calcCenter(), viewPosition) + MIN_VISIBLE_DISTANCE;
|
||||
float angularDiameter = cube.getScale() / distance;
|
||||
if (angularDiameter > MIN_ENTITY_ANGULAR_DIAMETER * lodScaleFactor) {
|
||||
if (!_traversal.getCompletedView().cubeIntersectsKeyhole(cube)) {
|
||||
float priority = _conicalView.computePriority(cube);
|
||||
_sendQueue.push(PrioritizedEntity(entity, priority));
|
||||
_entitiesInQueue.insert(entity.get());
|
||||
} else {
|
||||
// If this entity was skipped last time because it was too small, we still need to send it
|
||||
distance = glm::distance(cube.calcCenter(), completedViewPosition) + MIN_VISIBLE_DISTANCE;
|
||||
angularDiameter = cube.getScale() / distance;
|
||||
if (angularDiameter <= MIN_ENTITY_ANGULAR_DIAMETER * completedLODScaleFactor) {
|
||||
// this object was skipped in last completed traversal
|
||||
float priority = _conicalView.computePriority(cube);
|
||||
_sendQueue.push(PrioritizedEntity(entity, priority));
|
||||
_entitiesInQueue.insert(entity.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));
|
||||
_entitiesInQueue.insert(entity.get());
|
||||
}
|
||||
} else if (entity->getLastEdited() > knownTimestamp->second
|
||||
|| entity->getLastChangedOnServer() > knownTimestamp->second) {
|
||||
const auto& view = _traversal.getCurrentView();
|
||||
priority = view.computePriority(entity);
|
||||
|
||||
} else if (entity->getLastEdited() > knownTimestamp->second ||
|
||||
entity->getLastChangedOnServer() > knownTimestamp->second) {
|
||||
// it is known and it changed --> put it on the queue with any priority
|
||||
// TODO: sort these correctly
|
||||
_sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));
|
||||
_entitiesInQueue.insert(entity.get());
|
||||
priority = PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY;
|
||||
}
|
||||
|
||||
if (priority != PrioritizedEntity::DO_NOT_SEND) {
|
||||
_sendQueue.emplace(entity, priority);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -479,11 +380,10 @@ bool EntityTreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstream
|
|||
}
|
||||
}
|
||||
_sendQueue.pop();
|
||||
_entitiesInQueue.erase(entity.get());
|
||||
}
|
||||
nodeData->stats.encodeStopped();
|
||||
if (_sendQueue.empty()) {
|
||||
assert(_entitiesInQueue.empty());
|
||||
assert(_sendQueue.empty());
|
||||
params.stopReason = EncodeBitstreamParams::FINISHED;
|
||||
_extraEncodeData->entities.clear();
|
||||
}
|
||||
|
@ -501,18 +401,15 @@ bool EntityTreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstream
|
|||
|
||||
void EntityTreeSendThread::editingEntityPointer(const EntityItemPointer& entity) {
|
||||
if (entity) {
|
||||
if (_entitiesInQueue.find(entity.get()) == _entitiesInQueue.end() && _knownState.find(entity.get()) != _knownState.end()) {
|
||||
bool success = false;
|
||||
AACube cube = entity->getQueryAACube(success);
|
||||
if (success) {
|
||||
// We can force a removal from _knownState if the current view is used and entity is out of view
|
||||
if (_traversal.doesCurrentUseViewFrustum() && !_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) {
|
||||
_sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::FORCE_REMOVE, true));
|
||||
_entitiesInQueue.insert(entity.get());
|
||||
}
|
||||
} else {
|
||||
_sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY, true));
|
||||
_entitiesInQueue.insert(entity.get());
|
||||
if (!_sendQueue.contains(entity.get()) && _knownState.find(entity.get()) != _knownState.end()) {
|
||||
const auto& view = _traversal.getCurrentView();
|
||||
float priority = view.computePriority(entity);
|
||||
|
||||
// We can force a removal from _knownState if the current view is used and entity is out of view
|
||||
if (priority == PrioritizedEntity::DO_NOT_SEND) {
|
||||
_sendQueue.emplace(entity, PrioritizedEntity::FORCE_REMOVE, true);
|
||||
} else if (priority == PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY) {
|
||||
_sendQueue.emplace(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,8 +17,9 @@
|
|||
#include "../octree/OctreeSendThread.h"
|
||||
|
||||
#include <DiffTraversal.h>
|
||||
#include <EntityPriorityQueue.h>
|
||||
#include <shared/ConicalViewFrustum.h>
|
||||
|
||||
#include "EntityPriorityQueue.h"
|
||||
|
||||
class EntityNodeData;
|
||||
class EntityItem;
|
||||
|
@ -41,8 +42,7 @@ private:
|
|||
bool addAncestorsToExtraFlaggedEntities(const QUuid& filteredEntityID, EntityItem& entityItem, EntityNodeData& nodeData);
|
||||
bool addDescendantsToExtraFlaggedEntities(const QUuid& filteredEntityID, EntityItem& entityItem, EntityNodeData& nodeData);
|
||||
|
||||
void startNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset,
|
||||
bool usesViewFrustum);
|
||||
void startNewTraversal(const DiffTraversal::View& viewFrustum, EntityTreeElementPointer root);
|
||||
bool traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters) override;
|
||||
|
||||
void preDistributionProcessing() override;
|
||||
|
@ -51,9 +51,7 @@ private:
|
|||
|
||||
DiffTraversal _traversal;
|
||||
EntityPriorityQueue _sendQueue;
|
||||
std::unordered_set<EntityItem*> _entitiesInQueue;
|
||||
std::unordered_map<EntityItem*, uint64_t> _knownState;
|
||||
ConicalView _conicalView; // cached optimized view for fast priority calculations
|
||||
|
||||
// packet construction stuff
|
||||
EntityTreeElementExtraEncodeDataPointer _extraEncodeData { new EntityTreeElementExtraEncodeData() };
|
||||
|
|
|
@ -14,32 +14,21 @@
|
|||
#include <NodeList.h>
|
||||
#include <OctreeLogging.h>
|
||||
|
||||
|
||||
OctreeHeadlessViewer::OctreeHeadlessViewer() {
|
||||
_viewFrustum.setProjection(glm::perspective(glm::radians(DEFAULT_FIELD_OF_VIEW_DEGREES), DEFAULT_ASPECT_RATIO, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP));
|
||||
}
|
||||
|
||||
void OctreeHeadlessViewer::queryOctree() {
|
||||
char serverType = getMyNodeType();
|
||||
PacketType packetType = getMyQueryMessageType();
|
||||
|
||||
_octreeQuery.setCameraPosition(_viewFrustum.getPosition());
|
||||
_octreeQuery.setCameraOrientation(_viewFrustum.getOrientation());
|
||||
_octreeQuery.setCameraFov(_viewFrustum.getFieldOfView());
|
||||
_octreeQuery.setCameraAspectRatio(_viewFrustum.getAspectRatio());
|
||||
_octreeQuery.setCameraNearClip(_viewFrustum.getNearClip());
|
||||
_octreeQuery.setCameraFarClip(_viewFrustum.getFarClip());
|
||||
_octreeQuery.setCameraEyeOffsetPosition(glm::vec3());
|
||||
_octreeQuery.setCameraCenterRadius(_viewFrustum.getCenterRadius());
|
||||
_octreeQuery.setOctreeSizeScale(_voxelSizeScale);
|
||||
_octreeQuery.setBoundaryLevelAdjust(_boundaryLevelAdjust);
|
||||
if (_hasViewFrustum) {
|
||||
ConicalViewFrustums views { _viewFrustum };
|
||||
_octreeQuery.setConicalViews(views);
|
||||
} else {
|
||||
_octreeQuery.clearConicalViews();
|
||||
}
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
auto node = nodeList->soloNodeOfType(serverType);
|
||||
if (node && node->getActiveSocket()) {
|
||||
_octreeQuery.setMaxQueryPacketsPerSecond(getMaxPacketsPerSecond());
|
||||
|
||||
auto queryPacket = NLPacket::create(packetType);
|
||||
|
||||
// encode the query data
|
||||
|
|
|
@ -20,9 +20,6 @@
|
|||
class OctreeHeadlessViewer : public OctreeProcessor {
|
||||
Q_OBJECT
|
||||
public:
|
||||
OctreeHeadlessViewer();
|
||||
virtual ~OctreeHeadlessViewer() {};
|
||||
|
||||
OctreeQuery& getOctreeQuery() { return _octreeQuery; }
|
||||
|
||||
static int parseOctreeStats(QSharedPointer<ReceivedMessage> message, SharedNodePointer sourceNode);
|
||||
|
@ -32,34 +29,32 @@ public slots:
|
|||
void queryOctree();
|
||||
|
||||
// setters for camera attributes
|
||||
void setPosition(const glm::vec3& position) { _viewFrustum.setPosition(position); }
|
||||
void setOrientation(const glm::quat& orientation) { _viewFrustum.setOrientation(orientation); }
|
||||
void setCenterRadius(float radius) { _viewFrustum.setCenterRadius(radius); }
|
||||
void setKeyholeRadius(float radius) { _viewFrustum.setCenterRadius(radius); } // TODO: remove this legacy support
|
||||
void setPosition(const glm::vec3& position) { _hasViewFrustum = true; _viewFrustum.setPosition(position); }
|
||||
void setOrientation(const glm::quat& orientation) { _hasViewFrustum = true; _viewFrustum.setOrientation(orientation); }
|
||||
void setCenterRadius(float radius) { _hasViewFrustum = true; _viewFrustum.setCenterRadius(radius); }
|
||||
void setKeyholeRadius(float radius) { _hasViewFrustum = true; _viewFrustum.setCenterRadius(radius); } // TODO: remove this legacy support
|
||||
|
||||
// setters for LOD and PPS
|
||||
void setVoxelSizeScale(float sizeScale) { _voxelSizeScale = sizeScale; }
|
||||
void setBoundaryLevelAdjust(int boundaryLevelAdjust) { _boundaryLevelAdjust = boundaryLevelAdjust; }
|
||||
void setMaxPacketsPerSecond(int maxPacketsPerSecond) { _maxPacketsPerSecond = maxPacketsPerSecond; }
|
||||
void setVoxelSizeScale(float sizeScale) { _octreeQuery.setOctreeSizeScale(sizeScale) ; }
|
||||
void setBoundaryLevelAdjust(int boundaryLevelAdjust) { _octreeQuery.setBoundaryLevelAdjust(boundaryLevelAdjust); }
|
||||
void setMaxPacketsPerSecond(int maxPacketsPerSecond) { _octreeQuery.setMaxQueryPacketsPerSecond(maxPacketsPerSecond); }
|
||||
|
||||
// getters for camera attributes
|
||||
const glm::vec3& getPosition() const { return _viewFrustum.getPosition(); }
|
||||
const glm::quat& getOrientation() const { return _viewFrustum.getOrientation(); }
|
||||
|
||||
// getters for LOD and PPS
|
||||
float getVoxelSizeScale() const { return _voxelSizeScale; }
|
||||
int getBoundaryLevelAdjust() const { return _boundaryLevelAdjust; }
|
||||
int getMaxPacketsPerSecond() const { return _maxPacketsPerSecond; }
|
||||
float getVoxelSizeScale() const { return _octreeQuery.getOctreeSizeScale(); }
|
||||
int getBoundaryLevelAdjust() const { return _octreeQuery.getBoundaryLevelAdjust(); }
|
||||
int getMaxPacketsPerSecond() const { return _octreeQuery.getMaxQueryPacketsPerSecond(); }
|
||||
|
||||
unsigned getOctreeElementsCount() const { return _tree->getOctreeElementsCount(); }
|
||||
|
||||
private:
|
||||
OctreeQuery _octreeQuery;
|
||||
|
||||
bool _hasViewFrustum { false };
|
||||
ViewFrustum _viewFrustum;
|
||||
float _voxelSizeScale { DEFAULT_OCTREE_SIZE_SCALE };
|
||||
int _boundaryLevelAdjust { 0 };
|
||||
int _maxPacketsPerSecond { DEFAULT_MAX_OCTREE_PPS };
|
||||
};
|
||||
|
||||
#endif // hifi_OctreeHeadlessViewer_h
|
||||
|
|
|
@ -330,8 +330,9 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode*
|
|||
} else {
|
||||
// we aren't forcing a full scene, check if something else suggests we should
|
||||
isFullScene = nodeData->haveJSONParametersChanged() ||
|
||||
(nodeData->getUsesFrustum()
|
||||
&& ((!viewFrustumChanged && nodeData->getViewFrustumJustStoppedChanging()) || nodeData->hasLodChanged()));
|
||||
(nodeData->hasConicalViews() &&
|
||||
(nodeData->getViewFrustumJustStoppedChanging() ||
|
||||
nodeData->hasLodChanged()));
|
||||
}
|
||||
|
||||
if (nodeData->isPacketWaiting()) {
|
||||
|
@ -445,7 +446,6 @@ void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, Octre
|
|||
params.trackSend = [this](const QUuid& dataID, quint64 dataEdited) {
|
||||
_myServer->trackSend(dataID, dataEdited, _nodeUuid);
|
||||
};
|
||||
nodeData->copyCurrentViewFrustum(params.viewFrustum);
|
||||
|
||||
bool somethingToSend = true; // assume we have something
|
||||
bool hadSomething = hasSomethingToSend(nodeData);
|
||||
|
|
|
@ -294,7 +294,6 @@ void EntityScriptServer::run() {
|
|||
queryJSONParameters[EntityJSONQueryProperties::FLAGS_PROPERTY] = queryFlags;
|
||||
|
||||
// setup the JSON parameters so that OctreeQuery does not use a frustum and uses our JSON filter
|
||||
_entityViewer.getOctreeQuery().setUsesFrustum(false);
|
||||
_entityViewer.getOctreeQuery().setJSONParameters(queryJSONParameters);
|
||||
|
||||
entityScriptingInterface->setEntityTree(_entityViewer.getTree());
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
<span class='step-description'>
|
||||
<a target='_blank' href='https://docs.highfidelity.com/create-and-explore/start-working-in-your-sandbox/place-names'>Place names</a> are similar to web addresses. Users who want to visit your domain can
|
||||
enter its Place Name in High Fidelity's Interface. You can choose a Place Name for your domain.</br>
|
||||
People can also use your <b>domain's IP address (shown below)</b> to visit your High Fidelity domain.
|
||||
Your domain may also be reachable by <b>IP address</b>.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -35,10 +35,10 @@
|
|||
<div class="centered-hack-parent">
|
||||
<div id="place-name-group" class="centered-hack">
|
||||
<p id="place-name-link"></p>
|
||||
<div id="place-name-edit">
|
||||
<span class='glyphicon glyphicon-pencil'></span>
|
||||
<a href="#" id="change-place-name">Choose a custom Place Name instead</a>
|
||||
</div>
|
||||
<div id="place-name-edit">
|
||||
<span class='glyphicon glyphicon-pencil'></span>
|
||||
<a href="#" id="change-place-name">Choose a custom Place Name instead</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -729,7 +729,7 @@ Rectangle {
|
|||
}
|
||||
lightboxPopup.button2text = "CONFIRM";
|
||||
lightboxPopup.button2method = function() {
|
||||
MyAvatar.skeletonModelURL = '';
|
||||
MyAvatar.useFullAvatarURL('');
|
||||
root.activeView = "giftAsset";
|
||||
lightboxPopup.visible = false;
|
||||
};
|
||||
|
|
|
@ -47,6 +47,13 @@ Item {
|
|||
|
||||
onWalletAuthenticatedStatusResult: {
|
||||
submitPassphraseInputButton.enabled = true;
|
||||
|
||||
// It's not possible to auth with a blank passphrase,
|
||||
// so bail early if we get this signal without anything in the passphrase field
|
||||
if (passphraseField.text === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isAuthenticated) {
|
||||
errorText.text = "Authentication failed - please try again.";
|
||||
passphraseField.error = true;
|
||||
|
@ -211,6 +218,10 @@ Item {
|
|||
error = false;
|
||||
focus = true;
|
||||
forceActiveFocus();
|
||||
} else {
|
||||
showPassphrase.checked = false;
|
||||
passphraseField.text = "";
|
||||
error = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -967,7 +967,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
_entitySimulation(new PhysicalEntitySimulation()),
|
||||
_physicsEngine(new PhysicsEngine(Vectors::ZERO)),
|
||||
_entityClipboard(new EntityTree()),
|
||||
_lastQueriedTime(usecTimestampNow()),
|
||||
_previousScriptLocation("LastScriptLocation", DESKTOP_LOCATION),
|
||||
_fieldOfView("fieldOfView", DEFAULT_FIELD_OF_VIEW_DEGREES),
|
||||
_hmdTabletScale("hmdTabletScale", DEFAULT_HMD_TABLET_SCALE_PERCENT),
|
||||
|
@ -1325,6 +1324,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
|
||||
// Create the main thread context, the GPU backend, and the display plugins
|
||||
initializeGL();
|
||||
DependencyManager::get<TextureCache>()->setGPUContext(_gpuContext);
|
||||
qCDebug(interfaceapp, "Initialized Display.");
|
||||
// Create the rendering engine. This can be slow on some machines due to lots of
|
||||
// GPU pipeline creation.
|
||||
|
@ -5084,7 +5084,7 @@ void Application::reloadResourceCaches() {
|
|||
resetPhysicsReadyInformation();
|
||||
|
||||
// Query the octree to refresh everything in view
|
||||
_lastQueriedTime = 0;
|
||||
_queryExpiry = SteadyClock::now();
|
||||
_octreeQuery.incrementConnectionID();
|
||||
|
||||
queryOctree(NodeType::EntityServer, PacketType::EntityQuery);
|
||||
|
@ -5218,6 +5218,78 @@ void Application::updateDialogs(float deltaTime) const {
|
|||
}
|
||||
}
|
||||
|
||||
void Application::updateSecondaryCameraViewFrustum() {
|
||||
// TODO: Fix this by modeling the way the secondary camera works on how the main camera works
|
||||
// ie. Use a camera object stored in the game logic and informs the Engine on where the secondary
|
||||
// camera should be.
|
||||
|
||||
// Code based on SecondaryCameraJob
|
||||
auto renderConfig = _renderEngine->getConfiguration();
|
||||
assert(renderConfig);
|
||||
auto camera = dynamic_cast<SecondaryCameraJobConfig*>(renderConfig->getConfig("SecondaryCamera"));
|
||||
|
||||
if (!camera || !camera->isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ViewFrustum secondaryViewFrustum;
|
||||
if (camera->mirrorProjection && !camera->attachedEntityId.isNull()) {
|
||||
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
||||
auto entityProperties = entityScriptingInterface->getEntityProperties(camera->attachedEntityId);
|
||||
glm::vec3 mirrorPropertiesPosition = entityProperties.getPosition();
|
||||
glm::quat mirrorPropertiesRotation = entityProperties.getRotation();
|
||||
glm::vec3 mirrorPropertiesDimensions = entityProperties.getDimensions();
|
||||
glm::vec3 halfMirrorPropertiesDimensions = 0.5f * mirrorPropertiesDimensions;
|
||||
|
||||
// setup mirror from world as inverse of world from mirror transformation using inverted x and z for mirrored image
|
||||
// TODO: we are assuming here that UP is world y-axis
|
||||
glm::mat4 worldFromMirrorRotation = glm::mat4_cast(mirrorPropertiesRotation) * glm::scale(vec3(-1.0f, 1.0f, -1.0f));
|
||||
glm::mat4 worldFromMirrorTranslation = glm::translate(mirrorPropertiesPosition);
|
||||
glm::mat4 worldFromMirror = worldFromMirrorTranslation * worldFromMirrorRotation;
|
||||
glm::mat4 mirrorFromWorld = glm::inverse(worldFromMirror);
|
||||
|
||||
// get mirror camera position by reflecting main camera position's z coordinate in mirror space
|
||||
glm::vec3 mainCameraPositionWorld = getCamera().getPosition();
|
||||
glm::vec3 mainCameraPositionMirror = vec3(mirrorFromWorld * vec4(mainCameraPositionWorld, 1.0f));
|
||||
glm::vec3 mirrorCameraPositionMirror = vec3(mainCameraPositionMirror.x, mainCameraPositionMirror.y,
|
||||
-mainCameraPositionMirror.z);
|
||||
glm::vec3 mirrorCameraPositionWorld = vec3(worldFromMirror * vec4(mirrorCameraPositionMirror, 1.0f));
|
||||
|
||||
// set frustum position to be mirrored camera and set orientation to mirror's adjusted rotation
|
||||
glm::quat mirrorCameraOrientation = glm::quat_cast(worldFromMirrorRotation);
|
||||
secondaryViewFrustum.setPosition(mirrorCameraPositionWorld);
|
||||
secondaryViewFrustum.setOrientation(mirrorCameraOrientation);
|
||||
|
||||
// build frustum using mirror space translation of mirrored camera
|
||||
float nearClip = mirrorCameraPositionMirror.z + mirrorPropertiesDimensions.z * 2.0f;
|
||||
glm::vec3 upperRight = halfMirrorPropertiesDimensions - mirrorCameraPositionMirror;
|
||||
glm::vec3 bottomLeft = -halfMirrorPropertiesDimensions - mirrorCameraPositionMirror;
|
||||
glm::mat4 frustum = glm::frustum(bottomLeft.x, upperRight.x, bottomLeft.y, upperRight.y, nearClip, camera->farClipPlaneDistance);
|
||||
secondaryViewFrustum.setProjection(frustum);
|
||||
} else {
|
||||
if (!camera->attachedEntityId.isNull()) {
|
||||
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
||||
auto entityProperties = entityScriptingInterface->getEntityProperties(camera->attachedEntityId);
|
||||
secondaryViewFrustum.setPosition(entityProperties.getPosition());
|
||||
secondaryViewFrustum.setOrientation(entityProperties.getRotation());
|
||||
} else {
|
||||
secondaryViewFrustum.setPosition(camera->position);
|
||||
secondaryViewFrustum.setOrientation(camera->orientation);
|
||||
}
|
||||
|
||||
float aspectRatio = (float)camera->textureWidth / (float)camera->textureHeight;
|
||||
secondaryViewFrustum.setProjection(camera->vFoV,
|
||||
aspectRatio,
|
||||
camera->nearClipPlaneDistance,
|
||||
camera->farClipPlaneDistance);
|
||||
}
|
||||
// Without calculating the bound planes, the secondary camera will use the same culling frustum as the main camera,
|
||||
// which is not what we want here.
|
||||
secondaryViewFrustum.calculate();
|
||||
|
||||
_conicalViews.push_back(secondaryViewFrustum);
|
||||
}
|
||||
|
||||
static bool domainLoadingInProgress = false;
|
||||
|
||||
void Application::update(float deltaTime) {
|
||||
|
@ -5571,6 +5643,13 @@ void Application::update(float deltaTime) {
|
|||
{
|
||||
QMutexLocker viewLocker(&_viewMutex);
|
||||
_myCamera.loadViewFrustum(_viewFrustum);
|
||||
|
||||
_conicalViews.clear();
|
||||
_conicalViews.push_back(_viewFrustum);
|
||||
// TODO: Fix this by modeling the way the secondary camera works on how the main camera works
|
||||
// ie. Use a camera object stored in the game logic and informs the Engine on where the secondary
|
||||
// camera should be.
|
||||
updateSecondaryCameraViewFrustum();
|
||||
}
|
||||
|
||||
quint64 now = usecTimestampNow();
|
||||
|
@ -5580,18 +5659,31 @@ void Application::update(float deltaTime) {
|
|||
PROFILE_RANGE_EX(app, "QueryOctree", 0xffff0000, (uint64_t)getActiveDisplayPlugin()->presentCount());
|
||||
PerformanceTimer perfTimer("queryOctree");
|
||||
QMutexLocker viewLocker(&_viewMutex);
|
||||
quint64 sinceLastQuery = now - _lastQueriedTime;
|
||||
const quint64 TOO_LONG_SINCE_LAST_QUERY = 3 * USECS_PER_SECOND;
|
||||
bool queryIsDue = sinceLastQuery > TOO_LONG_SINCE_LAST_QUERY;
|
||||
bool viewIsDifferentEnough = !_lastQueriedViewFrustum.isVerySimilar(_viewFrustum);
|
||||
|
||||
bool viewIsDifferentEnough = false;
|
||||
if (_conicalViews.size() == _lastQueriedViews.size()) {
|
||||
for (size_t i = 0; i < _conicalViews.size(); ++i) {
|
||||
if (!_conicalViews[i].isVerySimilar(_lastQueriedViews[i])) {
|
||||
viewIsDifferentEnough = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
viewIsDifferentEnough = true;
|
||||
}
|
||||
|
||||
|
||||
// if it's been a while since our last query or the view has significantly changed then send a query, otherwise suppress it
|
||||
if (queryIsDue || viewIsDifferentEnough) {
|
||||
_lastQueriedTime = now;
|
||||
static const std::chrono::seconds MIN_PERIOD_BETWEEN_QUERIES { 3 };
|
||||
auto now = SteadyClock::now();
|
||||
if (now > _queryExpiry || viewIsDifferentEnough) {
|
||||
if (DependencyManager::get<SceneScriptingInterface>()->shouldRenderEntities()) {
|
||||
queryOctree(NodeType::EntityServer, PacketType::EntityQuery);
|
||||
}
|
||||
sendAvatarViewFrustum();
|
||||
_lastQueriedViewFrustum = _viewFrustum;
|
||||
queryAvatars();
|
||||
|
||||
_lastQueriedViews = _conicalViews;
|
||||
_queryExpiry = now + MIN_PERIOD_BETWEEN_QUERIES;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5763,10 +5855,20 @@ void Application::update(float deltaTime) {
|
|||
}
|
||||
}
|
||||
|
||||
void Application::sendAvatarViewFrustum() {
|
||||
QByteArray viewFrustumByteArray = _viewFrustum.toByteArray();
|
||||
auto avatarPacket = NLPacket::create(PacketType::ViewFrustum, viewFrustumByteArray.size());
|
||||
avatarPacket->write(viewFrustumByteArray);
|
||||
void Application::queryAvatars() {
|
||||
auto avatarPacket = NLPacket::create(PacketType::AvatarQuery);
|
||||
auto destinationBuffer = reinterpret_cast<unsigned char*>(avatarPacket->getPayload());
|
||||
unsigned char* bufferStart = destinationBuffer;
|
||||
|
||||
uint8_t numFrustums = (uint8_t)_conicalViews.size();
|
||||
memcpy(destinationBuffer, &numFrustums, sizeof(numFrustums));
|
||||
destinationBuffer += sizeof(numFrustums);
|
||||
|
||||
for (const auto& view : _conicalViews) {
|
||||
destinationBuffer += view.serialize(destinationBuffer);
|
||||
}
|
||||
|
||||
avatarPacket->setPayloadSize(destinationBuffer - bufferStart);
|
||||
|
||||
DependencyManager::get<NodeList>()->broadcastToNodes(std::move(avatarPacket), NodeSet() << NodeType::AvatarMixer);
|
||||
}
|
||||
|
@ -5828,16 +5930,8 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType) {
|
|||
return; // bail early if settings are not loaded
|
||||
}
|
||||
|
||||
ViewFrustum viewFrustum;
|
||||
copyViewFrustum(viewFrustum);
|
||||
_octreeQuery.setCameraPosition(viewFrustum.getPosition());
|
||||
_octreeQuery.setCameraOrientation(viewFrustum.getOrientation());
|
||||
_octreeQuery.setCameraFov(viewFrustum.getFieldOfView());
|
||||
_octreeQuery.setCameraAspectRatio(viewFrustum.getAspectRatio());
|
||||
_octreeQuery.setCameraNearClip(viewFrustum.getNearClip());
|
||||
_octreeQuery.setCameraFarClip(viewFrustum.getFarClip());
|
||||
_octreeQuery.setCameraEyeOffsetPosition(glm::vec3());
|
||||
_octreeQuery.setCameraCenterRadius(viewFrustum.getCenterRadius());
|
||||
_octreeQuery.setConicalViews(_conicalViews);
|
||||
|
||||
auto lodManager = DependencyManager::get<LODManager>();
|
||||
_octreeQuery.setOctreeSizeScale(lodManager->getOctreeSizeScale());
|
||||
_octreeQuery.setBoundaryLevelAdjust(lodManager->getBoundaryLevelAdjust());
|
||||
|
@ -6046,7 +6140,7 @@ void Application::nodeActivated(SharedNodePointer node) {
|
|||
// If we get a new EntityServer activated, reset lastQueried time
|
||||
// so we will do a proper query during update
|
||||
if (node->getType() == NodeType::EntityServer) {
|
||||
_lastQueriedTime = 0;
|
||||
_queryExpiry = SteadyClock::now();
|
||||
_octreeQuery.incrementConnectionID();
|
||||
}
|
||||
|
||||
|
@ -6055,6 +6149,8 @@ void Application::nodeActivated(SharedNodePointer node) {
|
|||
}
|
||||
|
||||
if (node->getType() == NodeType::AvatarMixer) {
|
||||
_queryExpiry = SteadyClock::now();
|
||||
|
||||
// new avatar mixer, send off our identity packet on next update loop
|
||||
// Reset skeletonModelUrl if the last server modified our choice.
|
||||
// Override the avatar url (but not model name) here too.
|
||||
|
|
|
@ -47,6 +47,7 @@
|
|||
#include <AbstractUriHandler.h>
|
||||
#include <shared/RateCounter.h>
|
||||
#include <ThreadSafeValueCache.h>
|
||||
#include <shared/ConicalViewFrustum.h>
|
||||
#include <shared/FileLogger.h>
|
||||
|
||||
#include <RunningMarker.h>
|
||||
|
@ -110,7 +111,8 @@ class Application : public QApplication,
|
|||
public AbstractViewStateInterface,
|
||||
public AbstractScriptingServicesInterface,
|
||||
public AbstractUriHandler,
|
||||
public PluginContainer {
|
||||
public PluginContainer
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
// TODO? Get rid of those
|
||||
|
@ -149,6 +151,8 @@ public:
|
|||
void initializeRenderEngine();
|
||||
void initializeUi();
|
||||
|
||||
void updateSecondaryCameraViewFrustum();
|
||||
|
||||
void updateCamera(RenderArgs& renderArgs, float deltaTime);
|
||||
void paintGL();
|
||||
void resizeGL();
|
||||
|
@ -178,6 +182,9 @@ public:
|
|||
// which might be different from the viewFrustum, i.e. shadowmap
|
||||
// passes, mirror window passes, etc
|
||||
void copyDisplayViewFrustum(ViewFrustum& viewOut) const;
|
||||
|
||||
const ConicalViewFrustums& getConicalViews() const override { return _conicalViews; }
|
||||
|
||||
const OctreePacketProcessor& getOctreePacketProcessor() const { return _octreeProcessor; }
|
||||
QSharedPointer<EntityTreeRenderer> getEntities() const { return DependencyManager::get<EntityTreeRenderer>(); }
|
||||
QUndoStack* getUndoStack() { return &_undoStack; }
|
||||
|
@ -486,9 +493,9 @@ private:
|
|||
void updateDialogs(float deltaTime) const;
|
||||
|
||||
void queryOctree(NodeType_t serverType, PacketType packetType);
|
||||
void queryAvatars();
|
||||
|
||||
int sendNackPackets();
|
||||
void sendAvatarViewFrustum();
|
||||
|
||||
std::shared_ptr<MyAvatar> getMyAvatar() const;
|
||||
|
||||
|
@ -569,9 +576,14 @@ private:
|
|||
|
||||
mutable QMutex _viewMutex { QMutex::Recursive };
|
||||
ViewFrustum _viewFrustum; // current state of view frustum, perspective, orientation, etc.
|
||||
ViewFrustum _lastQueriedViewFrustum; /// last view frustum used to query octree servers (voxels)
|
||||
ViewFrustum _displayViewFrustum;
|
||||
quint64 _lastQueriedTime;
|
||||
|
||||
ConicalViewFrustums _conicalViews;
|
||||
ConicalViewFrustums _lastQueriedViews; // last views used to query servers
|
||||
|
||||
using SteadyClock = std::chrono::steady_clock;
|
||||
using TimePoint = SteadyClock::time_point;
|
||||
TimePoint _queryExpiry;
|
||||
|
||||
OctreeQuery _octreeQuery { true }; // NodeData derived class for querying octee cells from octree servers
|
||||
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
|
||||
#include "Crashpad.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#if HAS_CRASHPAD
|
||||
|
@ -20,7 +22,7 @@
|
|||
#include <QStandardPaths>
|
||||
#include <QDir>
|
||||
|
||||
#include <BuildInfo.h>
|
||||
#include <Windows.h>
|
||||
|
||||
#include <client/crashpad_client.h>
|
||||
#include <client/crash_report_database.h>
|
||||
|
@ -28,28 +30,27 @@
|
|||
#include <client/annotation_list.h>
|
||||
#include <client/crashpad_info.h>
|
||||
|
||||
#include <BuildInfo.h>
|
||||
|
||||
using namespace crashpad;
|
||||
|
||||
static const std::string BACKTRACE_URL { CMAKE_BACKTRACE_URL };
|
||||
static const std::string BACKTRACE_TOKEN { CMAKE_BACKTRACE_TOKEN };
|
||||
|
||||
static std::wstring gIPCPipe;
|
||||
|
||||
extern QString qAppFileName();
|
||||
|
||||
CrashpadClient* client { nullptr };
|
||||
std::mutex annotationMutex;
|
||||
crashpad::SimpleStringDictionary* crashpadAnnotations { nullptr };
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
LONG WINAPI vectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) {
|
||||
if (!client) {
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
if (pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_HEAP_CORRUPTION ||
|
||||
pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_STACK_BUFFER_OVERRUN) {
|
||||
CrashpadClient client;
|
||||
if (gIPCPipe.length()) {
|
||||
client.SetHandlerIPCPipe(gIPCPipe);
|
||||
}
|
||||
client.DumpAndCrash(pExceptionInfo);
|
||||
client->DumpAndCrash(pExceptionInfo);
|
||||
}
|
||||
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
|
@ -60,7 +61,8 @@ bool startCrashHandler() {
|
|||
return false;
|
||||
}
|
||||
|
||||
CrashpadClient client;
|
||||
assert(!client);
|
||||
client = new CrashpadClient();
|
||||
std::vector<std::string> arguments;
|
||||
|
||||
std::map<std::string, std::string> annotations;
|
||||
|
@ -96,12 +98,9 @@ bool startCrashHandler() {
|
|||
// Enable automated uploads.
|
||||
database->GetSettings()->SetUploadsEnabled(true);
|
||||
|
||||
bool result = client.StartHandler(handler, db, db, BACKTRACE_URL, annotations, arguments, true, true);
|
||||
gIPCPipe = client.GetHandlerIPCPipe();
|
||||
|
||||
AddVectoredExceptionHandler(0, vectoredExceptionHandler);
|
||||
|
||||
return result;
|
||||
return client->StartHandler(handler, db, db, BACKTRACE_URL, annotations, arguments, true, true);
|
||||
}
|
||||
|
||||
void setCrashAnnotation(std::string name, std::string value) {
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
#include <UsersScriptingInterface.h>
|
||||
#include <UUID.h>
|
||||
#include <avatars-renderer/OtherAvatar.h>
|
||||
#include <shared/ConicalViewFrustum.h>
|
||||
|
||||
#include "Application.h"
|
||||
#include "AvatarManager.h"
|
||||
|
@ -155,9 +156,9 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
|
|||
AvatarSharedPointer _avatar;
|
||||
};
|
||||
|
||||
ViewFrustum cameraView;
|
||||
qApp->copyDisplayViewFrustum(cameraView);
|
||||
PrioritySortUtil::PriorityQueue<SortableAvatar> sortedAvatars(cameraView,
|
||||
|
||||
const auto& views = qApp->getConicalViews();
|
||||
PrioritySortUtil::PriorityQueue<SortableAvatar> sortedAvatars(views,
|
||||
AvatarData::_avatarSortCoefficientSize,
|
||||
AvatarData::_avatarSortCoefficientCenter,
|
||||
AvatarData::_avatarSortCoefficientAge);
|
||||
|
|
|
@ -1486,6 +1486,15 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
|||
std::shared_ptr<QMetaObject::Connection> skeletonConnection = std::make_shared<QMetaObject::Connection>();
|
||||
*skeletonConnection = QObject::connect(_skeletonModel.get(), &SkeletonModel::skeletonLoaded, [this, skeletonModelChangeCount, skeletonConnection]() {
|
||||
if (skeletonModelChangeCount == _skeletonModelChangeCount) {
|
||||
|
||||
if (_fullAvatarModelName.isEmpty()) {
|
||||
// Store the FST file name into preferences
|
||||
const auto& mapping = _skeletonModel->getGeometry()->getMapping();
|
||||
if (mapping.value("name").isValid()) {
|
||||
_fullAvatarModelName = mapping.value("name").toString();
|
||||
}
|
||||
}
|
||||
|
||||
initHeadBones();
|
||||
_skeletonModel->setCauterizeBoneSet(_headBoneSet);
|
||||
_fstAnimGraphOverrideUrl = _skeletonModel->getGeometry()->getAnimGraphOverrideUrl();
|
||||
|
@ -1548,12 +1557,7 @@ void MyAvatar::useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelN
|
|||
|
||||
if (_fullAvatarURLFromPreferences != fullAvatarURL) {
|
||||
_fullAvatarURLFromPreferences = fullAvatarURL;
|
||||
if (modelName.isEmpty()) {
|
||||
QVariantHash fullAvatarFST = FSTReader::downloadMapping(_fullAvatarURLFromPreferences.toString());
|
||||
_fullAvatarModelName = fullAvatarFST["name"].toString();
|
||||
} else {
|
||||
_fullAvatarModelName = modelName;
|
||||
}
|
||||
_fullAvatarModelName = modelName;
|
||||
}
|
||||
|
||||
const QString& urlString = fullAvatarURL.toString();
|
||||
|
@ -1561,8 +1565,8 @@ void MyAvatar::useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelN
|
|||
setSkeletonModelURL(fullAvatarURL);
|
||||
UserActivityLogger::getInstance().changedModel("skeleton", urlString);
|
||||
}
|
||||
|
||||
markIdentityDataChanged();
|
||||
|
||||
}
|
||||
|
||||
void MyAvatar::setAttachmentData(const QVector<AttachmentData>& attachmentData) {
|
||||
|
|
|
@ -301,7 +301,7 @@ bool QmlCommerce::uninstallApp(const QString& itemHref) {
|
|||
// Read from the file to know what .js script to stop
|
||||
QFile appFile(_appsPath + "/" + appHref.fileName());
|
||||
if (!appFile.open(QIODevice::ReadOnly)) {
|
||||
qCDebug(commerce) << "Couldn't open local .app.json file for deletion.";
|
||||
qCDebug(commerce) << "Couldn't open local .app.json file for deletion. Cannot continue with app uninstallation. App filename is:" << appHref.fileName();
|
||||
return false;
|
||||
}
|
||||
QJsonDocument appFileJsonDocument = QJsonDocument::fromJson(appFile.readAll());
|
||||
|
@ -309,15 +309,13 @@ bool QmlCommerce::uninstallApp(const QString& itemHref) {
|
|||
QString scriptUrl = appFileJsonObject["scriptURL"].toString();
|
||||
|
||||
if (!DependencyManager::get<ScriptEngines>()->stopScript(scriptUrl.trimmed(), false)) {
|
||||
qCDebug(commerce) << "Couldn't stop script.";
|
||||
return false;
|
||||
qCWarning(commerce) << "Couldn't stop script during app uninstall. Continuing anyway. ScriptURL is:" << scriptUrl.trimmed();
|
||||
}
|
||||
|
||||
// Delete the .app.json from the filesystem
|
||||
// remove() closes the file first.
|
||||
if (!appFile.remove()) {
|
||||
qCDebug(commerce) << "Couldn't delete local .app.json file.";
|
||||
return false;
|
||||
qCWarning(commerce) << "Couldn't delete local .app.json file during app uninstall. Continuing anyway. App filename is:" << appHref.fileName();
|
||||
}
|
||||
|
||||
emit appUninstalled(itemHref);
|
||||
|
|
|
@ -134,7 +134,7 @@ public:
|
|||
/**jsdoc
|
||||
* Get the names of all the selection lists.
|
||||
* @function Selection.getListNames
|
||||
* @return {list[]} An array of names of all the selection lists.
|
||||
* @returns {list[]} An array of names of all the selection lists.
|
||||
*/
|
||||
Q_INVOKABLE QStringList getListNames() const;
|
||||
|
||||
|
@ -184,7 +184,7 @@ public:
|
|||
* Get the list of avatars, entities, and overlays stored in a selection list.
|
||||
* @function Selection.getList
|
||||
* @param {string} listName - The name of the selection list.
|
||||
* @return {Selection.SelectedItemsList} The content of a selection list. If the list name doesn't exist, the function
|
||||
* @returns {Selection.SelectedItemsList} The content of a selection list. If the list name doesn't exist, the function
|
||||
* returns an empty object with no properties.
|
||||
*/
|
||||
Q_INVOKABLE QVariantMap getSelectedItemsList(const QString& listName) const;
|
||||
|
@ -192,7 +192,7 @@ public:
|
|||
/**jsdoc
|
||||
* Get the names of the highlighted selection lists.
|
||||
* @function Selection.getHighlightedListNames
|
||||
* @return {string[]} An array of names of the selection list currently highlight enabled.
|
||||
* @returns {string[]} An array of names of the selection list currently highlight enabled.
|
||||
*/
|
||||
Q_INVOKABLE QStringList getHighlightedListNames() const;
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
static AvatarInputs* INSTANCE{ nullptr };
|
||||
|
||||
Setting::Handle<bool> showAudioToolsSetting { QStringList { "AvatarInputs", "showAudioTools" }, false };
|
||||
Setting::Handle<bool> showAudioToolsSetting { QStringList { "AvatarInputs", "showAudioTools" }, true };
|
||||
|
||||
AvatarInputs* AvatarInputs::getInstance() {
|
||||
if (!INSTANCE) {
|
||||
|
|
|
@ -47,45 +47,39 @@ public:
|
|||
* @property {number} sizeCached - Size in bytes of all cached resources. <em>Read-only.</em>
|
||||
*/
|
||||
|
||||
// Functions are copied over from ResourceCache (see ResourceCache.h for reason).
|
||||
// Functions are copied over from ResourceCache (see ResourceCache.h for reason).
|
||||
|
||||
/**jsdoc
|
||||
/**jsdoc
|
||||
* Get the list of all resource URLs.
|
||||
* @function AnimationCache.getResourceList
|
||||
* @return {string[]}
|
||||
* @returns {string[]}
|
||||
*/
|
||||
|
||||
/**jsdoc
|
||||
/**jsdoc
|
||||
* @function AnimationCache.dirty
|
||||
* @returns {Signal}
|
||||
*/
|
||||
|
||||
/**jsdoc
|
||||
/**jsdoc
|
||||
* @function AnimationCache.updateTotalSize
|
||||
* @param {number} deltaSize
|
||||
*/
|
||||
|
||||
/**jsdoc
|
||||
/**jsdoc
|
||||
* Prefetches a resource.
|
||||
* @function AnimationCache.prefetch
|
||||
* @param {string} url
|
||||
* @param {object} extra
|
||||
* @returns {object}
|
||||
* @param {string} url - URL of the resource to prefetch.
|
||||
* @param {object} [extra=null]
|
||||
* @returns {Resource}
|
||||
*/
|
||||
|
||||
/**jsdoc
|
||||
/**jsdoc
|
||||
* Asynchronously loads a resource from the specified URL and returns it.
|
||||
* @function AnimationCache.getResource
|
||||
* @param {string} url - URL of the resource to load.
|
||||
* @param {string} [fallback=""] - Fallback URL if load of the desired URL fails.
|
||||
* @param {} [extra=null]
|
||||
* @return {Resource}
|
||||
*/
|
||||
|
||||
/**jsdoc
|
||||
* Prefetches a resource.
|
||||
* @function AnimationCache.prefetch
|
||||
* @param {string} url - URL of the resource to prefetch.
|
||||
* @return {Resource}
|
||||
* @returns {Resource}
|
||||
*/
|
||||
|
||||
|
||||
|
|
|
@ -10,9 +10,9 @@
|
|||
// Inline functions to implement audio dynamics processing
|
||||
//
|
||||
|
||||
#include <stddef.h>
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#ifndef MAX
|
||||
#define MAX(a,b) ((a) > (b) ? (a) : (b))
|
||||
|
@ -21,7 +21,15 @@
|
|||
#define MIN(a,b) ((a) < (b) ? (a) : (b))
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#if defined(_MSC_VER)
|
||||
#define FORCEINLINE __forceinline
|
||||
#elif defined(__GNUC__)
|
||||
#define FORCEINLINE inline __attribute__((always_inline))
|
||||
#else
|
||||
#define FORCEINLINE inline
|
||||
#endif
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#include <intrin.h>
|
||||
#define MUL64(a,b) __emul((a), (b))
|
||||
#else
|
||||
|
@ -42,14 +50,14 @@
|
|||
|
||||
#include <xmmintrin.h>
|
||||
// convert float to int using round-to-nearest
|
||||
static inline int32_t floatToInt(float x) {
|
||||
FORCEINLINE static int32_t floatToInt(float x) {
|
||||
return _mm_cvt_ss2si(_mm_load_ss(&x));
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
// convert float to int using round-to-nearest
|
||||
static inline int32_t floatToInt(float x) {
|
||||
FORCEINLINE static int32_t floatToInt(float x) {
|
||||
x += (x < 0.0f ? -0.5f : 0.5f); // round
|
||||
return (int32_t)x;
|
||||
}
|
||||
|
@ -60,12 +68,12 @@ static const double FIXQ31 = 2147483648.0; // convert float to Q31
|
|||
static const double DB_TO_LOG2 = 0.16609640474436813; // convert dB to log2
|
||||
|
||||
// convert dB to amplitude
|
||||
static inline double dBToGain(double dB) {
|
||||
FORCEINLINE static double dBToGain(double dB) {
|
||||
return pow(10.0, dB / 20.0);
|
||||
}
|
||||
|
||||
// convert milliseconds to first-order time constant
|
||||
static inline int32_t msToTc(double ms, double sampleRate) {
|
||||
FORCEINLINE static int32_t msToTc(double ms, double sampleRate) {
|
||||
double tc = exp(-1000.0 / (ms * sampleRate));
|
||||
return (int32_t)(FIXQ31 * tc); // Q31
|
||||
}
|
||||
|
@ -144,16 +152,16 @@ static const int IEEE754_EXPN_BIAS = 127;
|
|||
// x < 2^(31-LOG2_HEADROOM) returns 0x7fffffff
|
||||
// x > 2^LOG2_HEADROOM undefined
|
||||
//
|
||||
static inline int32_t peaklog2(float* input) {
|
||||
FORCEINLINE static int32_t peaklog2(float* input) {
|
||||
|
||||
// float as integer bits
|
||||
int32_t u = *(int32_t*)input;
|
||||
uint32_t u = *(uint32_t*)input;
|
||||
|
||||
// absolute value
|
||||
int32_t peak = u & IEEE754_FABS_MASK;
|
||||
uint32_t peak = u & IEEE754_FABS_MASK;
|
||||
|
||||
// split into e and x - 1.0
|
||||
int32_t e = IEEE754_EXPN_BIAS - (peak >> IEEE754_MANT_BITS) + LOG2_HEADROOM;
|
||||
int e = IEEE754_EXPN_BIAS - (peak >> IEEE754_MANT_BITS) + LOG2_HEADROOM;
|
||||
int32_t x = (peak << IEEE754_EXPN_BITS) & 0x7fffffff;
|
||||
|
||||
// saturate
|
||||
|
@ -180,19 +188,19 @@ static inline int32_t peaklog2(float* input) {
|
|||
// x < 2^(31-LOG2_HEADROOM) returns 0x7fffffff
|
||||
// x > 2^LOG2_HEADROOM undefined
|
||||
//
|
||||
static inline int32_t peaklog2(float* input0, float* input1) {
|
||||
FORCEINLINE static int32_t peaklog2(float* input0, float* input1) {
|
||||
|
||||
// float as integer bits
|
||||
int32_t u0 = *(int32_t*)input0;
|
||||
int32_t u1 = *(int32_t*)input1;
|
||||
uint32_t u0 = *(uint32_t*)input0;
|
||||
uint32_t u1 = *(uint32_t*)input1;
|
||||
|
||||
// max absolute value
|
||||
u0 &= IEEE754_FABS_MASK;
|
||||
u1 &= IEEE754_FABS_MASK;
|
||||
int32_t peak = MAX(u0, u1);
|
||||
uint32_t peak = MAX(u0, u1);
|
||||
|
||||
// split into e and x - 1.0
|
||||
int32_t e = IEEE754_EXPN_BIAS - (peak >> IEEE754_MANT_BITS) + LOG2_HEADROOM;
|
||||
int e = IEEE754_EXPN_BIAS - (peak >> IEEE754_MANT_BITS) + LOG2_HEADROOM;
|
||||
int32_t x = (peak << IEEE754_EXPN_BITS) & 0x7fffffff;
|
||||
|
||||
// saturate
|
||||
|
@ -219,23 +227,23 @@ static inline int32_t peaklog2(float* input0, float* input1) {
|
|||
// x < 2^(31-LOG2_HEADROOM) returns 0x7fffffff
|
||||
// x > 2^LOG2_HEADROOM undefined
|
||||
//
|
||||
static inline int32_t peaklog2(float* input0, float* input1, float* input2, float* input3) {
|
||||
FORCEINLINE static int32_t peaklog2(float* input0, float* input1, float* input2, float* input3) {
|
||||
|
||||
// float as integer bits
|
||||
int32_t u0 = *(int32_t*)input0;
|
||||
int32_t u1 = *(int32_t*)input1;
|
||||
int32_t u2 = *(int32_t*)input2;
|
||||
int32_t u3 = *(int32_t*)input3;
|
||||
uint32_t u0 = *(uint32_t*)input0;
|
||||
uint32_t u1 = *(uint32_t*)input1;
|
||||
uint32_t u2 = *(uint32_t*)input2;
|
||||
uint32_t u3 = *(uint32_t*)input3;
|
||||
|
||||
// max absolute value
|
||||
u0 &= IEEE754_FABS_MASK;
|
||||
u1 &= IEEE754_FABS_MASK;
|
||||
u2 &= IEEE754_FABS_MASK;
|
||||
u3 &= IEEE754_FABS_MASK;
|
||||
int32_t peak = MAX(MAX(u0, u1), MAX(u2, u3));
|
||||
uint32_t peak = MAX(MAX(u0, u1), MAX(u2, u3));
|
||||
|
||||
// split into e and x - 1.0
|
||||
int32_t e = IEEE754_EXPN_BIAS - (peak >> IEEE754_MANT_BITS) + LOG2_HEADROOM;
|
||||
int e = IEEE754_EXPN_BIAS - (peak >> IEEE754_MANT_BITS) + LOG2_HEADROOM;
|
||||
int32_t x = (peak << IEEE754_EXPN_BITS) & 0x7fffffff;
|
||||
|
||||
// saturate
|
||||
|
@ -261,7 +269,7 @@ static inline int32_t peaklog2(float* input0, float* input1, float* input2, floa
|
|||
// Count Leading Zeros
|
||||
// Emulates the CLZ (ARM) and LZCNT (x86) instruction
|
||||
//
|
||||
static inline int CLZ(uint32_t u) {
|
||||
FORCEINLINE static int CLZ(uint32_t u) {
|
||||
|
||||
if (u == 0) {
|
||||
return 32;
|
||||
|
@ -294,7 +302,7 @@ static inline int CLZ(uint32_t u) {
|
|||
// Compute -log2(x) for x=[0,1] in Q31, result in Q26
|
||||
// x <= 0 returns 0x7fffffff
|
||||
//
|
||||
static inline int32_t fixlog2(int32_t x) {
|
||||
FORCEINLINE static int32_t fixlog2(int32_t x) {
|
||||
|
||||
if (x <= 0) {
|
||||
return 0x7fffffff;
|
||||
|
@ -303,8 +311,7 @@ static inline int32_t fixlog2(int32_t x) {
|
|||
// split into e and x - 1.0
|
||||
uint32_t u = (uint32_t)x;
|
||||
int e = CLZ(u);
|
||||
u <<= e; // normalize to [0x80000000, 0xffffffff]
|
||||
x = u & 0x7fffffff; // x - 1.0
|
||||
x = (u << e) & 0x7fffffff;
|
||||
|
||||
int k = x >> (31 - LOG2_TABBITS);
|
||||
|
||||
|
@ -324,7 +331,7 @@ static inline int32_t fixlog2(int32_t x) {
|
|||
// Compute exp2(-x) for x=[0,32] in Q26, result in Q31
|
||||
// x <= 0 returns 0x7fffffff
|
||||
//
|
||||
static inline int32_t fixexp2(int32_t x) {
|
||||
FORCEINLINE static int32_t fixexp2(int32_t x) {
|
||||
|
||||
if (x <= 0) {
|
||||
return 0x7fffffff;
|
||||
|
@ -350,12 +357,12 @@ static inline int32_t fixexp2(int32_t x) {
|
|||
}
|
||||
|
||||
// fast TPDF dither in [-1.0f, 1.0f]
|
||||
static inline float dither() {
|
||||
FORCEINLINE static float dither() {
|
||||
static uint32_t rz = 0;
|
||||
rz = rz * 69069 + 1;
|
||||
int32_t r0 = rz & 0xffff;
|
||||
int32_t r1 = rz >> 16;
|
||||
return (int32_t)(r0 - r1) * (1/65536.0f);
|
||||
return (r0 - r1) * (1/65536.0f);
|
||||
}
|
||||
|
||||
//
|
||||
|
|
|
@ -13,18 +13,11 @@
|
|||
#include "AudioReverb.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
|
||||
#include <intrin.h>
|
||||
inline static int MULHI(int a, int b) {
|
||||
long long c = __emul(a, b);
|
||||
return ((int*)&c)[1];
|
||||
}
|
||||
|
||||
#define MULHI(a,b) ((int32_t)(__emul(a, b) >> 32))
|
||||
#else
|
||||
|
||||
#define MULHI(a,b) (int)(((long long)(a) * (b)) >> 32)
|
||||
|
||||
#endif // _MSC_VER
|
||||
#define MULHI(a,b) ((int32_t)(((int64_t)(a) * (int64_t)(b)) >> 32))
|
||||
#endif
|
||||
|
||||
#ifndef MAX
|
||||
#define MAX(a,b) (((a) > (b)) ? (a) : (b))
|
||||
|
@ -1954,7 +1947,7 @@ static inline float dither() {
|
|||
rz = rz * 69069 + 1;
|
||||
int32_t r0 = rz & 0xffff;
|
||||
int32_t r1 = rz >> 16;
|
||||
return (int32_t)(r0 - r1) * (1/65536.0f);
|
||||
return (r0 - r1) * (1/65536.0f);
|
||||
}
|
||||
|
||||
// convert float to int16_t with dither, interleave stereo
|
||||
|
|
|
@ -1200,7 +1200,7 @@ static inline float dither() {
|
|||
rz = rz * 69069 + 1;
|
||||
int32_t r0 = rz & 0xffff;
|
||||
int32_t r1 = rz >> 16;
|
||||
return (int32_t)(r0 - r1) * (1/65536.0f);
|
||||
return (r0 - r1) * (1/65536.0f);
|
||||
}
|
||||
|
||||
// convert float to int16_t with dither, interleave stereo
|
||||
|
|
|
@ -41,12 +41,12 @@ public:
|
|||
*/
|
||||
|
||||
|
||||
// Functions are copied over from ResourceCache (see ResourceCache.h for reason).
|
||||
// Functions are copied over from ResourceCache (see ResourceCache.h for reason).
|
||||
|
||||
/**jsdoc
|
||||
* Get the list of all resource URLs.
|
||||
* @function SoundCache.getResourceList
|
||||
* @return {string[]}
|
||||
* @returns {string[]}
|
||||
*/
|
||||
|
||||
/**jsdoc
|
||||
|
@ -60,10 +60,11 @@ public:
|
|||
*/
|
||||
|
||||
/**jsdoc
|
||||
* Prefetches a resource.
|
||||
* @function SoundCache.prefetch
|
||||
* @param {string} url
|
||||
* @param {object} extra
|
||||
* @returns {object}
|
||||
* @param {string} url - URL of the resource to prefetch.
|
||||
* @param {object} [extra=null]
|
||||
* @returns {Resource}
|
||||
*/
|
||||
|
||||
/**jsdoc
|
||||
|
@ -72,14 +73,7 @@ public:
|
|||
* @param {string} url - URL of the resource to load.
|
||||
* @param {string} [fallback=""] - Fallback URL if load of the desired URL fails.
|
||||
* @param {} [extra=null]
|
||||
* @return {Resource}
|
||||
*/
|
||||
|
||||
/**jsdoc
|
||||
* Prefetches a resource.
|
||||
* @function SoundCache.prefetch
|
||||
* @param {string} url - URL of the resource to prefetch.
|
||||
* @return {Resource}
|
||||
* @returns {Resource}
|
||||
*/
|
||||
|
||||
|
||||
|
|
|
@ -277,8 +277,6 @@ namespace AvatarDataPacket {
|
|||
|
||||
const float MAX_AUDIO_LOUDNESS = 1000.0f; // close enough for mouth animation
|
||||
|
||||
const int AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS = 1000;
|
||||
|
||||
// See also static AvatarData::defaultFullAvatarModelUrl().
|
||||
const QString DEFAULT_FULL_AVATAR_MODEL_NAME = QString("Default");
|
||||
|
||||
|
|
|
@ -70,13 +70,6 @@ void FBXBaker::bakeSourceCopy() {
|
|||
return;
|
||||
}
|
||||
|
||||
// export the FBX with re-written texture references
|
||||
exportScene();
|
||||
|
||||
if (shouldStop()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check if we're already done with textures (in case we had none to re-write)
|
||||
checkIfTexturesFinished();
|
||||
}
|
||||
|
@ -352,27 +345,3 @@ void FBXBaker::rewriteAndBakeSceneTextures() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FBXBaker::exportScene() {
|
||||
// save the relative path to this FBX inside our passed output folder
|
||||
auto fileName = _modelURL.fileName();
|
||||
auto baseName = fileName.left(fileName.lastIndexOf('.'));
|
||||
auto bakedFilename = baseName + BAKED_FBX_EXTENSION;
|
||||
|
||||
_bakedModelFilePath = _bakedOutputDir + "/" + bakedFilename;
|
||||
|
||||
auto fbxData = FBXWriter::encodeFBX(_rootNode);
|
||||
|
||||
QFile bakedFile(_bakedModelFilePath);
|
||||
|
||||
if (!bakedFile.open(QIODevice::WriteOnly)) {
|
||||
handleError("Error opening " + _bakedModelFilePath + " for writing");
|
||||
return;
|
||||
}
|
||||
|
||||
bakedFile.write(fbxData);
|
||||
|
||||
_outputFiles.push_back(_bakedModelFilePath);
|
||||
|
||||
qCDebug(model_baking) << "Exported" << _modelURL << "with re-written paths to" << _bakedModelFilePath;
|
||||
}
|
||||
|
|
|
@ -26,8 +26,6 @@
|
|||
|
||||
#include <FBX.h>
|
||||
|
||||
static const QString BAKED_FBX_EXTENSION = ".baked.fbx";
|
||||
|
||||
using TextureBakerThreadGetter = std::function<QThread*()>;
|
||||
|
||||
class FBXBaker : public ModelBaker {
|
||||
|
@ -51,11 +49,11 @@ private:
|
|||
void loadSourceFBX();
|
||||
|
||||
void importScene();
|
||||
void embedTextureMetaData();
|
||||
void rewriteAndBakeSceneModels();
|
||||
void rewriteAndBakeSceneTextures();
|
||||
void exportScene();
|
||||
|
||||
FBXNode _rootNode;
|
||||
FBXGeometry* _geometry;
|
||||
QHash<QString, int> _textureNameMatchCount;
|
||||
QHash<QUrl, QString> _remappedTexturePaths;
|
||||
|
|
|
@ -246,9 +246,9 @@ bool ModelBaker::compressMesh(FBXMesh& mesh, bool hasDeformers, FBXNode& dracoMe
|
|||
|
||||
QString ModelBaker::compressTexture(QString modelTextureFileName, image::TextureUsage::Type textureType) {
|
||||
|
||||
QFileInfo modelTextureFileInfo{ modelTextureFileName.replace("\\", "/") };
|
||||
QFileInfo modelTextureFileInfo { modelTextureFileName.replace("\\", "/") };
|
||||
|
||||
if (modelTextureFileInfo.suffix() == BAKED_TEXTURE_EXT.mid(1)) {
|
||||
if (modelTextureFileInfo.suffix().toLower() == BAKED_TEXTURE_KTX_EXT.mid(1)) {
|
||||
// re-baking a model that already references baked textures
|
||||
// this is an error - return from here
|
||||
handleError("Cannot re-bake a file that already references compressed textures");
|
||||
|
@ -273,31 +273,31 @@ QString ModelBaker::compressTexture(QString modelTextureFileName, image::Texture
|
|||
}
|
||||
auto urlToTexture = getTextureURL(modelTextureFileInfo, modelTextureFileName, !textureContent.isNull());
|
||||
|
||||
QString bakedTextureFileName;
|
||||
QString baseTextureFileName;
|
||||
if (_remappedTexturePaths.contains(urlToTexture)) {
|
||||
bakedTextureFileName = _remappedTexturePaths[urlToTexture];
|
||||
baseTextureFileName = _remappedTexturePaths[urlToTexture];
|
||||
} else {
|
||||
// construct the new baked texture file name and file path
|
||||
// ensuring that the baked texture will have a unique name
|
||||
// even if there was another texture with the same name at a different path
|
||||
bakedTextureFileName = createBakedTextureFileName(modelTextureFileInfo);
|
||||
_remappedTexturePaths[urlToTexture] = bakedTextureFileName;
|
||||
baseTextureFileName = createBaseTextureFileName(modelTextureFileInfo);
|
||||
_remappedTexturePaths[urlToTexture] = baseTextureFileName;
|
||||
}
|
||||
|
||||
qCDebug(model_baking).noquote() << "Re-mapping" << modelTextureFileName
|
||||
<< "to" << bakedTextureFileName;
|
||||
<< "to" << baseTextureFileName;
|
||||
|
||||
QString bakedTextureFilePath{
|
||||
_bakedOutputDir + "/" + bakedTextureFileName
|
||||
QString bakedTextureFilePath {
|
||||
_bakedOutputDir + "/" + baseTextureFileName + BAKED_META_TEXTURE_SUFFIX
|
||||
};
|
||||
|
||||
textureChild = bakedTextureFileName;
|
||||
textureChild = baseTextureFileName + BAKED_META_TEXTURE_SUFFIX;
|
||||
|
||||
if (!_bakingTextures.contains(urlToTexture)) {
|
||||
_outputFiles.push_back(bakedTextureFilePath);
|
||||
|
||||
// bake this texture asynchronously
|
||||
bakeTexture(urlToTexture, textureType, _bakedOutputDir, bakedTextureFileName, textureContent);
|
||||
bakeTexture(urlToTexture, textureType, _bakedOutputDir, baseTextureFileName, textureContent);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -309,7 +309,7 @@ void ModelBaker::bakeTexture(const QUrl& textureURL, image::TextureUsage::Type t
|
|||
|
||||
// start a bake for this texture and add it to our list to keep track of
|
||||
QSharedPointer<TextureBaker> bakingTexture{
|
||||
new TextureBaker(textureURL, textureType, outputDir, bakedFilename, textureContent),
|
||||
new TextureBaker(textureURL, textureType, outputDir, "../", bakedFilename, textureContent),
|
||||
&TextureBaker::deleteLater
|
||||
};
|
||||
|
||||
|
@ -484,30 +484,30 @@ void ModelBaker::checkIfTexturesFinished() {
|
|||
} else {
|
||||
qCDebug(model_baking) << "Finished baking, emitting finished" << _modelURL;
|
||||
|
||||
texturesFinished();
|
||||
|
||||
setIsFinished(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString ModelBaker::createBakedTextureFileName(const QFileInfo& textureFileInfo) {
|
||||
QString ModelBaker::createBaseTextureFileName(const QFileInfo& textureFileInfo) {
|
||||
// first make sure we have a unique base name for this texture
|
||||
// in case another texture referenced by this model has the same base name
|
||||
auto& nameMatches = _textureNameMatchCount[textureFileInfo.baseName()];
|
||||
|
||||
QString bakedTextureFileName{ textureFileInfo.completeBaseName() };
|
||||
QString baseTextureFileName{ textureFileInfo.completeBaseName() };
|
||||
|
||||
if (nameMatches > 0) {
|
||||
// there are already nameMatches texture with this name
|
||||
// append - and that number to our baked texture file name so that it is unique
|
||||
bakedTextureFileName += "-" + QString::number(nameMatches);
|
||||
baseTextureFileName += "-" + QString::number(nameMatches);
|
||||
}
|
||||
|
||||
bakedTextureFileName += BAKED_TEXTURE_EXT;
|
||||
|
||||
// increment the number of name matches
|
||||
++nameMatches;
|
||||
|
||||
return bakedTextureFileName;
|
||||
return baseTextureFileName;
|
||||
}
|
||||
|
||||
void ModelBaker::setWasAborted(bool wasAborted) {
|
||||
|
@ -519,3 +519,91 @@ void ModelBaker::setWasAborted(bool wasAborted) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ModelBaker::texturesFinished() {
|
||||
embedTextureMetaData();
|
||||
exportScene();
|
||||
}
|
||||
|
||||
void ModelBaker::embedTextureMetaData() {
|
||||
std::vector<FBXNode> embeddedTextureNodes;
|
||||
|
||||
for (FBXNode& rootChild : _rootNode.children) {
|
||||
if (rootChild.name == "Objects") {
|
||||
qlonglong maxId = 0;
|
||||
for (auto &child : rootChild.children) {
|
||||
if (child.properties.length() == 3) {
|
||||
maxId = std::max(maxId, child.properties[0].toLongLong());
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& object : rootChild.children) {
|
||||
if (object.name == "Texture") {
|
||||
QVariant relativeFilename;
|
||||
for (auto& child : object.children) {
|
||||
if (child.name == "RelativeFilename") {
|
||||
relativeFilename = child.properties[0];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (relativeFilename.isNull()
|
||||
|| !relativeFilename.toString().endsWith(BAKED_META_TEXTURE_SUFFIX)) {
|
||||
continue;
|
||||
}
|
||||
if (object.properties.length() < 2) {
|
||||
qWarning() << "Found texture with unexpected number of properties: " << object.name;
|
||||
continue;
|
||||
}
|
||||
|
||||
FBXNode videoNode;
|
||||
videoNode.name = "Video";
|
||||
videoNode.properties.append(++maxId);
|
||||
videoNode.properties.append(object.properties[1]);
|
||||
videoNode.properties.append("Clip");
|
||||
|
||||
QString bakedTextureFilePath {
|
||||
_bakedOutputDir + "/" + relativeFilename.toString()
|
||||
};
|
||||
|
||||
QFile textureFile { bakedTextureFilePath };
|
||||
if (!textureFile.open(QIODevice::ReadOnly)) {
|
||||
qWarning() << "Failed to open: " << bakedTextureFilePath;
|
||||
continue;
|
||||
}
|
||||
|
||||
videoNode.children.append({ "RelativeFilename", { relativeFilename }, { } });
|
||||
videoNode.children.append({ "Content", { textureFile.readAll() }, { } });
|
||||
|
||||
rootChild.children.append(videoNode);
|
||||
|
||||
textureFile.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ModelBaker::exportScene() {
|
||||
// save the relative path to this FBX inside our passed output folder
|
||||
auto fileName = _modelURL.fileName();
|
||||
auto baseName = fileName.left(fileName.lastIndexOf('.'));
|
||||
auto bakedFilename = baseName + BAKED_FBX_EXTENSION;
|
||||
|
||||
_bakedModelFilePath = _bakedOutputDir + "/" + bakedFilename;
|
||||
|
||||
auto fbxData = FBXWriter::encodeFBX(_rootNode);
|
||||
|
||||
QFile bakedFile(_bakedModelFilePath);
|
||||
|
||||
if (!bakedFile.open(QIODevice::WriteOnly)) {
|
||||
handleError("Error opening " + _bakedModelFilePath + " for writing");
|
||||
return;
|
||||
}
|
||||
|
||||
bakedFile.write(fbxData);
|
||||
|
||||
_outputFiles.push_back(_bakedModelFilePath);
|
||||
|
||||
qCDebug(model_baking) << "Exported" << _modelURL << "with re-written paths to" << _bakedModelFilePath;
|
||||
}
|
||||
|
|
|
@ -29,6 +29,8 @@
|
|||
using TextureBakerThreadGetter = std::function<QThread*()>;
|
||||
using GetMaterialIDCallback = std::function <int(int)>;
|
||||
|
||||
static const QString BAKED_FBX_EXTENSION = ".baked.fbx";
|
||||
|
||||
class ModelBaker : public Baker {
|
||||
Q_OBJECT
|
||||
|
||||
|
@ -49,7 +51,11 @@ public slots:
|
|||
|
||||
protected:
|
||||
void checkIfTexturesFinished();
|
||||
void texturesFinished();
|
||||
void embedTextureMetaData();
|
||||
void exportScene();
|
||||
|
||||
FBXNode _rootNode;
|
||||
QHash<QByteArray, QByteArray> _textureContentMap;
|
||||
QUrl _modelURL;
|
||||
QString _bakedOutputDir;
|
||||
|
@ -63,7 +69,7 @@ private slots:
|
|||
void handleAbortedTexture();
|
||||
|
||||
private:
|
||||
QString createBakedTextureFileName(const QFileInfo & textureFileInfo);
|
||||
QString createBaseTextureFileName(const QFileInfo & textureFileInfo);
|
||||
QUrl getTextureURL(const QFileInfo& textureFileInfo, QString relativeFileName, bool isEmbedded = false);
|
||||
void bakeTexture(const QUrl & textureURL, image::TextureUsage::Type textureType, const QDir & outputDir,
|
||||
const QString & bakedFilename, const QByteArray & textureContent);
|
||||
|
|
|
@ -147,31 +147,7 @@ void OBJBaker::bakeOBJ() {
|
|||
auto geometry = reader.readOBJ(objData, QVariantHash(), combineParts, _modelURL);
|
||||
|
||||
// Write OBJ Data as FBX tree nodes
|
||||
FBXNode rootNode;
|
||||
createFBXNodeTree(rootNode, *geometry);
|
||||
|
||||
// Serialize the resultant FBX tree
|
||||
auto encodedFBX = FBXWriter::encodeFBX(rootNode);
|
||||
|
||||
// Export as baked FBX
|
||||
auto fileName = _modelURL.fileName();
|
||||
auto baseName = fileName.left(fileName.lastIndexOf('.'));
|
||||
auto bakedFilename = baseName + ".baked.fbx";
|
||||
|
||||
_bakedModelFilePath = _bakedOutputDir + "/" + bakedFilename;
|
||||
|
||||
QFile bakedFile;
|
||||
bakedFile.setFileName(_bakedModelFilePath);
|
||||
if (!bakedFile.open(QIODevice::WriteOnly)) {
|
||||
handleError("Error opening " + _bakedModelFilePath + " for writing");
|
||||
return;
|
||||
}
|
||||
|
||||
bakedFile.write(encodedFBX);
|
||||
|
||||
// Export successful
|
||||
_outputFiles.push_back(_bakedModelFilePath);
|
||||
qCDebug(model_baking) << "Exported" << _modelURL << "to" << _bakedModelFilePath;
|
||||
createFBXNodeTree(_rootNode, *geometry);
|
||||
|
||||
checkIfTexturesFinished();
|
||||
}
|
||||
|
@ -203,15 +179,17 @@ void OBJBaker::createFBXNodeTree(FBXNode& rootNode, FBXGeometry& geometry) {
|
|||
globalSettingsNode.children = { properties70Node };
|
||||
|
||||
// Generating Object node
|
||||
_objectNode.name = OBJECTS_NODE_NAME;
|
||||
FBXNode objectNode;
|
||||
objectNode.name = OBJECTS_NODE_NAME;
|
||||
|
||||
// Generating Object node's child - Geometry node
|
||||
FBXNode geometryNode;
|
||||
geometryNode.name = GEOMETRY_NODE_NAME;
|
||||
NodeID geometryID;
|
||||
{
|
||||
_geometryID = nextNodeID();
|
||||
geometryID = nextNodeID();
|
||||
geometryNode.properties = {
|
||||
_geometryID,
|
||||
geometryID,
|
||||
GEOMETRY_NODE_NAME,
|
||||
MESH
|
||||
};
|
||||
|
@ -226,12 +204,13 @@ void OBJBaker::createFBXNodeTree(FBXNode& rootNode, FBXGeometry& geometry) {
|
|||
// Generating Object node's child - Model node
|
||||
FBXNode modelNode;
|
||||
modelNode.name = MODEL_NODE_NAME;
|
||||
NodeID modelID;
|
||||
{
|
||||
_modelID = nextNodeID();
|
||||
modelNode.properties = { _modelID, MODEL_NODE_NAME, MESH };
|
||||
modelID = nextNodeID();
|
||||
modelNode.properties = { modelID, MODEL_NODE_NAME, MESH };
|
||||
}
|
||||
|
||||
_objectNode.children = { geometryNode, modelNode };
|
||||
objectNode.children = { geometryNode, modelNode };
|
||||
|
||||
// Generating Objects node's child - Material node
|
||||
auto& meshParts = geometry.meshes[0].parts;
|
||||
|
@ -247,7 +226,7 @@ void OBJBaker::createFBXNodeTree(FBXNode& rootNode, FBXGeometry& geometry) {
|
|||
setMaterialNodeProperties(materialNode, meshPart.materialID, geometry);
|
||||
}
|
||||
|
||||
_objectNode.children.append(materialNode);
|
||||
objectNode.children.append(materialNode);
|
||||
}
|
||||
|
||||
// Generating Texture Node
|
||||
|
@ -257,13 +236,13 @@ void OBJBaker::createFBXNodeTree(FBXNode& rootNode, FBXGeometry& geometry) {
|
|||
QString material = meshParts[i].materialID;
|
||||
FBXMaterial currentMaterial = geometry.materials[material];
|
||||
if (!currentMaterial.albedoTexture.filename.isEmpty() || !currentMaterial.specularTexture.filename.isEmpty()) {
|
||||
_textureID = nextNodeID();
|
||||
_mapTextureMaterial.emplace_back(_textureID, i);
|
||||
auto textureID = nextNodeID();
|
||||
_mapTextureMaterial.emplace_back(textureID, i);
|
||||
|
||||
FBXNode textureNode;
|
||||
{
|
||||
textureNode.name = TEXTURE_NODE_NAME;
|
||||
textureNode.properties = { _textureID };
|
||||
textureNode.properties = { textureID, "texture" + QString::number(textureID) };
|
||||
}
|
||||
|
||||
// Texture node child - TextureName node
|
||||
|
@ -295,7 +274,7 @@ void OBJBaker::createFBXNodeTree(FBXNode& rootNode, FBXGeometry& geometry) {
|
|||
|
||||
textureNode.children = { textureNameNode, relativeFilenameNode };
|
||||
|
||||
_objectNode.children.append(textureNode);
|
||||
objectNode.children.append(textureNode);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -306,14 +285,14 @@ void OBJBaker::createFBXNodeTree(FBXNode& rootNode, FBXGeometry& geometry) {
|
|||
// connect Geometry to Model
|
||||
FBXNode cNode;
|
||||
cNode.name = C_NODE_NAME;
|
||||
cNode.properties = { CONNECTIONS_NODE_PROPERTY, _geometryID, _modelID };
|
||||
cNode.properties = { CONNECTIONS_NODE_PROPERTY, geometryID, modelID };
|
||||
connectionsNode.children = { cNode };
|
||||
|
||||
// connect all materials to model
|
||||
for (auto& materialID : _materialIDs) {
|
||||
FBXNode cNode;
|
||||
cNode.name = C_NODE_NAME;
|
||||
cNode.properties = { CONNECTIONS_NODE_PROPERTY, materialID, _modelID };
|
||||
cNode.properties = { CONNECTIONS_NODE_PROPERTY, materialID, modelID };
|
||||
connectionsNode.children.append(cNode);
|
||||
}
|
||||
|
||||
|
@ -341,7 +320,7 @@ void OBJBaker::createFBXNodeTree(FBXNode& rootNode, FBXGeometry& geometry) {
|
|||
}
|
||||
|
||||
// Make all generated nodes children of rootNode
|
||||
rootNode.children = { globalSettingsNode, _objectNode, connectionsNode };
|
||||
rootNode.children = { globalSettingsNode, objectNode, connectionsNode };
|
||||
}
|
||||
|
||||
// Set properties for material nodes
|
||||
|
|
|
@ -43,12 +43,9 @@ private:
|
|||
void setMaterialNodeProperties(FBXNode& materialNode, QString material, FBXGeometry& geometry);
|
||||
NodeID nextNodeID() { return _nodeID++; }
|
||||
|
||||
|
||||
NodeID _nodeID { 0 };
|
||||
NodeID _geometryID;
|
||||
NodeID _modelID;
|
||||
std::vector<NodeID> _materialIDs;
|
||||
NodeID _textureID;
|
||||
std::vector<std::pair<NodeID, int>> _mapTextureMaterial;
|
||||
FBXNode _objectNode;
|
||||
};
|
||||
#endif // hifi_OBJBaker_h
|
||||
|
|
|
@ -18,26 +18,30 @@
|
|||
#include <ktx/KTX.h>
|
||||
#include <NetworkAccessManager.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <TextureMeta.h>
|
||||
|
||||
#include "ModelBakingLoggingCategory.h"
|
||||
|
||||
#include "TextureBaker.h"
|
||||
|
||||
const QString BAKED_TEXTURE_EXT = ".ktx";
|
||||
const QString BAKED_TEXTURE_KTX_EXT = ".ktx";
|
||||
const QString BAKED_TEXTURE_BCN_SUFFIX = "_bcn.ktx";
|
||||
const QString BAKED_META_TEXTURE_SUFFIX = ".texmeta.json";
|
||||
|
||||
TextureBaker::TextureBaker(const QUrl& textureURL, image::TextureUsage::Type textureType,
|
||||
const QDir& outputDirectory, const QString& bakedFilename,
|
||||
const QByteArray& textureContent) :
|
||||
const QDir& outputDirectory, const QString& metaTexturePathPrefix,
|
||||
const QString& baseFilename, const QByteArray& textureContent) :
|
||||
_textureURL(textureURL),
|
||||
_originalTexture(textureContent),
|
||||
_textureType(textureType),
|
||||
_baseFilename(baseFilename),
|
||||
_outputDirectory(outputDirectory),
|
||||
_bakedTextureFileName(bakedFilename)
|
||||
_metaTexturePathPrefix(metaTexturePathPrefix)
|
||||
{
|
||||
if (bakedFilename.isEmpty()) {
|
||||
if (baseFilename.isEmpty()) {
|
||||
// figure out the baked texture filename
|
||||
auto originalFilename = textureURL.fileName();
|
||||
_bakedTextureFileName = originalFilename.left(originalFilename.lastIndexOf('.')) + BAKED_TEXTURE_EXT;
|
||||
_baseFilename = originalFilename.left(originalFilename.lastIndexOf('.'));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -118,6 +122,19 @@ void TextureBaker::processTexture() {
|
|||
auto hashData = QCryptographicHash::hash(_originalTexture, QCryptographicHash::Md5);
|
||||
std::string hash = hashData.toHex().toStdString();
|
||||
|
||||
TextureMeta meta;
|
||||
|
||||
{
|
||||
auto filePath = _outputDirectory.absoluteFilePath(_textureURL.fileName());
|
||||
QFile file { filePath };
|
||||
if (!file.open(QIODevice::WriteOnly) || file.write(_originalTexture) == -1) {
|
||||
handleError("Could not write original texture for " + _textureURL.toString());
|
||||
return;
|
||||
}
|
||||
_outputFiles.push_back(filePath);
|
||||
meta.original = _metaTexturePathPrefix +_textureURL.fileName();
|
||||
}
|
||||
|
||||
// IMPORTANT: _originalTexture is empty past this point
|
||||
auto processedTexture = image::processImage(std::move(_originalTexture), _textureURL.toString().toStdString(),
|
||||
ABSOLUTE_MAX_TEXTURE_NUM_PIXELS, _textureType, _abortProcessing);
|
||||
|
@ -140,17 +157,38 @@ void TextureBaker::processTexture() {
|
|||
return;
|
||||
}
|
||||
|
||||
const char* data = reinterpret_cast<const char*>(memKTX->_storage->data());
|
||||
const size_t length = memKTX->_storage->size();
|
||||
const char* name = khronos::gl::texture::toString(memKTX->_header.getGLInternaFormat());
|
||||
if (name == nullptr) {
|
||||
handleError("Could not determine internal format for compressed KTX: " + _textureURL.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
// attempt to write the baked texture to the destination file path
|
||||
auto filePath = _outputDirectory.absoluteFilePath(_bakedTextureFileName);
|
||||
QFile bakedTextureFile { filePath };
|
||||
{
|
||||
const char* data = reinterpret_cast<const char*>(memKTX->_storage->data());
|
||||
const size_t length = memKTX->_storage->size();
|
||||
|
||||
if (!bakedTextureFile.open(QIODevice::WriteOnly) || bakedTextureFile.write(data, length) == -1) {
|
||||
handleError("Could not write baked texture for " + _textureURL.toString());
|
||||
} else {
|
||||
auto fileName = _baseFilename + BAKED_TEXTURE_BCN_SUFFIX;
|
||||
auto filePath = _outputDirectory.absoluteFilePath(fileName);
|
||||
QFile bakedTextureFile { filePath };
|
||||
if (!bakedTextureFile.open(QIODevice::WriteOnly) || bakedTextureFile.write(data, length) == -1) {
|
||||
handleError("Could not write baked texture for " + _textureURL.toString());
|
||||
return;
|
||||
}
|
||||
_outputFiles.push_back(filePath);
|
||||
meta.availableTextureTypes[memKTX->_header.getGLInternaFormat()] = _metaTexturePathPrefix + fileName;
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
auto data = meta.serialize();
|
||||
_metaTextureFileName = _outputDirectory.absoluteFilePath(_baseFilename + BAKED_META_TEXTURE_SUFFIX);
|
||||
QFile file { _metaTextureFileName };
|
||||
if (!file.open(QIODevice::WriteOnly) || file.write(data) == -1) {
|
||||
handleError("Could not write meta texture for " + _textureURL.toString());
|
||||
} else {
|
||||
_outputFiles.push_back(_metaTextureFileName);
|
||||
}
|
||||
}
|
||||
|
||||
qCDebug(model_baking) << "Baked texture" << _textureURL;
|
||||
|
|
|
@ -21,22 +21,22 @@
|
|||
|
||||
#include "Baker.h"
|
||||
|
||||
extern const QString BAKED_TEXTURE_EXT;
|
||||
extern const QString BAKED_TEXTURE_KTX_EXT;
|
||||
extern const QString BAKED_META_TEXTURE_SUFFIX;
|
||||
|
||||
class TextureBaker : public Baker {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
TextureBaker(const QUrl& textureURL, image::TextureUsage::Type textureType,
|
||||
const QDir& outputDirectory, const QString& bakedFilename = QString(),
|
||||
const QByteArray& textureContent = QByteArray());
|
||||
const QDir& outputDirectory, const QString& metaTexturePathPrefix = "",
|
||||
const QString& baseFilename = QString(), const QByteArray& textureContent = QByteArray());
|
||||
|
||||
const QByteArray& getOriginalTexture() const { return _originalTexture; }
|
||||
|
||||
QUrl getTextureURL() const { return _textureURL; }
|
||||
|
||||
QString getDestinationFilePath() const { return _outputDirectory.absoluteFilePath(_bakedTextureFileName); }
|
||||
QString getBakedTextureFileName() const { return _bakedTextureFileName; }
|
||||
QString getMetaTextureFileName() const { return _metaTextureFileName; }
|
||||
|
||||
virtual void setWasAborted(bool wasAborted) override;
|
||||
|
||||
|
@ -58,8 +58,10 @@ private:
|
|||
QByteArray _originalTexture;
|
||||
image::TextureUsage::Type _textureType;
|
||||
|
||||
QString _baseFilename;
|
||||
QDir _outputDirectory;
|
||||
QString _bakedTextureFileName;
|
||||
QString _metaTextureFileName;
|
||||
QString _metaTexturePathPrefix;
|
||||
|
||||
std::atomic<bool> _abortProcessing { false };
|
||||
};
|
||||
|
|
|
@ -296,7 +296,7 @@ void EntityTreeRenderer::addPendingEntities(const render::ScenePointer& scene, r
|
|||
}
|
||||
}
|
||||
|
||||
void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene, const ViewFrustum& view, render::Transaction& transaction) {
|
||||
void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene, render::Transaction& transaction) {
|
||||
PROFILE_RANGE_EX(simulation_physics, "ChangeInScene", 0xffff00ff, (uint64_t)_changedEntities.size());
|
||||
PerformanceTimer pt("change");
|
||||
std::unordered_set<EntityItemID> changedEntities;
|
||||
|
@ -357,7 +357,9 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene
|
|||
|
||||
// prioritize and sort the renderables
|
||||
uint64_t sortStart = usecTimestampNow();
|
||||
PrioritySortUtil::PriorityQueue<SortableRenderer> sortedRenderables(view);
|
||||
|
||||
const auto& views = _viewState->getConicalViews();
|
||||
PrioritySortUtil::PriorityQueue<SortableRenderer> sortedRenderables(views);
|
||||
{
|
||||
PROFILE_RANGE_EX(simulation_physics, "SortRenderables", 0xffff00ff, (uint64_t)_renderablesToUpdate.size());
|
||||
std::unordered_map<EntityItemID, EntityRendererPointer>::iterator itr = _renderablesToUpdate.begin();
|
||||
|
@ -415,9 +417,8 @@ void EntityTreeRenderer::update(bool simulate) {
|
|||
if (scene) {
|
||||
render::Transaction transaction;
|
||||
addPendingEntities(scene, transaction);
|
||||
ViewFrustum view;
|
||||
_viewState->copyCurrentViewFrustum(view);
|
||||
updateChangedEntities(scene, view, transaction);
|
||||
|
||||
updateChangedEntities(scene, transaction);
|
||||
scene->enqueueTransaction(transaction);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -148,7 +148,7 @@ protected:
|
|||
|
||||
private:
|
||||
void addPendingEntities(const render::ScenePointer& scene, render::Transaction& transaction);
|
||||
void updateChangedEntities(const render::ScenePointer& scene, const ViewFrustum& view, render::Transaction& transaction);
|
||||
void updateChangedEntities(const render::ScenePointer& scene, render::Transaction& transaction);
|
||||
EntityRendererPointer renderableForEntity(const EntityItemPointer& entity) const { return renderableForEntityId(entity->getID()); }
|
||||
render::ItemID renderableIdForEntity(const EntityItemPointer& entity) const { return renderableIdForEntityId(entity->getID()); }
|
||||
|
||||
|
|
|
@ -23,8 +23,8 @@ using namespace render::entities;
|
|||
static uint8_t CUSTOM_PIPELINE_NUMBER = 0;
|
||||
static gpu::Stream::FormatPointer _vertexFormat;
|
||||
static std::weak_ptr<gpu::Pipeline> _texturedPipeline;
|
||||
// FIXME: This is interfering with the uniform buffers in DeferredLightingEffect.cpp, so use 11 to avoid collisions
|
||||
static int32_t PARTICLE_UNIFORM_SLOT { 11 };
|
||||
// FIXME: This is interfering with the uniform buffers in DeferredLightingEffect.cpp, so use 12 to avoid collisions
|
||||
static int32_t PARTICLE_UNIFORM_SLOT { 12 };
|
||||
|
||||
static ShapePipelinePointer shapePipelineFactory(const ShapePlumber& plumber, const ShapeKey& key, gpu::Batch& batch) {
|
||||
auto texturedPipeline = _texturedPipeline.lock();
|
||||
|
|
|
@ -34,8 +34,8 @@ using namespace render::entities;
|
|||
|
||||
static uint8_t CUSTOM_PIPELINE_NUMBER { 0 };
|
||||
static const int32_t PAINTSTROKE_TEXTURE_SLOT { 0 };
|
||||
// FIXME: This is interfering with the uniform buffers in DeferredLightingEffect.cpp, so use 11 to avoid collisions
|
||||
static const int32_t PAINTSTROKE_UNIFORM_SLOT { 11 };
|
||||
// FIXME: This is interfering with the uniform buffers in DeferredLightingEffect.cpp, so use 12 to avoid collisions
|
||||
static const int32_t PAINTSTROKE_UNIFORM_SLOT { 12 };
|
||||
static gpu::Stream::FormatPointer polylineFormat;
|
||||
static gpu::PipelinePointer polylinePipeline;
|
||||
#ifdef POLYLINE_ENTITY_USE_FADE_EFFECT
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
#include <OctreeUtils.h>
|
||||
|
||||
#include "EntityPriorityQueue.h"
|
||||
|
||||
DiffTraversal::Waypoint::Waypoint(EntityTreeElementPointer& element) : _nextIndex(0) {
|
||||
assert(element);
|
||||
|
@ -36,20 +37,9 @@ void DiffTraversal::Waypoint::getNextVisibleElementFirstTime(DiffTraversal::Visi
|
|||
while (_nextIndex < NUMBER_OF_CHILDREN) {
|
||||
EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex);
|
||||
++_nextIndex;
|
||||
if (nextElement) {
|
||||
if (!view.usesViewFrustum) {
|
||||
// No LOD truncation if we aren't using the view frustum
|
||||
next.element = nextElement;
|
||||
return;
|
||||
} else if (view.viewFrustum.cubeIntersectsKeyhole(nextElement->getAACube())) {
|
||||
// check for LOD truncation
|
||||
float distance = glm::distance(view.viewFrustum.getPosition(), nextElement->getAACube().calcCenter()) + MIN_VISIBLE_DISTANCE;
|
||||
float angularDiameter = nextElement->getAACube().getScale() / distance;
|
||||
if (angularDiameter > MIN_ELEMENT_ANGULAR_DIAMETER * view.lodScaleFactor) {
|
||||
next.element = nextElement;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (nextElement && view.shouldTraverseElement(*nextElement)) {
|
||||
next.element = nextElement;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -65,7 +55,6 @@ void DiffTraversal::Waypoint::getNextVisibleElementRepeat(
|
|||
EntityTreeElementPointer element = _weakElement.lock();
|
||||
if (element->getLastChangedContent() > lastTime) {
|
||||
next.element = element;
|
||||
next.intersection = ViewFrustum::INTERSECT;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -75,31 +64,17 @@ void DiffTraversal::Waypoint::getNextVisibleElementRepeat(
|
|||
while (_nextIndex < NUMBER_OF_CHILDREN) {
|
||||
EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex);
|
||||
++_nextIndex;
|
||||
if (nextElement && nextElement->getLastChanged() > lastTime) {
|
||||
if (!view.usesViewFrustum) {
|
||||
// No LOD truncation if we aren't using the view frustum
|
||||
next.element = nextElement;
|
||||
next.intersection = ViewFrustum::INSIDE;
|
||||
return;
|
||||
} else {
|
||||
// check for LOD truncation
|
||||
float distance = glm::distance(view.viewFrustum.getPosition(), nextElement->getAACube().calcCenter()) + MIN_VISIBLE_DISTANCE;
|
||||
float angularDiameter = nextElement->getAACube().getScale() / distance;
|
||||
if (angularDiameter > MIN_ELEMENT_ANGULAR_DIAMETER * view.lodScaleFactor) {
|
||||
ViewFrustum::intersection intersection = view.viewFrustum.calculateCubeKeyholeIntersection(nextElement->getAACube());
|
||||
if (intersection != ViewFrustum::OUTSIDE) {
|
||||
next.element = nextElement;
|
||||
next.intersection = intersection;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nextElement &&
|
||||
nextElement->getLastChanged() > lastTime &&
|
||||
view.shouldTraverseElement(*nextElement)) {
|
||||
|
||||
next.element = nextElement;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
next.element.reset();
|
||||
next.intersection = ViewFrustum::OUTSIDE;
|
||||
}
|
||||
|
||||
void DiffTraversal::Waypoint::getNextVisibleElementDifferential(DiffTraversal::VisibleElement& next,
|
||||
|
@ -109,7 +84,6 @@ void DiffTraversal::Waypoint::getNextVisibleElementDifferential(DiffTraversal::V
|
|||
++_nextIndex;
|
||||
EntityTreeElementPointer element = _weakElement.lock();
|
||||
next.element = element;
|
||||
next.intersection = ViewFrustum::INTERSECT;
|
||||
return;
|
||||
} else if (_nextIndex < NUMBER_OF_CHILDREN) {
|
||||
EntityTreeElementPointer element = _weakElement.lock();
|
||||
|
@ -117,24 +91,101 @@ void DiffTraversal::Waypoint::getNextVisibleElementDifferential(DiffTraversal::V
|
|||
while (_nextIndex < NUMBER_OF_CHILDREN) {
|
||||
EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex);
|
||||
++_nextIndex;
|
||||
if (nextElement) {
|
||||
AACube cube = nextElement->getAACube();
|
||||
// check for LOD truncation
|
||||
float distance = glm::distance(view.viewFrustum.getPosition(), cube.calcCenter()) + MIN_VISIBLE_DISTANCE;
|
||||
float angularDiameter = cube.getScale() / distance;
|
||||
if (angularDiameter > MIN_ELEMENT_ANGULAR_DIAMETER * view.lodScaleFactor) {
|
||||
if (view.viewFrustum.calculateCubeKeyholeIntersection(cube) != ViewFrustum::OUTSIDE) {
|
||||
next.element = nextElement;
|
||||
next.intersection = ViewFrustum::OUTSIDE;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (nextElement && view.shouldTraverseElement(*nextElement)) {
|
||||
next.element = nextElement;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
next.element.reset();
|
||||
next.intersection = ViewFrustum::OUTSIDE;
|
||||
}
|
||||
|
||||
bool DiffTraversal::View::usesViewFrustums() const {
|
||||
return !viewFrustums.empty();
|
||||
}
|
||||
|
||||
bool DiffTraversal::View::isVerySimilar(const View& view) const {
|
||||
auto size = view.viewFrustums.size();
|
||||
|
||||
if (view.lodScaleFactor != lodScaleFactor ||
|
||||
viewFrustums.size() != size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
if (!viewFrustums[i].isVerySimilar(view.viewFrustums[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
float DiffTraversal::View::computePriority(const EntityItemPointer& entity) const {
|
||||
if (!entity) {
|
||||
return PrioritizedEntity::DO_NOT_SEND;
|
||||
}
|
||||
|
||||
if (!usesViewFrustums()) {
|
||||
return PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY;
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
auto cube = entity->getQueryAACube(success);
|
||||
if (!success) {
|
||||
return PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY;
|
||||
}
|
||||
|
||||
auto center = cube.calcCenter(); // center of bounding sphere
|
||||
auto radius = 0.5f * SQRT_THREE * cube.getScale(); // radius of bounding sphere
|
||||
|
||||
auto priority = PrioritizedEntity::DO_NOT_SEND;
|
||||
|
||||
for (const auto& frustum : viewFrustums) {
|
||||
auto position = center - frustum.getPosition(); // position of bounding sphere in view-frame
|
||||
float distance = glm::length(position); // distance to center of bounding sphere
|
||||
|
||||
// Check the size of the entity, it's possible that a "too small to see" entity is included in a
|
||||
// larger octree cell because of its position (for example if it crosses the boundary of a cell it
|
||||
// pops to the next higher cell. So we want to check to see that the entity is large enough to be seen
|
||||
// before we consider including it.
|
||||
float angularSize = frustum.getAngularSize(distance, radius);
|
||||
if (angularSize > lodScaleFactor * MIN_ENTITY_ANGULAR_DIAMETER &&
|
||||
frustum.intersects(position, distance, radius)) {
|
||||
|
||||
// use the angular size as priority
|
||||
// we compute the max priority for all frustums
|
||||
priority = std::max(priority, angularSize);
|
||||
}
|
||||
}
|
||||
|
||||
return priority;
|
||||
}
|
||||
|
||||
bool DiffTraversal::View::shouldTraverseElement(const EntityTreeElement& element) const {
|
||||
if (!usesViewFrustums()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto& cube = element.getAACube();
|
||||
|
||||
auto center = cube.calcCenter(); // center of bounding sphere
|
||||
auto radius = 0.5f * SQRT_THREE * cube.getScale(); // radius of bounding sphere
|
||||
|
||||
|
||||
return any_of(begin(viewFrustums), end(viewFrustums), [&](const ConicalViewFrustum& frustum) {
|
||||
auto position = center - frustum.getPosition(); // position of bounding sphere in view-frame
|
||||
float distance = glm::length(position); // distance to center of bounding sphere
|
||||
|
||||
// Check the size of the entity, it's possible that a "too small to see" entity is included in a
|
||||
// larger octree cell because of its position (for example if it crosses the boundary of a cell it
|
||||
// pops to the next higher cell. So we want to check to see that the entity is large enough to be seen
|
||||
// before we consider including it.
|
||||
float angularSize = frustum.getAngularSize(distance, radius);
|
||||
|
||||
return angularSize > lodScaleFactor * MIN_ELEMENT_ANGULAR_DIAMETER &&
|
||||
frustum.intersects(position, distance, radius);
|
||||
});
|
||||
}
|
||||
|
||||
DiffTraversal::DiffTraversal() {
|
||||
|
@ -142,8 +193,7 @@ DiffTraversal::DiffTraversal() {
|
|||
_path.reserve(MIN_PATH_DEPTH);
|
||||
}
|
||||
|
||||
DiffTraversal::Type DiffTraversal::prepareNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root,
|
||||
int32_t lodLevelOffset, bool usesViewFrustum) {
|
||||
DiffTraversal::Type DiffTraversal::prepareNewTraversal(const DiffTraversal::View& view, EntityTreeElementPointer root) {
|
||||
assert(root);
|
||||
// there are three types of traversal:
|
||||
//
|
||||
|
@ -159,29 +209,25 @@ DiffTraversal::Type DiffTraversal::prepareNewTraversal(const ViewFrustum& viewFr
|
|||
//
|
||||
// external code should update the _scanElementCallback after calling prepareNewTraversal
|
||||
//
|
||||
_currentView.usesViewFrustum = usesViewFrustum;
|
||||
float lodScaleFactor = powf(2.0f, lodLevelOffset);
|
||||
|
||||
Type type;
|
||||
// If usesViewFrustum changes, treat it as a First traversal
|
||||
if (_completedView.startTime == 0 || _currentView.usesViewFrustum != _completedView.usesViewFrustum) {
|
||||
if (_completedView.startTime == 0 || _currentView.usesViewFrustums() != _completedView.usesViewFrustums()) {
|
||||
type = Type::First;
|
||||
_currentView.viewFrustum = viewFrustum;
|
||||
_currentView.lodScaleFactor = lodScaleFactor;
|
||||
_currentView.viewFrustums = view.viewFrustums;
|
||||
_currentView.lodScaleFactor = view.lodScaleFactor;
|
||||
_getNextVisibleElementCallback = [this](DiffTraversal::VisibleElement& next) {
|
||||
_path.back().getNextVisibleElementFirstTime(next, _currentView);
|
||||
};
|
||||
} else if (!_currentView.usesViewFrustum ||
|
||||
(_completedView.viewFrustum.isVerySimilar(viewFrustum) &&
|
||||
lodScaleFactor == _completedView.lodScaleFactor)) {
|
||||
} else if (!_currentView.usesViewFrustums() || _completedView.isVerySimilar(view)) {
|
||||
type = Type::Repeat;
|
||||
_getNextVisibleElementCallback = [this](DiffTraversal::VisibleElement& next) {
|
||||
_path.back().getNextVisibleElementRepeat(next, _completedView, _completedView.startTime);
|
||||
};
|
||||
} else {
|
||||
type = Type::Differential;
|
||||
_currentView.viewFrustum = viewFrustum;
|
||||
_currentView.lodScaleFactor = lodScaleFactor;
|
||||
_currentView.viewFrustums = view.viewFrustums;
|
||||
_currentView.lodScaleFactor = view.lodScaleFactor;
|
||||
_getNextVisibleElementCallback = [this](DiffTraversal::VisibleElement& next) {
|
||||
_path.back().getNextVisibleElementDifferential(next, _currentView, _completedView);
|
||||
};
|
||||
|
@ -200,7 +246,6 @@ DiffTraversal::Type DiffTraversal::prepareNewTraversal(const ViewFrustum& viewFr
|
|||
void DiffTraversal::getNextVisibleElement(DiffTraversal::VisibleElement& next) {
|
||||
if (_path.empty()) {
|
||||
next.element.reset();
|
||||
next.intersection = ViewFrustum::OUTSIDE;
|
||||
return;
|
||||
}
|
||||
_getNextVisibleElementCallback(next);
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
#ifndef hifi_DiffTraversal_h
|
||||
#define hifi_DiffTraversal_h
|
||||
|
||||
#include <ViewFrustum.h>
|
||||
#include <shared/ConicalViewFrustum.h>
|
||||
|
||||
#include "EntityTreeElement.h"
|
||||
|
||||
|
@ -24,16 +24,20 @@ public:
|
|||
class VisibleElement {
|
||||
public:
|
||||
EntityTreeElementPointer element;
|
||||
ViewFrustum::intersection intersection { ViewFrustum::OUTSIDE };
|
||||
};
|
||||
|
||||
// View is a struct with a ViewFrustum and LOD parameters
|
||||
class View {
|
||||
public:
|
||||
ViewFrustum viewFrustum;
|
||||
bool usesViewFrustums() const;
|
||||
bool isVerySimilar(const View& view) const;
|
||||
|
||||
bool shouldTraverseElement(const EntityTreeElement& element) const;
|
||||
float computePriority(const EntityItemPointer& entity) const;
|
||||
|
||||
ConicalViewFrustums viewFrustums;
|
||||
uint64_t startTime { 0 };
|
||||
float lodScaleFactor { 1.0f };
|
||||
bool usesViewFrustum { true };
|
||||
};
|
||||
|
||||
// Waypoint is an bookmark in a "path" of waypoints during a traversal.
|
||||
|
@ -57,15 +61,9 @@ public:
|
|||
|
||||
DiffTraversal();
|
||||
|
||||
Type prepareNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset,
|
||||
bool usesViewFrustum);
|
||||
Type prepareNewTraversal(const DiffTraversal::View& view, EntityTreeElementPointer root);
|
||||
|
||||
const ViewFrustum& getCurrentView() const { return _currentView.viewFrustum; }
|
||||
const ViewFrustum& getCompletedView() const { return _completedView.viewFrustum; }
|
||||
|
||||
bool doesCurrentUseViewFrustum() const { return _currentView.usesViewFrustum; }
|
||||
float getCurrentLODScaleFactor() const { return _currentView.lodScaleFactor; }
|
||||
float getCompletedLODScaleFactor() const { return _completedView.lodScaleFactor; }
|
||||
const View& getCurrentView() const { return _currentView; }
|
||||
|
||||
uint64_t getStartOfCompletedTraversal() const { return _completedView.startTime; }
|
||||
bool finished() const { return _path.empty(); }
|
||||
|
|
92
libraries/entities/src/EntityPriorityQueue.h
Normal file
92
libraries/entities/src/EntityPriorityQueue.h
Normal file
|
@ -0,0 +1,92 @@
|
|||
//
|
||||
// EntityPriorityQueue.h
|
||||
// assignment-client/src/entities
|
||||
//
|
||||
// Created by Andrew Meadows 2017.08.08
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_EntityPriorityQueue_h
|
||||
#define hifi_EntityPriorityQueue_h
|
||||
|
||||
#include <queue>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "EntityItem.h"
|
||||
|
||||
// PrioritizedEntity is a placeholder in a sorted queue.
|
||||
class PrioritizedEntity {
|
||||
public:
|
||||
static constexpr float DO_NOT_SEND { -1.0e6f };
|
||||
static constexpr float FORCE_REMOVE { -1.0e5f };
|
||||
static constexpr float WHEN_IN_DOUBT_PRIORITY { 1.0f };
|
||||
|
||||
PrioritizedEntity(EntityItemPointer entity, float priority, bool forceRemove = false) : _weakEntity(entity), _rawEntityPointer(entity.get()), _priority(priority), _forceRemove(forceRemove) {}
|
||||
EntityItemPointer getEntity() const { return _weakEntity.lock(); }
|
||||
EntityItem* getRawEntityPointer() const { return _rawEntityPointer; }
|
||||
float getPriority() const { return _priority; }
|
||||
bool shouldForceRemove() const { return _forceRemove; }
|
||||
|
||||
class Compare {
|
||||
public:
|
||||
bool operator() (const PrioritizedEntity& A, const PrioritizedEntity& B) { return A._priority < B._priority; }
|
||||
};
|
||||
friend class Compare;
|
||||
|
||||
private:
|
||||
EntityItemWeakPointer _weakEntity;
|
||||
EntityItem* _rawEntityPointer;
|
||||
float _priority;
|
||||
bool _forceRemove;
|
||||
};
|
||||
|
||||
class EntityPriorityQueue {
|
||||
public:
|
||||
inline bool empty() const {
|
||||
assert(_queue.empty() == _entities.empty());
|
||||
return _queue.empty();
|
||||
}
|
||||
|
||||
inline const PrioritizedEntity& top() const {
|
||||
assert(!_queue.empty());
|
||||
return _queue.top();
|
||||
}
|
||||
|
||||
inline bool contains(const EntityItem* entity) const {
|
||||
return _entities.find(entity) != std::end(_entities);
|
||||
}
|
||||
|
||||
inline void emplace(const EntityItemPointer& entity, float priority, bool forceRemove = false) {
|
||||
assert(entity && !contains(entity.get()));
|
||||
_queue.emplace(entity, priority, forceRemove);
|
||||
_entities.insert(entity.get());
|
||||
assert(_queue.size() == _entities.size());
|
||||
}
|
||||
|
||||
inline void pop() {
|
||||
assert(!empty());
|
||||
_entities.erase(_queue.top().getRawEntityPointer());
|
||||
_queue.pop();
|
||||
assert(_queue.size() == _entities.size());
|
||||
}
|
||||
|
||||
inline void swap(EntityPriorityQueue& other) {
|
||||
std::swap(_queue, other._queue);
|
||||
std::swap(_entities, other._entities);
|
||||
}
|
||||
|
||||
private:
|
||||
using PriorityQueue = std::priority_queue<PrioritizedEntity,
|
||||
std::vector<PrioritizedEntity>,
|
||||
PrioritizedEntity::Compare>;
|
||||
|
||||
PriorityQueue _queue;
|
||||
// Keep dictionary of all the entities in the queue for fast contain checks.
|
||||
std::unordered_set<const EntityItem*> _entities;
|
||||
|
||||
};
|
||||
|
||||
#endif // hifi_EntityPriorityQueue_h
|
|
@ -27,9 +27,6 @@ using EntityTreePointer = std::shared_ptr<EntityTree>;
|
|||
#include "MovingEntitiesOperator.h"
|
||||
|
||||
class EntityEditFilters;
|
||||
class Model;
|
||||
using ModelPointer = std::shared_ptr<Model>;
|
||||
using ModelWeakPointer = std::weak_ptr<Model>;
|
||||
|
||||
class EntitySimulation;
|
||||
|
||||
|
|
|
@ -17,15 +17,18 @@
|
|||
#include <QtCore/QProcessEnvironment>
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QThread>
|
||||
#include <QtCore/QThreadStorage>
|
||||
#include <QtCore/QPointer>
|
||||
#include <QtGui/QOffscreenSurface>
|
||||
#include <QtGui/QOpenGLContext>
|
||||
#include <QtGui/QOpenGLDebugLogger>
|
||||
|
||||
#include <DependencyManager.h>
|
||||
|
||||
#include "Context.h"
|
||||
#include "GLHelpers.h"
|
||||
#include "GLLogging.h"
|
||||
|
||||
|
||||
OffscreenGLCanvas::OffscreenGLCanvas() :
|
||||
_context(new QOpenGLContext),
|
||||
_offscreenSurface(new QOffscreenSurface)
|
||||
|
@ -33,6 +36,8 @@ OffscreenGLCanvas::OffscreenGLCanvas() :
|
|||
}
|
||||
|
||||
OffscreenGLCanvas::~OffscreenGLCanvas() {
|
||||
clearThreadContext();
|
||||
|
||||
// A context with logging enabled needs to be current when it's destroyed
|
||||
_context->makeCurrent(_offscreenSurface);
|
||||
delete _context;
|
||||
|
@ -117,25 +122,51 @@ QObject* OffscreenGLCanvas::getContextObject() {
|
|||
}
|
||||
|
||||
void OffscreenGLCanvas::moveToThreadWithContext(QThread* thread) {
|
||||
clearThreadContext();
|
||||
moveToThread(thread);
|
||||
_context->moveToThread(thread);
|
||||
}
|
||||
|
||||
static const char* THREAD_CONTEXT_PROPERTY = "offscreenGlCanvas";
|
||||
struct ThreadContextStorage : public Dependency {
|
||||
QThreadStorage<QPointer<OffscreenGLCanvas>> threadContext;
|
||||
};
|
||||
|
||||
void OffscreenGLCanvas::setThreadContext() {
|
||||
QThread::currentThread()->setProperty(THREAD_CONTEXT_PROPERTY, QVariant::fromValue<QObject*>(this));
|
||||
if (!DependencyManager::isSet<ThreadContextStorage>()) {
|
||||
DependencyManager::set<ThreadContextStorage>();
|
||||
}
|
||||
auto threadContextStorage = DependencyManager::get<ThreadContextStorage>();
|
||||
QPointer<OffscreenGLCanvas> p(this);
|
||||
threadContextStorage->threadContext.setLocalData(p);
|
||||
}
|
||||
|
||||
void OffscreenGLCanvas::clearThreadContext() {
|
||||
if (!DependencyManager::isSet<ThreadContextStorage>()) {
|
||||
return;
|
||||
}
|
||||
auto threadContextStorage = DependencyManager::get<ThreadContextStorage>();
|
||||
if (!threadContextStorage->threadContext.hasLocalData()) {
|
||||
return;
|
||||
}
|
||||
auto& threadContext = threadContextStorage->threadContext.localData();
|
||||
if (this != threadContext.operator OffscreenGLCanvas *()) {
|
||||
return;
|
||||
}
|
||||
threadContextStorage->threadContext.setLocalData(nullptr);
|
||||
}
|
||||
|
||||
bool OffscreenGLCanvas::restoreThreadContext() {
|
||||
// Restore the rendering context for this thread
|
||||
auto threadCanvasVariant = QThread::currentThread()->property(THREAD_CONTEXT_PROPERTY);
|
||||
if (!threadCanvasVariant.isValid()) {
|
||||
if (!DependencyManager::isSet<ThreadContextStorage>()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto threadCanvasObject = qvariant_cast<QObject*>(threadCanvasVariant);
|
||||
auto threadCanvas = static_cast<OffscreenGLCanvas*>(threadCanvasObject);
|
||||
auto threadContextStorage = DependencyManager::get<ThreadContextStorage>();
|
||||
if (!threadContextStorage->threadContext.hasLocalData()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto threadCanvas = threadContextStorage->threadContext.localData();
|
||||
if (!threadCanvas) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -39,6 +39,8 @@ private slots:
|
|||
void onMessageLogged(const QOpenGLDebugMessage &debugMessage);
|
||||
|
||||
protected:
|
||||
void clearThreadContext();
|
||||
|
||||
std::once_flag _reportOnce;
|
||||
QOpenGLContext* _context{ nullptr };
|
||||
QOffscreenSurface* _offscreenSurface{ nullptr };
|
||||
|
|
|
@ -91,7 +91,7 @@ public:
|
|||
|
||||
// this is the maximum per shader stage on the low end apple
|
||||
// TODO make it platform dependant at init time
|
||||
static const int MAX_NUM_UNIFORM_BUFFERS = 12;
|
||||
static const int MAX_NUM_UNIFORM_BUFFERS = 14;
|
||||
size_t getMaxNumUniformBuffers() const { return MAX_NUM_UNIFORM_BUFFERS; }
|
||||
|
||||
// this is the maximum per shader stage on the low end apple
|
||||
|
|
|
@ -52,6 +52,8 @@ public:
|
|||
static const std::string GL41_VERSION;
|
||||
const std::string& getVersion() const override { return GL41_VERSION; }
|
||||
|
||||
bool supportedTextureFormat(const gpu::Element& format) override;
|
||||
|
||||
class GL41Texture : public GLTexture {
|
||||
using Parent = GLTexture;
|
||||
friend class GL41Backend;
|
||||
|
@ -173,8 +175,6 @@ protected:
|
|||
void makeProgramBindings(ShaderObject& shaderObject) override;
|
||||
int makeResourceBufferSlots(GLuint glprogram, const Shader::BindingSet& slotBindings,Shader::SlotSet& resourceBuffers) override;
|
||||
|
||||
static bool supportedTextureFormat(const gpu::Element& format);
|
||||
|
||||
};
|
||||
|
||||
} }
|
||||
|
|
|
@ -54,6 +54,8 @@ public:
|
|||
static const std::string GL45_VERSION;
|
||||
const std::string& getVersion() const override { return GL45_VERSION; }
|
||||
|
||||
bool supportedTextureFormat(const gpu::Element& format) override;
|
||||
|
||||
class GL45Texture : public GLTexture {
|
||||
using Parent = GLTexture;
|
||||
friend class GL45Backend;
|
||||
|
|
|
@ -32,6 +32,24 @@ using namespace gpu::gl45;
|
|||
#define FORCE_STRICT_TEXTURE 0
|
||||
#define ENABLE_SPARSE_TEXTURE 0
|
||||
|
||||
bool GL45Backend::supportedTextureFormat(const gpu::Element& format) {
|
||||
switch (format.getSemantic()) {
|
||||
case gpu::Semantic::COMPRESSED_ETC2_RGB:
|
||||
case gpu::Semantic::COMPRESSED_ETC2_SRGB:
|
||||
case gpu::Semantic::COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA:
|
||||
case gpu::Semantic::COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA:
|
||||
case gpu::Semantic::COMPRESSED_ETC2_RGBA:
|
||||
case gpu::Semantic::COMPRESSED_ETC2_SRGBA:
|
||||
case gpu::Semantic::COMPRESSED_EAC_RED:
|
||||
case gpu::Semantic::COMPRESSED_EAC_RED_SIGNED:
|
||||
case gpu::Semantic::COMPRESSED_EAC_XY:
|
||||
case gpu::Semantic::COMPRESSED_EAC_XY_SIGNED:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
GLTexture* GL45Backend::syncGPUObject(const TexturePointer& texturePointer) {
|
||||
if (!texturePointer) {
|
||||
return nullptr;
|
||||
|
|
|
@ -32,7 +32,6 @@ public:
|
|||
static const GLint RESOURCE_TRANSFER_EXTRA_TEX_UNIT { 33 };
|
||||
static const GLint RESOURCE_BUFFER_TEXBUF_TEX_UNIT { 34 };
|
||||
static const GLint RESOURCE_BUFFER_SLOT0_TEX_UNIT { 35 };
|
||||
static bool supportedTextureFormat(const gpu::Element& format);
|
||||
explicit GLESBackend(bool syncCache) : Parent(syncCache) {}
|
||||
GLESBackend() : Parent() {}
|
||||
virtual ~GLESBackend() {
|
||||
|
@ -40,6 +39,8 @@ public:
|
|||
// which is pure virtual from GLBackend's dtor.
|
||||
resetStages();
|
||||
}
|
||||
|
||||
bool supportedTextureFormat(const gpu::Element& format) override;
|
||||
|
||||
static const std::string GLES_VERSION;
|
||||
const std::string& getVersion() const override { return GLES_VERSION; }
|
||||
|
|
|
@ -34,7 +34,7 @@ ProfileRangeBatch::~ProfileRangeBatch() {
|
|||
using namespace gpu;
|
||||
|
||||
// FIXME make these backend / pipeline dependent.
|
||||
static const int MAX_NUM_UNIFORM_BUFFERS = 12;
|
||||
static const int MAX_NUM_UNIFORM_BUFFERS = 14;
|
||||
static const int MAX_NUM_RESOURCE_BUFFERS = 16;
|
||||
static const int MAX_NUM_RESOURCE_TEXTURES = 16;
|
||||
|
||||
|
|
|
@ -64,6 +64,8 @@ public:
|
|||
virtual void recycle() const = 0;
|
||||
virtual void downloadFramebuffer(const FramebufferPointer& srcFramebuffer, const Vec4i& region, QImage& destImage) = 0;
|
||||
|
||||
virtual bool supportedTextureFormat(const gpu::Element& format) = 0;
|
||||
|
||||
// Shared header between C++ and GLSL
|
||||
#include "TransformCamera_shared.slh"
|
||||
|
||||
|
|
|
@ -37,6 +37,10 @@ namespace ktx {
|
|||
using KeyValues = std::list<KeyValue>;
|
||||
}
|
||||
|
||||
namespace khronos { namespace gl { namespace texture {
|
||||
enum class InternalFormat: uint32_t;
|
||||
}}}
|
||||
|
||||
namespace gpu {
|
||||
|
||||
|
||||
|
@ -565,6 +569,7 @@ public:
|
|||
|
||||
static bool evalKTXFormat(const Element& mipFormat, const Element& texelFormat, ktx::Header& header);
|
||||
static bool evalTextureFormat(const ktx::Header& header, Element& mipFormat, Element& texelFormat);
|
||||
static bool getCompressedFormat(khronos::gl::texture::InternalFormat format, Element& elFormat);
|
||||
|
||||
protected:
|
||||
const TextureUsageType _usageType;
|
||||
|
|
|
@ -619,6 +619,47 @@ bool Texture::evalKTXFormat(const Element& mipFormat, const Element& texelFormat
|
|||
return true;
|
||||
}
|
||||
|
||||
bool Texture::getCompressedFormat(ktx::GLInternalFormat format, Element& elFormat) {
|
||||
if (format == ktx::GLInternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT) {
|
||||
elFormat = Format::COLOR_COMPRESSED_BCX_SRGB;
|
||||
} else if (format == ktx::GLInternalFormat::COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT) {
|
||||
elFormat = Format::COLOR_COMPRESSED_BCX_SRGBA_MASK;
|
||||
} else if (format == ktx::GLInternalFormat::COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT) {
|
||||
elFormat = Format::COLOR_COMPRESSED_BCX_SRGBA;
|
||||
} else if (format == ktx::GLInternalFormat::COMPRESSED_RED_RGTC1) {
|
||||
elFormat = Format::COLOR_COMPRESSED_BCX_RED;
|
||||
} else if (format == ktx::GLInternalFormat::COMPRESSED_RG_RGTC2) {
|
||||
elFormat = Format::COLOR_COMPRESSED_BCX_XY;
|
||||
} else if (format == ktx::GLInternalFormat::COMPRESSED_SRGB_ALPHA_BPTC_UNORM) {
|
||||
elFormat = Format::COLOR_COMPRESSED_BCX_SRGBA_HIGH;
|
||||
} else if (format == ktx::GLInternalFormat::COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT) {
|
||||
elFormat = Format::COLOR_COMPRESSED_BCX_HDR_RGB;
|
||||
} else if (format == ktx::GLInternalFormat::COMPRESSED_RGB8_ETC2) {
|
||||
elFormat = Format::COLOR_COMPRESSED_ETC2_RGB;
|
||||
} else if (format == ktx::GLInternalFormat::COMPRESSED_SRGB8_ETC2) {
|
||||
elFormat = Format::COLOR_COMPRESSED_ETC2_SRGB;
|
||||
} else if (format == ktx::GLInternalFormat::COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2) {
|
||||
elFormat = Format::COLOR_COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA;
|
||||
} else if (format == ktx::GLInternalFormat::COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2) {
|
||||
elFormat = Format::COLOR_COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA;
|
||||
} else if (format == ktx::GLInternalFormat::COMPRESSED_RGBA8_ETC2_EAC) {
|
||||
elFormat = Format::COLOR_COMPRESSED_ETC2_RGBA;
|
||||
} else if (format == ktx::GLInternalFormat::COMPRESSED_SRGB8_ALPHA8_ETC2_EAC) {
|
||||
elFormat = Format::COLOR_COMPRESSED_ETC2_SRGBA;
|
||||
} else if (format == ktx::GLInternalFormat::COMPRESSED_R11_EAC) {
|
||||
elFormat = Format::COLOR_COMPRESSED_EAC_RED;
|
||||
} else if (format == ktx::GLInternalFormat::COMPRESSED_SIGNED_R11_EAC) {
|
||||
elFormat = Format::COLOR_COMPRESSED_EAC_RED_SIGNED;
|
||||
} else if (format == ktx::GLInternalFormat::COMPRESSED_RG11_EAC) {
|
||||
elFormat = Format::COLOR_COMPRESSED_EAC_XY;
|
||||
} else if (format == ktx::GLInternalFormat::COMPRESSED_SIGNED_RG11_EAC) {
|
||||
elFormat = Format::COLOR_COMPRESSED_EAC_XY_SIGNED;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Texture::evalTextureFormat(const ktx::Header& header, Element& mipFormat, Element& texelFormat) {
|
||||
if (header.getGLFormat() == ktx::GLFormat::BGRA && header.getGLType() == ktx::GLType::UNSIGNED_BYTE && header.getTypeSize() == 1) {
|
||||
if (header.getGLInternaFormat() == ktx::GLInternalFormat::RGBA8) {
|
||||
|
@ -661,41 +702,7 @@ bool Texture::evalTextureFormat(const ktx::Header& header, Element& mipFormat, E
|
|||
mipFormat = Format::COLOR_RGB9E5;
|
||||
texelFormat = Format::COLOR_RGB9E5;
|
||||
} else if (header.isCompressed()) {
|
||||
if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT) {
|
||||
texelFormat = Format::COLOR_COMPRESSED_BCX_SRGB;
|
||||
} else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT) {
|
||||
texelFormat = Format::COLOR_COMPRESSED_BCX_SRGBA_MASK;
|
||||
} else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT) {
|
||||
texelFormat = Format::COLOR_COMPRESSED_BCX_SRGBA;
|
||||
} else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_RED_RGTC1) {
|
||||
texelFormat = Format::COLOR_COMPRESSED_BCX_RED;
|
||||
} else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_RG_RGTC2) {
|
||||
texelFormat = Format::COLOR_COMPRESSED_BCX_XY;
|
||||
} else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SRGB_ALPHA_BPTC_UNORM) {
|
||||
texelFormat = Format::COLOR_COMPRESSED_BCX_SRGBA_HIGH;
|
||||
} else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT) {
|
||||
texelFormat = Format::COLOR_COMPRESSED_BCX_HDR_RGB;
|
||||
} else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_RGB8_ETC2) {
|
||||
texelFormat = Format::COLOR_COMPRESSED_ETC2_RGB;
|
||||
} else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SRGB8_ETC2) {
|
||||
texelFormat = Format::COLOR_COMPRESSED_ETC2_SRGB;
|
||||
} else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2) {
|
||||
texelFormat = Format::COLOR_COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA;
|
||||
} else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2) {
|
||||
texelFormat = Format::COLOR_COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA;
|
||||
} else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_RGBA8_ETC2_EAC) {
|
||||
texelFormat = Format::COLOR_COMPRESSED_ETC2_RGBA;
|
||||
} else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SRGB8_ALPHA8_ETC2_EAC) {
|
||||
texelFormat = Format::COLOR_COMPRESSED_ETC2_SRGBA;
|
||||
} else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_R11_EAC) {
|
||||
texelFormat = Format::COLOR_COMPRESSED_EAC_RED;
|
||||
} else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SIGNED_R11_EAC) {
|
||||
texelFormat = Format::COLOR_COMPRESSED_EAC_RED_SIGNED;
|
||||
} else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_RG11_EAC) {
|
||||
texelFormat = Format::COLOR_COMPRESSED_EAC_XY;
|
||||
} else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SIGNED_RG11_EAC) {
|
||||
texelFormat = Format::COLOR_COMPRESSED_EAC_XY_SIGNED;
|
||||
} else {
|
||||
if (!getCompressedFormat(header.getGLInternaFormat(), texelFormat)) {
|
||||
return false;
|
||||
}
|
||||
mipFormat = texelFormat;
|
||||
|
|
|
@ -42,7 +42,7 @@ public slots:
|
|||
*
|
||||
* @function Graphics.getModel
|
||||
* @param {UUID} entityID - The objectID of the model whose meshes are to be retrieved.
|
||||
* @return {Graphics.Model} the resulting Model object
|
||||
* @returns {Graphics.Model} the resulting Model object
|
||||
*/
|
||||
scriptable::ScriptableModelPointer getModel(QUuid uuid);
|
||||
|
||||
|
@ -57,7 +57,7 @@ public slots:
|
|||
*
|
||||
* @function Graphics.newMesh
|
||||
* @param {Graphics.IFSData} ifsMeshData Index-Faced Set (IFS) arrays used to create the new mesh.
|
||||
* @return {Graphics.Mesh} the resulting Mesh / Mesh Part object
|
||||
* @returns {Graphics.Mesh} the resulting Mesh / Mesh Part object
|
||||
*/
|
||||
/**jsdoc
|
||||
* @typedef {object} Graphics.IFSData
|
||||
|
|
64
libraries/ktx/src/TextureMeta.cpp
Normal file
64
libraries/ktx/src/TextureMeta.cpp
Normal file
|
@ -0,0 +1,64 @@
|
|||
//
|
||||
// TextureMeta.cpp
|
||||
// libraries/shared/src
|
||||
//
|
||||
// Created by Ryan Huffman on 04/10/18.
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "TextureMeta.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
const QString TEXTURE_META_EXTENSION = ".texmeta.json";
|
||||
|
||||
bool TextureMeta::deserialize(const QByteArray& data, TextureMeta* meta) {
|
||||
QJsonParseError error;
|
||||
auto doc = QJsonDocument::fromJson(data, &error);
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
qDebug() << "Failed to parse TextureMeta:" << error.errorString();
|
||||
return false;
|
||||
}
|
||||
if (!doc.isObject()) {
|
||||
qDebug() << "Unable to process TextureMeta: top-level value is not an Object";
|
||||
return false;
|
||||
}
|
||||
|
||||
auto root = doc.object();
|
||||
if (root.contains("original")) {
|
||||
meta->original = root["original"].toString();
|
||||
}
|
||||
if (root.contains("compressed")) {
|
||||
auto compressed = root["compressed"].toObject();
|
||||
for (auto it = compressed.constBegin(); it != compressed.constEnd(); it++) {
|
||||
khronos::gl::texture::InternalFormat format;
|
||||
auto formatName = it.key().toLatin1();
|
||||
if (khronos::gl::texture::fromString(formatName.constData(), &format)) {
|
||||
meta->availableTextureTypes[format] = it.value().toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QByteArray TextureMeta::serialize() {
|
||||
QJsonDocument doc;
|
||||
QJsonObject root;
|
||||
QJsonObject compressed;
|
||||
|
||||
for (auto kv : availableTextureTypes) {
|
||||
const char* name = khronos::gl::texture::toString(kv.first);
|
||||
compressed[name] = kv.second.toString();
|
||||
}
|
||||
root["original"] = original.toString();
|
||||
root["compressed"] = compressed;
|
||||
doc.setObject(root);
|
||||
|
||||
return doc.toJson();
|
||||
}
|
42
libraries/ktx/src/TextureMeta.h
Normal file
42
libraries/ktx/src/TextureMeta.h
Normal file
|
@ -0,0 +1,42 @@
|
|||
//
|
||||
// TextureMeta.h
|
||||
// libraries/shared/src
|
||||
//
|
||||
// Created by Ryan Huffman on 04/10/18.
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_TextureMeta_h
|
||||
#define hifi_TextureMeta_h
|
||||
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <QUrl>
|
||||
|
||||
#include "khronos/KHR.h"
|
||||
|
||||
extern const QString TEXTURE_META_EXTENSION;
|
||||
|
||||
namespace std {
|
||||
template<> struct hash<khronos::gl::texture::InternalFormat> {
|
||||
using enum_type = std::underlying_type<khronos::gl::texture::InternalFormat>::type;
|
||||
typedef std::size_t result_type;
|
||||
result_type operator()(khronos::gl::texture::InternalFormat const& v) const noexcept {
|
||||
return std::hash<enum_type>()(static_cast<enum_type>(v));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
struct TextureMeta {
|
||||
static bool deserialize(const QByteArray& data, TextureMeta* meta);
|
||||
QByteArray serialize();
|
||||
|
||||
QUrl original;
|
||||
std::unordered_map<khronos::gl::texture::InternalFormat, QUrl> availableTextureTypes;
|
||||
};
|
||||
|
||||
|
||||
#endif // hifi_TextureMeta_h
|
|
@ -10,6 +10,8 @@
|
|||
#ifndef khronos_khr_hpp
|
||||
#define khronos_khr_hpp
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
namespace khronos {
|
||||
|
||||
namespace gl {
|
||||
|
@ -209,6 +211,63 @@ namespace khronos {
|
|||
COMPRESSED_SIGNED_RG11_EAC = 0x9273,
|
||||
};
|
||||
|
||||
static std::unordered_map<std::string, InternalFormat> nameToFormat {
|
||||
{ "COMPRESSED_RED", InternalFormat::COMPRESSED_RED },
|
||||
{ "COMPRESSED_RG", InternalFormat::COMPRESSED_RG },
|
||||
{ "COMPRESSED_RGB", InternalFormat::COMPRESSED_RGB },
|
||||
{ "COMPRESSED_RGBA", InternalFormat::COMPRESSED_RGBA },
|
||||
|
||||
{ "COMPRESSED_SRGB", InternalFormat::COMPRESSED_SRGB },
|
||||
{ "COMPRESSED_SRGB_ALPHA", InternalFormat::COMPRESSED_SRGB_ALPHA },
|
||||
|
||||
{ "COMPRESSED_ETC1_RGB8_OES", InternalFormat::COMPRESSED_ETC1_RGB8_OES },
|
||||
|
||||
{ "COMPRESSED_SRGB_S3TC_DXT1_EXT", InternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT },
|
||||
{ "COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT", InternalFormat::COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT },
|
||||
{ "COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT", InternalFormat::COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT },
|
||||
{ "COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT", InternalFormat::COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT },
|
||||
|
||||
{ "COMPRESSED_RED_RGTC1", InternalFormat::COMPRESSED_RED_RGTC1 },
|
||||
{ "COMPRESSED_SIGNED_RED_RGTC1", InternalFormat::COMPRESSED_SIGNED_RED_RGTC1 },
|
||||
{ "COMPRESSED_RG_RGTC2", InternalFormat::COMPRESSED_RG_RGTC2 },
|
||||
{ "COMPRESSED_SIGNED_RG_RGTC2", InternalFormat::COMPRESSED_SIGNED_RG_RGTC2 },
|
||||
|
||||
{ "COMPRESSED_RGBA_BPTC_UNORM", InternalFormat::COMPRESSED_RGBA_BPTC_UNORM },
|
||||
{ "COMPRESSED_SRGB_ALPHA_BPTC_UNORM", InternalFormat::COMPRESSED_SRGB_ALPHA_BPTC_UNORM },
|
||||
{ "COMPRESSED_RGB_BPTC_SIGNED_FLOAT", InternalFormat::COMPRESSED_RGB_BPTC_SIGNED_FLOAT },
|
||||
{ "COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT", InternalFormat::COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT },
|
||||
|
||||
{ "COMPRESSED_RGB8_ETC2", InternalFormat::COMPRESSED_RGB8_ETC2 },
|
||||
{ "COMPRESSED_SRGB8_ETC2", InternalFormat::COMPRESSED_SRGB8_ETC2 },
|
||||
{ "COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2", InternalFormat::COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 },
|
||||
{ "COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2", InternalFormat::COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2 },
|
||||
{ "COMPRESSED_RGBA8_ETC2_EAC", InternalFormat::COMPRESSED_RGBA8_ETC2_EAC },
|
||||
{ "COMPRESSED_SRGB8_ALPHA8_ETC2_EAC", InternalFormat::COMPRESSED_SRGB8_ALPHA8_ETC2_EAC },
|
||||
|
||||
{ "COMPRESSED_R11_EAC", InternalFormat::COMPRESSED_R11_EAC },
|
||||
{ "COMPRESSED_SIGNED_R11_EAC", InternalFormat::COMPRESSED_SIGNED_R11_EAC },
|
||||
{ "COMPRESSED_RG11_EAC", InternalFormat::COMPRESSED_RG11_EAC },
|
||||
{ "COMPRESSED_SIGNED_RG11_EAC", InternalFormat::COMPRESSED_SIGNED_RG11_EAC }
|
||||
};
|
||||
|
||||
inline const char* toString(InternalFormat format) {
|
||||
for (auto& pair : nameToFormat) {
|
||||
if (pair.second == format) {
|
||||
return pair.first.data();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
inline bool fromString(const char* name, InternalFormat* format) {
|
||||
auto it = nameToFormat.find(name);
|
||||
if (it == nameToFormat.end()) {
|
||||
return false;
|
||||
}
|
||||
*format = it->second;
|
||||
return true;
|
||||
}
|
||||
|
||||
inline uint8_t evalUncompressedBlockBitSize(InternalFormat format) {
|
||||
switch (format) {
|
||||
case InternalFormat::R8:
|
||||
|
|
|
@ -63,9 +63,10 @@ void GeometryMappingResource::downloadFinished(const QByteArray& data) {
|
|||
PROFILE_ASYNC_BEGIN(resource_parse_geometry, "GeometryMappingResource::downloadFinished", _url.toString(),
|
||||
{ { "url", _url.toString() } });
|
||||
|
||||
auto mapping = FSTReader::readMapping(data);
|
||||
// store parsed contents of FST file
|
||||
_mapping = FSTReader::readMapping(data);
|
||||
|
||||
QString filename = mapping.value("filename").toString();
|
||||
QString filename = _mapping.value("filename").toString();
|
||||
|
||||
if (filename.isNull()) {
|
||||
qCDebug(modelnetworking) << "Mapping file" << _url << "has no \"filename\" field";
|
||||
|
@ -73,7 +74,7 @@ void GeometryMappingResource::downloadFinished(const QByteArray& data) {
|
|||
} else {
|
||||
QUrl url = _url.resolved(filename);
|
||||
|
||||
QString texdir = mapping.value(TEXDIR_FIELD).toString();
|
||||
QString texdir = _mapping.value(TEXDIR_FIELD).toString();
|
||||
if (!texdir.isNull()) {
|
||||
if (!texdir.endsWith('/')) {
|
||||
texdir += '/';
|
||||
|
@ -83,15 +84,16 @@ void GeometryMappingResource::downloadFinished(const QByteArray& data) {
|
|||
_textureBaseUrl = url.resolved(QUrl("."));
|
||||
}
|
||||
|
||||
auto scripts = FSTReader::getScripts(_url, mapping);
|
||||
auto scripts = FSTReader::getScripts(_url, _mapping);
|
||||
if (scripts.size() > 0) {
|
||||
mapping.remove(SCRIPT_FIELD);
|
||||
_mapping.remove(SCRIPT_FIELD);
|
||||
for (auto &scriptPath : scripts) {
|
||||
mapping.insertMulti(SCRIPT_FIELD, scriptPath);
|
||||
_mapping.insertMulti(SCRIPT_FIELD, scriptPath);
|
||||
}
|
||||
}
|
||||
|
||||
auto animGraphVariant = mapping.value("animGraphUrl");
|
||||
auto animGraphVariant = _mapping.value("animGraphUrl");
|
||||
|
||||
if (animGraphVariant.isValid()) {
|
||||
QUrl fstUrl(animGraphVariant.toString());
|
||||
if (fstUrl.isValid()) {
|
||||
|
@ -104,7 +106,7 @@ void GeometryMappingResource::downloadFinished(const QByteArray& data) {
|
|||
}
|
||||
|
||||
auto modelCache = DependencyManager::get<ModelCache>();
|
||||
GeometryExtra extra{ mapping, _textureBaseUrl, false };
|
||||
GeometryExtra extra{ _mapping, _textureBaseUrl, false };
|
||||
|
||||
// Get the raw GeometryResource
|
||||
_geometryResource = modelCache->getResource(url, QUrl(), &extra).staticCast<GeometryResource>();
|
||||
|
@ -379,6 +381,7 @@ Geometry::Geometry(const Geometry& geometry) {
|
|||
}
|
||||
|
||||
_animGraphOverrideUrl = geometry._animGraphOverrideUrl;
|
||||
_mapping = geometry._mapping;
|
||||
}
|
||||
|
||||
void Geometry::setTextures(const QVariantMap& textureMap) {
|
||||
|
@ -534,10 +537,11 @@ QUrl NetworkMaterial::getTextureUrl(const QUrl& baseUrl, const FBXTexture& textu
|
|||
// Inlined file: cache under the fbx file to avoid namespace clashes
|
||||
// NOTE: We cannot resolve the path because filename may be an absolute path
|
||||
assert(texture.filename.size() > 0);
|
||||
auto baseUrlStripped = baseUrl.toDisplayString(QUrl::RemoveFragment | QUrl::RemoveQuery | QUrl::RemoveUserInfo);
|
||||
if (texture.filename.at(0) == '/') {
|
||||
return baseUrl.toString() + texture.filename;
|
||||
return baseUrlStripped + texture.filename;
|
||||
} else {
|
||||
return baseUrl.toString() + '/' + texture.filename;
|
||||
return baseUrlStripped + '/' + texture.filename;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,6 +55,7 @@ public:
|
|||
|
||||
virtual bool areTexturesLoaded() const;
|
||||
const QUrl& getAnimGraphOverrideUrl() const { return _animGraphOverrideUrl; }
|
||||
const QVariantHash& getMapping() const { return _mapping; }
|
||||
|
||||
protected:
|
||||
friend class GeometryMappingResource;
|
||||
|
@ -68,6 +69,7 @@ protected:
|
|||
NetworkMaterials _materials;
|
||||
|
||||
QUrl _animGraphOverrideUrl;
|
||||
QVariantHash _mapping; // parsed contents of FST file.
|
||||
|
||||
private:
|
||||
mutable bool _areTexturesLoaded { false };
|
||||
|
@ -159,7 +161,7 @@ public:
|
|||
/**jsdoc
|
||||
* Get the list of all resource URLs.
|
||||
* @function ModelCache.getResourceList
|
||||
* @return {string[]}
|
||||
* @returns {string[]}
|
||||
*/
|
||||
|
||||
/**jsdoc
|
||||
|
@ -173,10 +175,11 @@ public:
|
|||
*/
|
||||
|
||||
/**jsdoc
|
||||
* Prefetches a resource.
|
||||
* @function ModelCache.prefetch
|
||||
* @param {string} url
|
||||
* @param {object} extra
|
||||
* @returns {object}
|
||||
* @param {string} url - URL of the resource to prefetch.
|
||||
* @param {object} [extra=null]
|
||||
* @returns {Resource}
|
||||
*/
|
||||
|
||||
/**jsdoc
|
||||
|
@ -185,14 +188,7 @@ public:
|
|||
* @param {string} url - URL of the resource to load.
|
||||
* @param {string} [fallback=""] - Fallback URL if load of the desired URL fails.
|
||||
* @param {} [extra=null]
|
||||
* @return {Resource}
|
||||
*/
|
||||
|
||||
/**jsdoc
|
||||
* Prefetches a resource.
|
||||
* @function ModelCache.prefetch
|
||||
* @param {string} url - URL of the resource to prefetch.
|
||||
* @return {Resource}
|
||||
* @returns {Resource}
|
||||
*/
|
||||
|
||||
|
||||
|
|
|
@ -48,6 +48,8 @@
|
|||
#include <Trace.h>
|
||||
#include <StatTracker.h>
|
||||
|
||||
#include <TextureMeta.h>
|
||||
|
||||
Q_LOGGING_CATEGORY(trace_resource_parse_image, "trace.resource.parse.image")
|
||||
Q_LOGGING_CATEGORY(trace_resource_parse_image_raw, "trace.resource.parse.image.raw")
|
||||
Q_LOGGING_CATEGORY(trace_resource_parse_image_ktx, "trace.resource.parse.image.ktx")
|
||||
|
@ -293,7 +295,6 @@ int networkTexturePointerMetaTypeId = qRegisterMetaType<QWeakPointer<NetworkText
|
|||
NetworkTexture::NetworkTexture(const QUrl& url) :
|
||||
Resource(url),
|
||||
_type(),
|
||||
_sourceIsKTX(false),
|
||||
_maxNumPixels(100)
|
||||
{
|
||||
_textureSource = std::make_shared<gpu::TextureSource>(url);
|
||||
|
@ -309,17 +310,25 @@ static bool isLocalUrl(const QUrl& url) {
|
|||
NetworkTexture::NetworkTexture(const QUrl& url, image::TextureUsage::Type type, const QByteArray& content, int maxNumPixels) :
|
||||
Resource(url),
|
||||
_type(type),
|
||||
_sourceIsKTX(url.path().endsWith(".ktx")),
|
||||
_maxNumPixels(maxNumPixels)
|
||||
{
|
||||
_textureSource = std::make_shared<gpu::TextureSource>(url, (int)type);
|
||||
_lowestRequestedMipLevel = 0;
|
||||
|
||||
_shouldFailOnRedirect = !_sourceIsKTX;
|
||||
auto fileNameLowercase = url.fileName().toLower();
|
||||
if (fileNameLowercase.endsWith(TEXTURE_META_EXTENSION)) {
|
||||
_currentlyLoadingResourceType = ResourceType::META;
|
||||
} else if (fileNameLowercase.endsWith(".ktx")) {
|
||||
_currentlyLoadingResourceType = ResourceType::KTX;
|
||||
} else {
|
||||
_currentlyLoadingResourceType = ResourceType::ORIGINAL;
|
||||
}
|
||||
|
||||
_shouldFailOnRedirect = _currentlyLoadingResourceType != ResourceType::KTX;
|
||||
|
||||
if (type == image::TextureUsage::CUBE_TEXTURE) {
|
||||
setLoadPriority(this, SKYBOX_LOAD_PRIORITY);
|
||||
} else if (_sourceIsKTX) {
|
||||
} else if (_currentlyLoadingResourceType == ResourceType::KTX) {
|
||||
setLoadPriority(this, HIGH_MIPS_LOAD_PRIORITY);
|
||||
}
|
||||
|
||||
|
@ -330,7 +339,7 @@ NetworkTexture::NetworkTexture(const QUrl& url, image::TextureUsage::Type type,
|
|||
// if we have content, load it after we have our self pointer
|
||||
if (!content.isEmpty()) {
|
||||
_startedLoading = true;
|
||||
QMetaObject::invokeMethod(this, "loadContent", Qt::QueuedConnection, Q_ARG(const QByteArray&, content));
|
||||
QMetaObject::invokeMethod(this, "downloadFinished", Qt::QueuedConnection, Q_ARG(const QByteArray&, content));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -393,12 +402,12 @@ NetworkTexture::~NetworkTexture() {
|
|||
|
||||
const uint16_t NetworkTexture::NULL_MIP_LEVEL = std::numeric_limits<uint16_t>::max();
|
||||
void NetworkTexture::makeRequest() {
|
||||
if (!_sourceIsKTX) {
|
||||
if (_currentlyLoadingResourceType != ResourceType::KTX) {
|
||||
Resource::makeRequest();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isLocalUrl(_url)) {
|
||||
if (isLocalUrl(_activeUrl)) {
|
||||
auto self = _self;
|
||||
QtConcurrent::run(QThreadPool::globalInstance(), [self] {
|
||||
auto resource = self.lock();
|
||||
|
@ -466,12 +475,12 @@ void NetworkTexture::handleLocalRequestCompleted() {
|
|||
}
|
||||
|
||||
void NetworkTexture::makeLocalRequest() {
|
||||
const QString scheme = _url.scheme();
|
||||
const QString scheme = _activeUrl.scheme();
|
||||
QString path;
|
||||
if (scheme == URL_SCHEME_FILE) {
|
||||
path = PathUtils::expandToLocalDataAbsolutePath(_url).toLocalFile();
|
||||
path = PathUtils::expandToLocalDataAbsolutePath(_activeUrl).toLocalFile();
|
||||
} else {
|
||||
path = ":" + _url.path();
|
||||
path = ":" + _activeUrl.path();
|
||||
}
|
||||
|
||||
connect(this, &Resource::finished, this, &NetworkTexture::handleLocalRequestCompleted);
|
||||
|
@ -497,7 +506,7 @@ void NetworkTexture::makeLocalRequest() {
|
|||
});
|
||||
|
||||
if (found == ktxDescriptor->keyValues.end() || found->_value.size() != gpu::SOURCE_HASH_BYTES) {
|
||||
hash = _url.toString().toLocal8Bit().toHex().toStdString();
|
||||
hash = _activeUrl.toString().toLocal8Bit().toHex().toStdString();
|
||||
} else {
|
||||
// at this point the source hash is in binary 16-byte form
|
||||
// and we need it in a hexadecimal string
|
||||
|
@ -536,11 +545,13 @@ void NetworkTexture::makeLocalRequest() {
|
|||
}
|
||||
|
||||
bool NetworkTexture::handleFailedRequest(ResourceRequest::Result result) {
|
||||
if (!_sourceIsKTX && result == ResourceRequest::Result::RedirectFail) {
|
||||
if (_currentlyLoadingResourceType != ResourceType::KTX
|
||||
&& result == ResourceRequest::Result::RedirectFail) {
|
||||
|
||||
auto newPath = _request->getRelativePathUrl();
|
||||
if (newPath.fileName().endsWith(".ktx")) {
|
||||
qDebug() << "Redirecting to" << newPath << "from" << _url;
|
||||
_sourceIsKTX = true;
|
||||
_currentlyLoadingResourceType = ResourceType::KTX;
|
||||
_activeUrl = newPath;
|
||||
_shouldFailOnRedirect = false;
|
||||
makeRequest();
|
||||
|
@ -930,11 +941,75 @@ void NetworkTexture::handleFinishedInitialLoad() {
|
|||
}
|
||||
|
||||
void NetworkTexture::downloadFinished(const QByteArray& data) {
|
||||
loadContent(data);
|
||||
if (_currentlyLoadingResourceType == ResourceType::META) {
|
||||
loadMetaContent(data);
|
||||
} else if (_currentlyLoadingResourceType == ResourceType::ORIGINAL) {
|
||||
loadTextureContent(data);
|
||||
} else {
|
||||
TextureCache::requestCompleted(_self);
|
||||
Resource::handleFailedRequest(ResourceRequest::Error);
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkTexture::loadContent(const QByteArray& content) {
|
||||
if (_sourceIsKTX) {
|
||||
void NetworkTexture::loadMetaContent(const QByteArray& content) {
|
||||
if (_currentlyLoadingResourceType != ResourceType::META) {
|
||||
qWarning() << "Trying to load meta content when current resource type is not META";
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
|
||||
TextureMeta meta;
|
||||
if (!TextureMeta::deserialize(content, &meta)) {
|
||||
qWarning() << "Failed to read texture meta from " << _url;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
auto& backend = DependencyManager::get<TextureCache>()->getGPUContext()->getBackend();
|
||||
for (auto pair : meta.availableTextureTypes) {
|
||||
gpu::Element elFormat;
|
||||
|
||||
if (gpu::Texture::getCompressedFormat(pair.first, elFormat)) {
|
||||
if (backend->supportedTextureFormat(elFormat)) {
|
||||
auto url = pair.second;
|
||||
if (url.fileName().endsWith(TEXTURE_META_EXTENSION)) {
|
||||
qWarning() << "Found a texture meta URL inside of the texture meta file at" << _activeUrl;
|
||||
continue;
|
||||
}
|
||||
|
||||
_currentlyLoadingResourceType = ResourceType::KTX;
|
||||
_activeUrl = _activeUrl.resolved(url);
|
||||
auto textureCache = DependencyManager::get<TextureCache>();
|
||||
auto self = _self.lock();
|
||||
if (!self) {
|
||||
return;
|
||||
}
|
||||
QMetaObject::invokeMethod(this, "attemptRequest", Qt::QueuedConnection);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!meta.original.isEmpty()) {
|
||||
_currentlyLoadingResourceType = ResourceType::ORIGINAL;
|
||||
_activeUrl = _activeUrl.resolved(meta.original);
|
||||
|
||||
auto textureCache = DependencyManager::get<TextureCache>();
|
||||
auto self = _self.lock();
|
||||
if (!self) {
|
||||
return;
|
||||
}
|
||||
QMetaObject::invokeMethod(this, "attemptRequest", Qt::QueuedConnection);
|
||||
return;
|
||||
}
|
||||
|
||||
qWarning() << "Failed to find supported texture type in " << _activeUrl;
|
||||
Resource::handleFailedRequest(ResourceRequest::NotFound);
|
||||
}
|
||||
|
||||
void NetworkTexture::loadTextureContent(const QByteArray& content) {
|
||||
if (_currentlyLoadingResourceType != ResourceType::ORIGINAL) {
|
||||
qWarning() << "Trying to load texture content when current resource type is not ORIGINAL";
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -24,7 +24,9 @@
|
|||
#include <graphics/TextureMap.h>
|
||||
#include <image/Image.h>
|
||||
#include <ktx/KTX.h>
|
||||
#include <TextureMeta.h>
|
||||
|
||||
#include <gpu/Context.h>
|
||||
#include "KTXCache.h"
|
||||
|
||||
namespace gpu {
|
||||
|
@ -75,11 +77,13 @@ protected:
|
|||
|
||||
virtual bool isCacheable() const override { return _loaded; }
|
||||
|
||||
virtual void downloadFinished(const QByteArray& data) override;
|
||||
Q_INVOKABLE virtual void downloadFinished(const QByteArray& data) override;
|
||||
|
||||
bool handleFailedRequest(ResourceRequest::Result result) override;
|
||||
|
||||
Q_INVOKABLE void loadContent(const QByteArray& content);
|
||||
Q_INVOKABLE void loadMetaContent(const QByteArray& content);
|
||||
Q_INVOKABLE void loadTextureContent(const QByteArray& content);
|
||||
|
||||
Q_INVOKABLE void setImage(gpu::TexturePointer texture, int originalWidth, int originalHeight);
|
||||
|
||||
Q_INVOKABLE void startRequestForNextMipLevel();
|
||||
|
@ -93,6 +97,14 @@ private:
|
|||
|
||||
image::TextureUsage::Type _type;
|
||||
|
||||
enum class ResourceType {
|
||||
META,
|
||||
ORIGINAL,
|
||||
KTX
|
||||
};
|
||||
|
||||
ResourceType _currentlyLoadingResourceType { ResourceType::META };
|
||||
|
||||
static const uint16_t NULL_MIP_LEVEL;
|
||||
enum KTXResourceState {
|
||||
PENDING_INITIAL_LOAD = 0,
|
||||
|
@ -103,7 +115,6 @@ private:
|
|||
FAILED_TO_LOAD
|
||||
};
|
||||
|
||||
bool _sourceIsKTX { false };
|
||||
KTXResourceState _ktxResourceState { PENDING_INITIAL_LOAD };
|
||||
|
||||
// The current mips that are currently being requested w/ _ktxMipRequest
|
||||
|
@ -163,44 +174,38 @@ public:
|
|||
|
||||
// Functions are copied over from ResourceCache (see ResourceCache.h for reason).
|
||||
|
||||
/**jsdoc
|
||||
* Get the list of all resource URLs.
|
||||
* @function TextureCache.getResourceList
|
||||
* @return {string[]}
|
||||
*/
|
||||
/**jsdoc
|
||||
* Get the list of all resource URLs.
|
||||
* @function TextureCache.getResourceList
|
||||
* @returns {string[]}
|
||||
*/
|
||||
|
||||
/**jsdoc
|
||||
* @function TextureCache.dirty
|
||||
* @returns {Signal}
|
||||
*/
|
||||
/**jsdoc
|
||||
* @function TextureCache.dirty
|
||||
* @returns {Signal}
|
||||
*/
|
||||
|
||||
/**jsdoc
|
||||
* @function TextureCache.updateTotalSize
|
||||
* @param {number} deltaSize
|
||||
*/
|
||||
/**jsdoc
|
||||
* @function TextureCache.updateTotalSize
|
||||
* @param {number} deltaSize
|
||||
*/
|
||||
|
||||
/**jsdoc
|
||||
* @function TextureCache.prefetch
|
||||
* @param {string} url
|
||||
* @param {object} extra
|
||||
* @returns {object}
|
||||
*/
|
||||
/**jsdoc
|
||||
* Prefetches a resource.
|
||||
* @function TextureCache.prefetch
|
||||
* @param {string} url - URL of the resource to prefetch.
|
||||
* @param {object} [extra=null]
|
||||
* @returns {Resource}
|
||||
*/
|
||||
|
||||
/**jsdoc
|
||||
* Asynchronously loads a resource from the specified URL and returns it.
|
||||
* @function TextureCache.getResource
|
||||
* @param {string} url - URL of the resource to load.
|
||||
* @param {string} [fallback=""] - Fallback URL if load of the desired URL fails.
|
||||
* @param {} [extra=null]
|
||||
* @return {Resource}
|
||||
*/
|
||||
|
||||
/**jsdoc
|
||||
* Prefetches a resource.
|
||||
* @function TextureCache.prefetch
|
||||
* @param {string} url - URL of the resource to prefetch.
|
||||
* @return {Resource}
|
||||
*/
|
||||
/**jsdoc
|
||||
* Asynchronously loads a resource from the specified URL and returns it.
|
||||
* @function TextureCache.getResource
|
||||
* @param {string} url - URL of the resource to load.
|
||||
* @param {string} [fallback=""] - Fallback URL if load of the desired URL fails.
|
||||
* @param {} [extra=null]
|
||||
* @returns {Resource}
|
||||
*/
|
||||
|
||||
|
||||
/// Returns the ID of the permutation/normal texture used for Perlin noise shader programs. This texture
|
||||
|
@ -239,6 +244,9 @@ public:
|
|||
static const int DEFAULT_SPECTATOR_CAM_WIDTH { 2048 };
|
||||
static const int DEFAULT_SPECTATOR_CAM_HEIGHT { 1024 };
|
||||
|
||||
void setGPUContext(const gpu::ContextPointer& context) { _gpuContext = context; }
|
||||
gpu::ContextPointer getGPUContext() const { return _gpuContext; }
|
||||
|
||||
signals:
|
||||
/**jsdoc
|
||||
* @function TextureCache.spectatorCameraFramebufferReset
|
||||
|
@ -249,10 +257,11 @@ signals:
|
|||
protected:
|
||||
|
||||
/**jsdoc
|
||||
* @function TextureCache.prefect
|
||||
* @function TextureCache.prefetch
|
||||
* @param {string} url
|
||||
* @param {number} type
|
||||
* @param {number} [maxNumPixels=67108864]
|
||||
* @returns {Resource}
|
||||
*/
|
||||
// Overload ResourceCache::prefetch to allow specifying texture type for loads
|
||||
Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url, int type, int maxNumPixels = ABSOLUTE_MAX_TEXTURE_NUM_PIXELS);
|
||||
|
@ -271,6 +280,8 @@ private:
|
|||
static const std::string KTX_DIRNAME;
|
||||
static const std::string KTX_EXT;
|
||||
|
||||
gpu::ContextPointer _gpuContext { nullptr };
|
||||
|
||||
std::shared_ptr<cache::FileCache> _ktxCache { std::make_shared<KTXCache>(KTX_DIRNAME, KTX_EXT) };
|
||||
|
||||
// Map from image hashes to texture weak pointers
|
||||
|
|
|
@ -41,8 +41,6 @@ const QString GET_PLACE = "/api/v1/places/%1";
|
|||
* @property {Uuid} domainID - A UUID uniquely identifying the domain you're visiting. Is {@link Uuid|Uuid.NULL} if you're not
|
||||
* connected to the domain or are in a serverless domain.
|
||||
* <em>Read-only.</em>
|
||||
* @property {Uuid} domainId - Synonym for <code>domainId</code>. <em>Read-only.</em> <strong>Deprecated:</strong> This property
|
||||
* is deprecated and will soon be removed.
|
||||
* @property {string} hostname - The name of the domain for your current metaverse address (e.g., <code>"AvatarIsland"</code>,
|
||||
* <code>localhost</code>, or an IP address). Is blank if you're in a serverless domain.
|
||||
* <em>Read-only.</em>
|
||||
|
@ -73,7 +71,6 @@ class AddressManager : public QObject, public Dependency {
|
|||
Q_PROPERTY(QString pathname READ currentPath)
|
||||
Q_PROPERTY(QString placename READ getPlaceName)
|
||||
Q_PROPERTY(QString domainID READ getDomainID)
|
||||
Q_PROPERTY(QString domainId READ getDomainID)
|
||||
public:
|
||||
using PositionGetter = std::function<glm::vec3()>;
|
||||
using OrientationGetter = std::function<glm::quat()>;
|
||||
|
|
|
@ -167,7 +167,7 @@ private:
|
|||
HifiSockAddr _assignmentServerSocket;
|
||||
bool _isShuttingDown { false };
|
||||
QTimer _keepAlivePingTimer;
|
||||
bool _requestsDomainListData;
|
||||
bool _requestsDomainListData { false };
|
||||
|
||||
mutable QReadWriteLock _ignoredSetLock;
|
||||
tbb::concurrent_unordered_set<QUuid, UUIDHasher> _ignoredNodeIDs;
|
||||
|
|
|
@ -581,6 +581,7 @@ void Resource::refresh() {
|
|||
ResourceCache::requestCompleted(_self);
|
||||
}
|
||||
|
||||
_activeUrl = _url;
|
||||
init();
|
||||
ensureLoading();
|
||||
emit onRefresh();
|
||||
|
@ -618,7 +619,6 @@ void Resource::init(bool resetLoaded) {
|
|||
_loaded = false;
|
||||
}
|
||||
_attempts = 0;
|
||||
_activeUrl = _url;
|
||||
|
||||
if (_url.isEmpty()) {
|
||||
_startedLoading = _loaded = true;
|
||||
|
@ -724,7 +724,7 @@ void Resource::handleReplyFinished() {
|
|||
auto result = _request->getResult();
|
||||
if (result == ResourceRequest::Success) {
|
||||
auto extraInfo = _url == _activeUrl ? "" : QString(", %1").arg(_activeUrl.toDisplayString());
|
||||
qCDebug(networking).noquote() << QString("Request finished for %1%2").arg(_url.toDisplayString(), extraInfo);
|
||||
qCDebug(networking).noquote() << QString("Request finished for %1%2").arg(_activeUrl.toDisplayString(), extraInfo);
|
||||
|
||||
auto relativePathURL = _request->getRelativePathUrl();
|
||||
if (!relativePathURL.isEmpty()) {
|
||||
|
|
|
@ -215,7 +215,7 @@ public:
|
|||
/**jsdoc
|
||||
* Get the list of all resource URLs.
|
||||
* @function ResourceCache.getResourceList
|
||||
* @return {string[]}
|
||||
* @returns {string[]}
|
||||
*/
|
||||
Q_INVOKABLE QVariantList getResourceList();
|
||||
|
||||
|
@ -257,10 +257,11 @@ protected slots:
|
|||
void updateTotalSize(const qint64& deltaSize);
|
||||
|
||||
/**jsdoc
|
||||
* Prefetches a resource.
|
||||
* @function ResourceCache.prefetch
|
||||
* @param {string} url
|
||||
* @param {object} extra
|
||||
* @returns {object}
|
||||
* @param {string} url - URL of the resource to prefetch.
|
||||
* @param {object} [extra=null]
|
||||
* @returns {Resource}
|
||||
*/
|
||||
// Prefetches a resource to be held by the QScriptEngine.
|
||||
// Left as a protected member so subclasses can overload prefetch
|
||||
|
@ -273,7 +274,7 @@ protected slots:
|
|||
* @param {string} url - URL of the resource to load.
|
||||
* @param {string} [fallback=""] - Fallback URL if load of the desired URL fails.
|
||||
* @param {} [extra=null]
|
||||
* @return {Resource}
|
||||
* @returns {Resource}
|
||||
*/
|
||||
/// Loads a resource from the specified URL and returns it.
|
||||
/// If the caller is on a different thread than the ResourceCache,
|
||||
|
@ -291,12 +292,7 @@ protected:
|
|||
// Pointers created through this method should be owned by the caller,
|
||||
// which should be a QScriptEngine with ScriptableResource registered, so that
|
||||
// the QScriptEngine will delete the pointer when it is garbage collected.
|
||||
/**jsdoc
|
||||
* Prefetches a resource.
|
||||
* @function ResourceCache.prefetch
|
||||
* @param {string} url - URL of the resource to prefetch.
|
||||
* @return {Resource}
|
||||
*/
|
||||
// JSDoc is provided on more general function signature.
|
||||
Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url) { return prefetch(url, nullptr); }
|
||||
|
||||
/// Creates a new resource.
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <QtCore/QDebug>
|
||||
#include "ConnectionStats.h"
|
||||
|
||||
using namespace udt;
|
||||
|
@ -112,3 +113,31 @@ void ConnectionStats::recordPacketSendPeriod(int sample) {
|
|||
_currentSample.packetSendPeriod = sample;
|
||||
_total.packetSendPeriod = (int)((_total.packetSendPeriod * EWMA_PREVIOUS_SAMPLES_WEIGHT) + (sample * EWMA_CURRENT_SAMPLE_WEIGHT));
|
||||
}
|
||||
|
||||
QDebug& operator<<(QDebug&& debug, const udt::ConnectionStats::Stats& stats) {
|
||||
debug << "Connection stats:\n";
|
||||
#define HIFI_LOG_EVENT(x) << " " #x " events: " << stats.events[ConnectionStats::Stats::Event::x] << "\n"
|
||||
debug
|
||||
HIFI_LOG_EVENT(SentACK)
|
||||
HIFI_LOG_EVENT(ReceivedACK)
|
||||
HIFI_LOG_EVENT(ProcessedACK)
|
||||
HIFI_LOG_EVENT(SentLightACK)
|
||||
HIFI_LOG_EVENT(ReceivedLightACK)
|
||||
HIFI_LOG_EVENT(SentACK2)
|
||||
HIFI_LOG_EVENT(ReceivedACK2)
|
||||
HIFI_LOG_EVENT(SentNAK)
|
||||
HIFI_LOG_EVENT(ReceivedNAK)
|
||||
HIFI_LOG_EVENT(SentTimeoutNAK)
|
||||
HIFI_LOG_EVENT(ReceivedTimeoutNAK)
|
||||
HIFI_LOG_EVENT(Retransmission)
|
||||
HIFI_LOG_EVENT(Duplicate)
|
||||
;
|
||||
#undef HIFI_LOG_EVENT
|
||||
|
||||
debug << " Sent packets: " << stats.sentPackets;
|
||||
debug << "\n Received packets: " << stats.receivedPackets;
|
||||
debug << "\n Sent util bytes: " << stats.sentUtilBytes;
|
||||
debug << "\n Sent bytes: " << stats.sentBytes;
|
||||
debug << "\n Received bytes: " << stats.receivedBytes << "\n";
|
||||
return debug;
|
||||
}
|
||||
|
|
|
@ -101,4 +101,7 @@ private:
|
|||
|
||||
}
|
||||
|
||||
class QDebug;
|
||||
QDebug& operator<<(QDebug&& debug, const udt::ConnectionStats::Stats& stats);
|
||||
|
||||
#endif // hifi_ConnectionStats_h
|
||||
|
|
|
@ -34,7 +34,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
|
|||
case PacketType::EntityPhysics:
|
||||
return static_cast<PacketVersion>(EntityVersion::MaterialData);
|
||||
case PacketType::EntityQuery:
|
||||
return static_cast<PacketVersion>(EntityQueryPacketVersion::RemovedJurisdictions);
|
||||
return static_cast<PacketVersion>(EntityQueryPacketVersion::ConicalFrustums);
|
||||
case PacketType::AvatarIdentity:
|
||||
case PacketType::AvatarData:
|
||||
case PacketType::BulkAvatarData:
|
||||
|
@ -59,11 +59,10 @@ PacketVersion versionForPacketType(PacketType packetType) {
|
|||
return 17;
|
||||
case PacketType::AssetMappingOperation:
|
||||
case PacketType::AssetMappingOperationReply:
|
||||
return static_cast<PacketVersion>(AssetServerPacketVersion::RedirectedMappings);
|
||||
case PacketType::AssetGetInfo:
|
||||
case PacketType::AssetGet:
|
||||
case PacketType::AssetUpload:
|
||||
return static_cast<PacketVersion>(AssetServerPacketVersion::RangeRequestSupport);
|
||||
return static_cast<PacketVersion>(AssetServerPacketVersion::BakingTextureMeta);
|
||||
case PacketType::NodeIgnoreRequest:
|
||||
return 18; // Introduction of node ignore request (which replaced an unused packet tpye)
|
||||
|
||||
|
@ -90,6 +89,8 @@ PacketVersion versionForPacketType(PacketType packetType) {
|
|||
return 18; // replace min_avatar_scale and max_avatar_scale with min_avatar_height and max_avatar_height
|
||||
case PacketType::Ping:
|
||||
return static_cast<PacketVersion>(PingVersion::IncludeConnectionID);
|
||||
case PacketType::AvatarQuery:
|
||||
return static_cast<PacketVersion>(AvatarQueryVersion::ConicalFrustums);
|
||||
default:
|
||||
return 20;
|
||||
}
|
||||
|
|
|
@ -103,7 +103,7 @@ public:
|
|||
RadiusIgnoreRequest,
|
||||
UsernameFromIDRequest,
|
||||
UsernameFromIDReply,
|
||||
ViewFrustum,
|
||||
AvatarQuery,
|
||||
RequestsDomainListData,
|
||||
PerAvatarGainSet,
|
||||
EntityScriptGetStatus,
|
||||
|
@ -244,13 +244,16 @@ enum class EntityQueryPacketVersion: PacketVersion {
|
|||
JSONFilter = 18,
|
||||
JSONFilterWithFamilyTree = 19,
|
||||
ConnectionIdentifier = 20,
|
||||
RemovedJurisdictions = 21
|
||||
RemovedJurisdictions = 21,
|
||||
MultiFrustumQuery = 22,
|
||||
ConicalFrustums = 23
|
||||
};
|
||||
|
||||
enum class AssetServerPacketVersion: PacketVersion {
|
||||
VegasCongestionControl = 19,
|
||||
RangeRequestSupport,
|
||||
RedirectedMappings
|
||||
RedirectedMappings,
|
||||
BakingTextureMeta
|
||||
};
|
||||
|
||||
enum class AvatarMixerPacketVersion : PacketVersion {
|
||||
|
@ -327,4 +330,9 @@ enum class PingVersion : PacketVersion {
|
|||
IncludeConnectionID = 18
|
||||
};
|
||||
|
||||
enum class AvatarQueryVersion : PacketVersion {
|
||||
SendMultipleFrustums = 21,
|
||||
ConicalFrustums = 22
|
||||
};
|
||||
|
||||
#endif // hifi_PacketHeaders_h
|
||||
|
|
|
@ -59,7 +59,6 @@ const int LOW_RES_MOVING_ADJUST = 1;
|
|||
|
||||
class EncodeBitstreamParams {
|
||||
public:
|
||||
ViewFrustum viewFrustum;
|
||||
bool includeExistsBits;
|
||||
NodeData* nodeData;
|
||||
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "OctreeQuery.h"
|
||||
|
||||
#include <random>
|
||||
|
||||
#include <QtCore/QJsonDocument>
|
||||
|
@ -16,23 +18,7 @@
|
|||
#include <GLMHelpers.h>
|
||||
#include <udt/PacketHeaders.h>
|
||||
|
||||
#include "OctreeConstants.h"
|
||||
#include "OctreeQuery.h"
|
||||
|
||||
const float DEFAULT_FOV = 45.0f; // degrees
|
||||
const float DEFAULT_ASPECT_RATIO = 1.0f;
|
||||
const float DEFAULT_NEAR_CLIP = 0.1f;
|
||||
const float DEFAULT_FAR_CLIP = 3.0f;
|
||||
|
||||
OctreeQuery::OctreeQuery(bool randomizeConnectionID) :
|
||||
_cameraFov(DEFAULT_FOV),
|
||||
_cameraAspectRatio(DEFAULT_ASPECT_RATIO),
|
||||
_cameraNearClip(DEFAULT_NEAR_CLIP),
|
||||
_cameraFarClip(DEFAULT_FAR_CLIP),
|
||||
_cameraCenterRadius(DEFAULT_FAR_CLIP)
|
||||
{
|
||||
_maxQueryPPS = DEFAULT_MAX_OCTREE_PPS;
|
||||
|
||||
OctreeQuery::OctreeQuery(bool randomizeConnectionID) {
|
||||
if (randomizeConnectionID) {
|
||||
// randomize our initial octree query connection ID using random_device
|
||||
// the connection ID is 16 bits so we take a generated 32 bit value from random device and chop off the top
|
||||
|
@ -47,26 +33,14 @@ int OctreeQuery::getBroadcastData(unsigned char* destinationBuffer) {
|
|||
// pack the connection ID so the server can detect when we start a new connection
|
||||
memcpy(destinationBuffer, &_connectionID, sizeof(_connectionID));
|
||||
destinationBuffer += sizeof(_connectionID);
|
||||
|
||||
// back a boolean (cut to 1 byte) to designate if this query uses the sent view frustum
|
||||
memcpy(destinationBuffer, &_usesFrustum, sizeof(_usesFrustum));
|
||||
destinationBuffer += sizeof(_usesFrustum);
|
||||
|
||||
if (_usesFrustum) {
|
||||
// TODO: DRY this up to a shared method
|
||||
// that can pack any type given the number of bytes
|
||||
// and return the number of bytes to push the pointer
|
||||
|
||||
// camera details
|
||||
memcpy(destinationBuffer, &_cameraPosition, sizeof(_cameraPosition));
|
||||
destinationBuffer += sizeof(_cameraPosition);
|
||||
destinationBuffer += packOrientationQuatToBytes(destinationBuffer, _cameraOrientation);
|
||||
destinationBuffer += packFloatAngleToTwoByte(destinationBuffer, _cameraFov);
|
||||
destinationBuffer += packFloatRatioToTwoByte(destinationBuffer, _cameraAspectRatio);
|
||||
destinationBuffer += packClipValueToTwoByte(destinationBuffer, _cameraNearClip);
|
||||
destinationBuffer += packClipValueToTwoByte(destinationBuffer, _cameraFarClip);
|
||||
memcpy(destinationBuffer, &_cameraEyeOffsetPosition, sizeof(_cameraEyeOffsetPosition));
|
||||
destinationBuffer += sizeof(_cameraEyeOffsetPosition);
|
||||
|
||||
// Number of frustums
|
||||
uint8_t numFrustums = (uint8_t)_conicalViews.size();
|
||||
memcpy(destinationBuffer, &numFrustums, sizeof(numFrustums));
|
||||
destinationBuffer += sizeof(numFrustums);
|
||||
|
||||
for (const auto& view : _conicalViews) {
|
||||
destinationBuffer += view.serialize(destinationBuffer);
|
||||
}
|
||||
|
||||
// desired Max Octree PPS
|
||||
|
@ -80,9 +54,6 @@ int OctreeQuery::getBroadcastData(unsigned char* destinationBuffer) {
|
|||
// desired boundaryLevelAdjust
|
||||
memcpy(destinationBuffer, &_boundaryLevelAdjust, sizeof(_boundaryLevelAdjust));
|
||||
destinationBuffer += sizeof(_boundaryLevelAdjust);
|
||||
|
||||
memcpy(destinationBuffer, &_cameraCenterRadius, sizeof(_cameraCenterRadius));
|
||||
destinationBuffer += sizeof(_cameraCenterRadius);
|
||||
|
||||
// create a QByteArray that holds the binary representation of the JSON parameters
|
||||
QByteArray binaryParametersDocument;
|
||||
|
@ -133,20 +104,15 @@ int OctreeQuery::parseData(ReceivedMessage& message) {
|
|||
}
|
||||
|
||||
// check if this query uses a view frustum
|
||||
memcpy(&_usesFrustum, sourceBuffer, sizeof(_usesFrustum));
|
||||
sourceBuffer += sizeof(_usesFrustum);
|
||||
|
||||
if (_usesFrustum) {
|
||||
// unpack camera details
|
||||
memcpy(&_cameraPosition, sourceBuffer, sizeof(_cameraPosition));
|
||||
sourceBuffer += sizeof(_cameraPosition);
|
||||
sourceBuffer += unpackOrientationQuatFromBytes(sourceBuffer, _cameraOrientation);
|
||||
sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &_cameraFov);
|
||||
sourceBuffer += unpackFloatRatioFromTwoByte(sourceBuffer,_cameraAspectRatio);
|
||||
sourceBuffer += unpackClipValueFromTwoByte(sourceBuffer,_cameraNearClip);
|
||||
sourceBuffer += unpackClipValueFromTwoByte(sourceBuffer,_cameraFarClip);
|
||||
memcpy(&_cameraEyeOffsetPosition, sourceBuffer, sizeof(_cameraEyeOffsetPosition));
|
||||
sourceBuffer += sizeof(_cameraEyeOffsetPosition);
|
||||
uint8_t numFrustums = 0;
|
||||
memcpy(&numFrustums, sourceBuffer, sizeof(numFrustums));
|
||||
sourceBuffer += sizeof(numFrustums);
|
||||
|
||||
_conicalViews.clear();
|
||||
for (int i = 0; i < numFrustums; ++i) {
|
||||
ConicalViewFrustum view;
|
||||
sourceBuffer += view.deserialize(sourceBuffer);
|
||||
_conicalViews.push_back(view);
|
||||
}
|
||||
|
||||
// desired Max Octree PPS
|
||||
|
@ -161,9 +127,6 @@ int OctreeQuery::parseData(ReceivedMessage& message) {
|
|||
memcpy(&_boundaryLevelAdjust, sourceBuffer, sizeof(_boundaryLevelAdjust));
|
||||
sourceBuffer += sizeof(_boundaryLevelAdjust);
|
||||
|
||||
memcpy(&_cameraCenterRadius, sourceBuffer, sizeof(_cameraCenterRadius));
|
||||
sourceBuffer += sizeof(_cameraCenterRadius);
|
||||
|
||||
// check if we have a packed JSON filter
|
||||
uint16_t binaryParametersBytes;
|
||||
memcpy(&binaryParametersBytes, sourceBuffer, sizeof(binaryParametersBytes));
|
||||
|
@ -184,8 +147,3 @@ int OctreeQuery::parseData(ReceivedMessage& message) {
|
|||
|
||||
return sourceBuffer - startPosition;
|
||||
}
|
||||
|
||||
glm::vec3 OctreeQuery::calculateCameraDirection() const {
|
||||
glm::vec3 direction = glm::vec3(_cameraOrientation * glm::vec4(IDENTITY_FORWARD, 0.0f));
|
||||
return direction;
|
||||
}
|
||||
|
|
|
@ -12,16 +12,13 @@
|
|||
#ifndef hifi_OctreeQuery_h
|
||||
#define hifi_OctreeQuery_h
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QReadWriteLock>
|
||||
|
||||
#include <NodeData.h>
|
||||
#include <shared/ConicalViewFrustum.h>
|
||||
|
||||
#include "OctreeConstants.h"
|
||||
|
||||
class OctreeQuery : public NodeData {
|
||||
Q_OBJECT
|
||||
|
@ -30,31 +27,16 @@ public:
|
|||
OctreeQuery(bool randomizeConnectionID = false);
|
||||
virtual ~OctreeQuery() {}
|
||||
|
||||
OctreeQuery(const OctreeQuery&) = delete;
|
||||
OctreeQuery& operator=(const OctreeQuery&) = delete;
|
||||
|
||||
int getBroadcastData(unsigned char* destinationBuffer);
|
||||
int parseData(ReceivedMessage& message) override;
|
||||
|
||||
// getters for camera details
|
||||
const glm::vec3& getCameraPosition() const { return _cameraPosition; }
|
||||
const glm::quat& getCameraOrientation() const { return _cameraOrientation; }
|
||||
float getCameraFov() const { return _cameraFov; }
|
||||
float getCameraAspectRatio() const { return _cameraAspectRatio; }
|
||||
float getCameraNearClip() const { return _cameraNearClip; }
|
||||
float getCameraFarClip() const { return _cameraFarClip; }
|
||||
const glm::vec3& getCameraEyeOffsetPosition() const { return _cameraEyeOffsetPosition; }
|
||||
float getCameraCenterRadius() const { return _cameraCenterRadius; }
|
||||
bool hasConicalViews() const { return !_conicalViews.empty(); }
|
||||
void setConicalViews(ConicalViewFrustums views) { _conicalViews = views; }
|
||||
void clearConicalViews() { _conicalViews.clear(); }
|
||||
|
||||
glm::vec3 calculateCameraDirection() const;
|
||||
|
||||
// setters for camera details
|
||||
void setCameraPosition(const glm::vec3& position) { _cameraPosition = position; }
|
||||
void setCameraOrientation(const glm::quat& orientation) { _cameraOrientation = orientation; }
|
||||
void setCameraFov(float fov) { _cameraFov = fov; }
|
||||
void setCameraAspectRatio(float aspectRatio) { _cameraAspectRatio = aspectRatio; }
|
||||
void setCameraNearClip(float nearClip) { _cameraNearClip = nearClip; }
|
||||
void setCameraFarClip(float farClip) { _cameraFarClip = farClip; }
|
||||
void setCameraEyeOffsetPosition(const glm::vec3& eyeOffsetPosition) { _cameraEyeOffsetPosition = eyeOffsetPosition; }
|
||||
void setCameraCenterRadius(float radius) { _cameraCenterRadius = radius; }
|
||||
|
||||
// getters/setters for JSON filter
|
||||
QJsonObject getJSONParameters() { QReadLocker locker { &_jsonParametersLock }; return _jsonParameters; }
|
||||
void setJSONParameters(const QJsonObject& jsonParameters)
|
||||
|
@ -64,9 +46,6 @@ public:
|
|||
int getMaxQueryPacketsPerSecond() const { return _maxQueryPPS; }
|
||||
float getOctreeSizeScale() const { return _octreeElementSizeScale; }
|
||||
int getBoundaryLevelAdjust() const { return _boundaryLevelAdjust; }
|
||||
|
||||
bool getUsesFrustum() { return _usesFrustum; }
|
||||
void setUsesFrustum(bool usesFrustum) { _usesFrustum = usesFrustum; }
|
||||
|
||||
void incrementConnectionID() { ++_connectionID; }
|
||||
|
||||
|
@ -81,33 +60,19 @@ public slots:
|
|||
void setBoundaryLevelAdjust(int boundaryLevelAdjust) { _boundaryLevelAdjust = boundaryLevelAdjust; }
|
||||
|
||||
protected:
|
||||
// camera details for the avatar
|
||||
glm::vec3 _cameraPosition { glm::vec3(0.0f) };
|
||||
glm::quat _cameraOrientation { glm::quat() };
|
||||
float _cameraFov;
|
||||
float _cameraAspectRatio;
|
||||
float _cameraNearClip;
|
||||
float _cameraFarClip;
|
||||
float _cameraCenterRadius;
|
||||
glm::vec3 _cameraEyeOffsetPosition { glm::vec3(0.0f) };
|
||||
ConicalViewFrustums _conicalViews;
|
||||
|
||||
// octree server sending items
|
||||
int _maxQueryPPS = DEFAULT_MAX_OCTREE_PPS;
|
||||
float _octreeElementSizeScale = DEFAULT_OCTREE_SIZE_SCALE; /// used for LOD calculations
|
||||
int _boundaryLevelAdjust = 0; /// used for LOD calculations
|
||||
|
||||
uint8_t _usesFrustum = true;
|
||||
|
||||
uint16_t _connectionID; // query connection ID, randomized to start, increments with each new connection to server
|
||||
|
||||
QJsonObject _jsonParameters;
|
||||
QReadWriteLock _jsonParametersLock;
|
||||
|
||||
bool _hasReceivedFirstQuery { false };
|
||||
|
||||
private:
|
||||
// privatize the copy constructor and assignment operator so they cannot be called
|
||||
OctreeQuery(const OctreeQuery&);
|
||||
OctreeQuery& operator= (const OctreeQuery&);
|
||||
};
|
||||
|
||||
#endif // hifi_OctreeQuery_h
|
||||
|
|
|
@ -139,81 +139,61 @@ void OctreeQueryNode::writeToPacket(const unsigned char* buffer, unsigned int by
|
|||
}
|
||||
}
|
||||
|
||||
void OctreeQueryNode::copyCurrentViewFrustum(ViewFrustum& viewOut) const {
|
||||
QMutexLocker viewLocker(&_viewMutex);
|
||||
viewOut = _currentViewFrustum;
|
||||
}
|
||||
|
||||
bool OctreeQueryNode::updateCurrentViewFrustum() {
|
||||
// if shutting down, return immediately
|
||||
if (_isShuttingDown) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_usesFrustum) {
|
||||
if (!hasConicalViews()) {
|
||||
// this client does not use a view frustum so the view frustum for this query has not changed
|
||||
return false;
|
||||
} else {
|
||||
bool currentViewFrustumChanged = false;
|
||||
|
||||
ViewFrustum newestViewFrustum;
|
||||
// get position and orientation details from the camera
|
||||
newestViewFrustum.setPosition(getCameraPosition());
|
||||
newestViewFrustum.setOrientation(getCameraOrientation());
|
||||
|
||||
newestViewFrustum.setCenterRadius(getCameraCenterRadius());
|
||||
|
||||
// Also make sure it's got the correct lens details from the camera
|
||||
float originalFOV = getCameraFov();
|
||||
float wideFOV = originalFOV + VIEW_FRUSTUM_FOV_OVERSEND;
|
||||
|
||||
if (0.0f != getCameraAspectRatio() &&
|
||||
0.0f != getCameraNearClip() &&
|
||||
0.0f != getCameraFarClip() &&
|
||||
getCameraNearClip() != getCameraFarClip()) {
|
||||
newestViewFrustum.setProjection(glm::perspective(
|
||||
glm::radians(wideFOV), // hack
|
||||
getCameraAspectRatio(),
|
||||
getCameraNearClip(),
|
||||
getCameraFarClip()));
|
||||
newestViewFrustum.calculate();
|
||||
}
|
||||
|
||||
|
||||
{ // if there has been a change, then recalculate
|
||||
QMutexLocker viewLocker(&_viewMutex);
|
||||
if (!newestViewFrustum.isVerySimilar(_currentViewFrustum)) {
|
||||
_currentViewFrustum = newestViewFrustum;
|
||||
currentViewFrustumChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Also check for LOD changes from the client
|
||||
if (_lodInitialized) {
|
||||
if (_lastClientBoundaryLevelAdjust != getBoundaryLevelAdjust()) {
|
||||
_lastClientBoundaryLevelAdjust = getBoundaryLevelAdjust();
|
||||
_lodChanged = true;
|
||||
}
|
||||
if (_lastClientOctreeSizeScale != getOctreeSizeScale()) {
|
||||
_lastClientOctreeSizeScale = getOctreeSizeScale();
|
||||
_lodChanged = true;
|
||||
}
|
||||
|
||||
bool currentViewFrustumChanged = false;
|
||||
|
||||
{ // if there has been a change, then recalculate
|
||||
QMutexLocker viewLocker(&_viewMutex);
|
||||
|
||||
if (_conicalViews.size() == _currentConicalViews.size()) {
|
||||
for (size_t i = 0; i < _conicalViews.size(); ++i) {
|
||||
if (!_conicalViews[i].isVerySimilar(_currentConicalViews[i])) {
|
||||
_currentConicalViews = _conicalViews;
|
||||
currentViewFrustumChanged = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_lodInitialized = true;
|
||||
_lastClientOctreeSizeScale = getOctreeSizeScale();
|
||||
_lastClientBoundaryLevelAdjust = getBoundaryLevelAdjust();
|
||||
_lodChanged = false;
|
||||
_currentConicalViews = _conicalViews;
|
||||
currentViewFrustumChanged = true;
|
||||
}
|
||||
|
||||
// When we first detect that the view stopped changing, we record this.
|
||||
// but we don't change it back to false until we've completely sent this
|
||||
// scene.
|
||||
if (_viewFrustumChanging && !currentViewFrustumChanged) {
|
||||
_viewFrustumJustStoppedChanging = true;
|
||||
}
|
||||
_viewFrustumChanging = currentViewFrustumChanged;
|
||||
return currentViewFrustumChanged;
|
||||
}
|
||||
|
||||
// Also check for LOD changes from the client
|
||||
if (_lodInitialized) {
|
||||
if (_lastClientBoundaryLevelAdjust != getBoundaryLevelAdjust()) {
|
||||
_lastClientBoundaryLevelAdjust = getBoundaryLevelAdjust();
|
||||
_lodChanged = true;
|
||||
}
|
||||
if (_lastClientOctreeSizeScale != getOctreeSizeScale()) {
|
||||
_lastClientOctreeSizeScale = getOctreeSizeScale();
|
||||
_lodChanged = true;
|
||||
}
|
||||
} else {
|
||||
_lodInitialized = true;
|
||||
_lastClientOctreeSizeScale = getOctreeSizeScale();
|
||||
_lastClientBoundaryLevelAdjust = getBoundaryLevelAdjust();
|
||||
_lodChanged = false;
|
||||
}
|
||||
|
||||
// When we first detect that the view stopped changing, we record this.
|
||||
// but we don't change it back to false until we've completely sent this
|
||||
// scene.
|
||||
if (_viewFrustumChanging && !currentViewFrustumChanged) {
|
||||
_viewFrustumJustStoppedChanging = true;
|
||||
}
|
||||
_viewFrustumChanging = currentViewFrustumChanged;
|
||||
return currentViewFrustumChanged;
|
||||
}
|
||||
|
||||
void OctreeQueryNode::setViewSent(bool viewSent) {
|
||||
|
|
|
@ -14,14 +14,14 @@
|
|||
|
||||
#include <iostream>
|
||||
|
||||
#include <NodeData.h>
|
||||
#include <qqueue.h>
|
||||
|
||||
#include "OctreeConstants.h"
|
||||
#include "OctreeElementBag.h"
|
||||
#include "OctreePacketData.h"
|
||||
#include "OctreeQuery.h"
|
||||
#include "OctreeSceneStats.h"
|
||||
#include "SentPacketHistory.h"
|
||||
#include <qqueue.h>
|
||||
|
||||
class OctreeSendThread;
|
||||
class OctreeServer;
|
||||
|
@ -49,7 +49,7 @@ public:
|
|||
|
||||
OctreeElementExtraEncodeData extraEncodeData;
|
||||
|
||||
void copyCurrentViewFrustum(ViewFrustum& viewOut) const;
|
||||
const ConicalViewFrustums& getCurrentViews() const { return _currentConicalViews; }
|
||||
|
||||
// These are not classic setters because they are calculating and maintaining state
|
||||
// which is set asynchronously through the network receive
|
||||
|
@ -87,9 +87,6 @@ public:
|
|||
void setShouldForceFullScene(bool shouldForceFullScene) { _shouldForceFullScene = shouldForceFullScene; }
|
||||
|
||||
private:
|
||||
OctreeQueryNode(const OctreeQueryNode &);
|
||||
OctreeQueryNode& operator= (const OctreeQueryNode&);
|
||||
|
||||
bool _viewSent { false };
|
||||
std::unique_ptr<NLPacket> _octreePacket;
|
||||
bool _octreePacketWaiting;
|
||||
|
@ -99,7 +96,7 @@ private:
|
|||
quint64 _firstSuppressedPacket { usecTimestampNow() };
|
||||
|
||||
mutable QMutex _viewMutex { QMutex::Recursive };
|
||||
ViewFrustum _currentViewFrustum;
|
||||
ConicalViewFrustums _currentConicalViews;
|
||||
bool _viewFrustumChanging { false };
|
||||
bool _viewFrustumJustStoppedChanging { true };
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include <glm/glm.hpp>
|
||||
|
||||
#include <AABox.h>
|
||||
#include <AACube.h>
|
||||
|
||||
float calculateRenderAccuracy(const glm::vec3& position,
|
||||
const AABox& bounds,
|
||||
|
@ -73,4 +74,10 @@ float getOrthographicAccuracySize(float octreeSizeScale, int boundaryLevelAdjust
|
|||
// Smallest visible element is 1cm
|
||||
const float smallestSize = 0.01f;
|
||||
return (smallestSize * MAX_VISIBILITY_DISTANCE_FOR_UNIT_ELEMENT) / boundaryDistanceForRenderLevel(boundaryLevelAdjust, octreeSizeScale);
|
||||
}
|
||||
}
|
||||
|
||||
bool isAngularSizeBigEnough(glm::vec3 position, const AACube& cube, float lodScaleFactor, float minDiameter) {
|
||||
float distance = glm::distance(cube.calcCenter(), position) + MIN_VISIBLE_DISTANCE;
|
||||
float angularDiameter = cube.getScale() / distance;
|
||||
return angularDiameter > minDiameter * lodScaleFactor;
|
||||
}
|
||||
|
|
|
@ -12,9 +12,12 @@
|
|||
#ifndef hifi_OctreeUtils_h
|
||||
#define hifi_OctreeUtils_h
|
||||
|
||||
#include <NumericalConstants.h>
|
||||
|
||||
#include "OctreeConstants.h"
|
||||
|
||||
class AABox;
|
||||
class AACube;
|
||||
class QJsonDocument;
|
||||
|
||||
/// renderAccuracy represents a floating point "visibility" of an object based on it's view from the camera. At a simple
|
||||
|
@ -32,8 +35,9 @@ float getOrthographicAccuracySize(float octreeSizeScale, int boundaryLevelAdjust
|
|||
// MIN_ELEMENT_ANGULAR_DIAMETER = angular diameter of 1x1x1m cube at 400m = sqrt(3) / 400 = 0.0043301 radians ~= 0.25 degrees
|
||||
const float MIN_ELEMENT_ANGULAR_DIAMETER = 0.0043301f; // radians
|
||||
// NOTE: the entity bounding cube is larger than the smallest possible containing octree element by sqrt(3)
|
||||
const float SQRT_THREE = 1.73205080f;
|
||||
const float MIN_ENTITY_ANGULAR_DIAMETER = MIN_ELEMENT_ANGULAR_DIAMETER * SQRT_THREE;
|
||||
const float MIN_VISIBLE_DISTANCE = 0.0001f; // helps avoid divide-by-zero check
|
||||
|
||||
bool isAngularSizeBigEnough(glm::vec3 position, const AACube& cube, float lodScaleFactor, float minDiameter);
|
||||
|
||||
#endif // hifi_OctreeUtils_h
|
||||
|
|
|
@ -24,6 +24,8 @@ class Transform;
|
|||
class QThread;
|
||||
class ViewFrustum;
|
||||
class PickRay;
|
||||
class ConicalViewFrustum;
|
||||
using ConicalViewFrustums = std::vector<ConicalViewFrustum>;
|
||||
|
||||
/// Interface provided by Application to other objects that need access to the current view state details
|
||||
class AbstractViewStateInterface {
|
||||
|
@ -31,6 +33,8 @@ public:
|
|||
/// copies the current view frustum for rendering the view state
|
||||
virtual void copyCurrentViewFrustum(ViewFrustum& viewOut) const = 0;
|
||||
|
||||
virtual const ConicalViewFrustums& getConicalViews() const = 0;
|
||||
|
||||
virtual QThread* getMainThread() = 0;
|
||||
|
||||
virtual PickRay computePickRay(float x, float y) const = 0;
|
||||
|
|
|
@ -16,11 +16,21 @@
|
|||
<@include ForwardGlobalLight.slh@>
|
||||
<$declareEvalSkyboxGlobalColor()$>
|
||||
|
||||
|
||||
// the interpolated normal
|
||||
in vec3 _normalWS;
|
||||
in vec3 _normalMS;
|
||||
in vec4 _color;
|
||||
in vec2 _texCoord0;
|
||||
in vec4 _positionMS;
|
||||
in vec4 _positionES;
|
||||
|
||||
// For retro-compatibility
|
||||
#define _normal _normalWS
|
||||
#define _modelNormal _normalMS
|
||||
#define _position _positionMS
|
||||
#define _eyePosition _positionES
|
||||
|
||||
layout(location = 0) out vec4 _fragColor0;
|
||||
|
||||
//PROCEDURAL_COMMON_BLOCK
|
||||
|
|
|
@ -18,9 +18,18 @@
|
|||
|
||||
// the interpolated normal
|
||||
in vec3 _normalWS;
|
||||
in vec3 _normalMS;
|
||||
in vec4 _color;
|
||||
in vec2 _texCoord0;
|
||||
in vec4 _positionMS;
|
||||
in vec4 _positionES;
|
||||
|
||||
// For retro-compatibility
|
||||
#define _normal _normalWS
|
||||
#define _modelNormal _normalMS
|
||||
#define _position _positionMS
|
||||
#define _eyePosition _positionES
|
||||
|
||||
layout(location = 0) out vec4 _fragColor0;
|
||||
|
||||
//PROCEDURAL_COMMON_BLOCK
|
||||
|
|
|
@ -16,7 +16,17 @@
|
|||
|
||||
// the interpolated normal
|
||||
in vec3 _normalWS;
|
||||
in vec3 _normalMS;
|
||||
in vec4 _color;
|
||||
in vec2 _texCoord0;
|
||||
in vec4 _positionMS;
|
||||
in vec4 _positionES;
|
||||
|
||||
// For retro-compatibility
|
||||
#define _normal _normalWS
|
||||
#define _modelNormal _normalMS
|
||||
#define _position _positionMS
|
||||
#define _eyePosition _positionES
|
||||
|
||||
//PROCEDURAL_COMMON_BLOCK
|
||||
|
||||
|
|
|
@ -19,9 +19,19 @@
|
|||
|
||||
// the interpolated normal
|
||||
in vec3 _normalWS;
|
||||
in vec3 _normalMS;
|
||||
in vec4 _color;
|
||||
in vec2 _texCoord0;
|
||||
in vec4 _positionMS;
|
||||
in vec4 _positionES;
|
||||
in vec4 _positionWS;
|
||||
|
||||
// For retro-compatibility
|
||||
#define _normal _normalWS
|
||||
#define _modelNormal _normalMS
|
||||
#define _position _positionMS
|
||||
#define _eyePosition _positionES
|
||||
|
||||
//PROCEDURAL_COMMON_BLOCK
|
||||
|
||||
#line 1001
|
||||
|
|
|
@ -16,7 +16,17 @@
|
|||
|
||||
// the interpolated normal
|
||||
in vec3 _normalWS;
|
||||
in vec3 _normalMS;
|
||||
in vec4 _color;
|
||||
in vec2 _texCoord0;
|
||||
in vec4 _positionMS;
|
||||
in vec4 _positionES;
|
||||
|
||||
// For retro-compatibility
|
||||
#define _normal _normalWS
|
||||
#define _modelNormal _normalMS
|
||||
#define _position _positionMS
|
||||
#define _eyePosition _positionES
|
||||
|
||||
//PROCEDURAL_COMMON_BLOCK
|
||||
|
||||
|
|
77
libraries/shared/src/CrashHelpers.cpp
Normal file
77
libraries/shared/src/CrashHelpers.cpp
Normal file
|
@ -0,0 +1,77 @@
|
|||
//
|
||||
// CrashHelpers.cpp
|
||||
// libraries/shared/src
|
||||
//
|
||||
// Created by Clement Brisset on 4/30/18.
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "CrashHelpers.h"
|
||||
|
||||
namespace crash {
|
||||
|
||||
class B;
|
||||
class A {
|
||||
public:
|
||||
A(B* b) : _b(b) { }
|
||||
~A();
|
||||
virtual void virtualFunction() = 0;
|
||||
|
||||
private:
|
||||
B* _b;
|
||||
};
|
||||
|
||||
class B : public A {
|
||||
public:
|
||||
B() : A(this) { }
|
||||
virtual void virtualFunction() override { }
|
||||
};
|
||||
|
||||
A::~A() {
|
||||
_b->virtualFunction();
|
||||
}
|
||||
|
||||
void pureVirtualCall() {
|
||||
qCDebug(shared) << "About to make a pure virtual call";
|
||||
B b;
|
||||
}
|
||||
|
||||
void doubleFree() {
|
||||
qCDebug(shared) << "About to double delete memory";
|
||||
int* blah = new int(200);
|
||||
delete blah;
|
||||
delete blah;
|
||||
}
|
||||
|
||||
void nullDeref() {
|
||||
qCDebug(shared) << "About to dereference a null pointer";
|
||||
int* p = nullptr;
|
||||
*p = 1;
|
||||
}
|
||||
|
||||
void doAbort() {
|
||||
qCDebug(shared) << "About to abort";
|
||||
abort();
|
||||
}
|
||||
|
||||
void outOfBoundsVectorCrash() {
|
||||
qCDebug(shared) << "std::vector out of bounds crash!";
|
||||
std::vector<int> v;
|
||||
v[0] = 42;
|
||||
}
|
||||
|
||||
void newFault() {
|
||||
qCDebug(shared) << "About to crash inside new fault";
|
||||
|
||||
// Force crash with multiple large allocations
|
||||
while (true) {
|
||||
const size_t GIGABYTE = 1024 * 1024 * 1024;
|
||||
new char[GIGABYTE];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -18,66 +18,12 @@
|
|||
|
||||
namespace crash {
|
||||
|
||||
class B;
|
||||
class A {
|
||||
public:
|
||||
A(B* b) : _b(b) { }
|
||||
~A();
|
||||
virtual void virtualFunction() = 0;
|
||||
|
||||
private:
|
||||
B* _b;
|
||||
};
|
||||
|
||||
class B : public A {
|
||||
public:
|
||||
B() : A(this) { }
|
||||
virtual void virtualFunction() override { }
|
||||
};
|
||||
|
||||
A::~A() {
|
||||
_b->virtualFunction();
|
||||
}
|
||||
|
||||
void pureVirtualCall() {
|
||||
qCDebug(shared) << "About to make a pure virtual call";
|
||||
B b;
|
||||
}
|
||||
|
||||
void doubleFree() {
|
||||
qCDebug(shared) << "About to double delete memory";
|
||||
int* blah = new int(200);
|
||||
delete blah;
|
||||
delete blah;
|
||||
}
|
||||
|
||||
void nullDeref() {
|
||||
qCDebug(shared) << "About to dereference a null pointer";
|
||||
int* p = nullptr;
|
||||
*p = 1;
|
||||
}
|
||||
|
||||
void doAbort() {
|
||||
qCDebug(shared) << "About to abort";
|
||||
abort();
|
||||
}
|
||||
|
||||
void outOfBoundsVectorCrash() {
|
||||
qCDebug(shared) << "std::vector out of bounds crash!";
|
||||
std::vector<int> v;
|
||||
v[0] = 42;
|
||||
}
|
||||
|
||||
void newFault() {
|
||||
qCDebug(shared) << "About to crash inside new fault";
|
||||
|
||||
// Force crash with multiple large allocations
|
||||
while (true) {
|
||||
const size_t GIGABYTE = 1024 * 1024 * 1024;
|
||||
new char[GIGABYTE];
|
||||
}
|
||||
|
||||
}
|
||||
void pureVirtualCall();
|
||||
void doubleFree();
|
||||
void nullDeref();
|
||||
void doAbort();
|
||||
void outOfBoundsVectorCrash();
|
||||
void newFault();
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -76,13 +76,15 @@ glm::quat safeMix(const glm::quat& q1, const glm::quat& q2, float proportion) {
|
|||
|
||||
// Allows sending of fixed-point numbers: radix 1 makes 15.1 number, radix 8 makes 8.8 number, etc
|
||||
int packFloatScalarToSignedTwoByteFixed(unsigned char* buffer, float scalar, int radix) {
|
||||
int16_t outVal = (int16_t)(scalar * (float)(1 << radix));
|
||||
memcpy(buffer, &outVal, sizeof(uint16_t));
|
||||
return sizeof(uint16_t);
|
||||
int16_t twoByteFixed = (int16_t)(scalar * (float)(1 << radix));
|
||||
memcpy(buffer, &twoByteFixed, sizeof(int16_t));
|
||||
return sizeof(int16_t);
|
||||
}
|
||||
|
||||
int unpackFloatScalarFromSignedTwoByteFixed(const int16_t* byteFixedPointer, float* destinationPointer, int radix) {
|
||||
*destinationPointer = *byteFixedPointer / (float)(1 << radix);
|
||||
int16_t twoByteFixed;
|
||||
memcpy(&twoByteFixed, byteFixedPointer, sizeof(int16_t));
|
||||
*destinationPointer = twoByteFixed / (float)(1 << radix);
|
||||
return sizeof(int16_t);
|
||||
}
|
||||
|
||||
|
@ -102,18 +104,19 @@ int unpackFloatVec3FromSignedTwoByteFixed(const unsigned char* sourceBuffer, glm
|
|||
return sourceBuffer - startPosition;
|
||||
}
|
||||
|
||||
|
||||
int packFloatAngleToTwoByte(unsigned char* buffer, float degrees) {
|
||||
const float ANGLE_CONVERSION_RATIO = (std::numeric_limits<uint16_t>::max() / 360.0f);
|
||||
|
||||
uint16_t angleHolder = floorf((degrees + 180.0f) * ANGLE_CONVERSION_RATIO);
|
||||
memcpy(buffer, &angleHolder, sizeof(uint16_t));
|
||||
uint16_t twoByteAngle = floorf((degrees + 180.0f) * ANGLE_CONVERSION_RATIO);
|
||||
memcpy(buffer, &twoByteAngle, sizeof(uint16_t));
|
||||
|
||||
return sizeof(uint16_t);
|
||||
}
|
||||
|
||||
int unpackFloatAngleFromTwoByte(const uint16_t* byteAnglePointer, float* destinationPointer) {
|
||||
*destinationPointer = (*byteAnglePointer / (float) std::numeric_limits<uint16_t>::max()) * 360.0f - 180.0f;
|
||||
uint16_t twoByteAngle;
|
||||
memcpy(&twoByteAngle, byteAnglePointer, sizeof(uint16_t));
|
||||
*destinationPointer = (twoByteAngle / (float) std::numeric_limits<uint16_t>::max()) * 360.0f - 180.0f;
|
||||
return sizeof(uint16_t);
|
||||
}
|
||||
|
||||
|
@ -222,6 +225,12 @@ int unpackOrientationQuatFromSixBytes(const unsigned char* buffer, glm::quat& qu
|
|||
return 6;
|
||||
}
|
||||
|
||||
bool closeEnough(float a, float b, float relativeError) {
|
||||
assert(relativeError >= 0.0f);
|
||||
// NOTE: we add EPSILON to the denominator so we can avoid checking for division by zero.
|
||||
// This method works fine when: fabsf(a + b) >> EPSILON
|
||||
return fabsf(a - b) / (0.5f * fabsf(a + b) + EPSILON) < relativeError;
|
||||
}
|
||||
|
||||
// Safe version of glm::eulerAngles; uses the factorization method described in David Eberly's
|
||||
// http://www.geometrictools.com/Documentation/EulerAngles.pdf (via Clyde,
|
||||
|
|
|
@ -137,6 +137,8 @@ int unpackFloatScalarFromSignedTwoByteFixed(const int16_t* byteFixedPointer, flo
|
|||
int packFloatVec3ToSignedTwoByteFixed(unsigned char* destBuffer, const glm::vec3& srcVector, int radix);
|
||||
int unpackFloatVec3FromSignedTwoByteFixed(const unsigned char* sourceBuffer, glm::vec3& destination, int radix);
|
||||
|
||||
bool closeEnough(float a, float b, float relativeError);
|
||||
|
||||
/// \return vec3 with euler angles in radians
|
||||
glm::vec3 safeEulerAngles(const glm::quat& q);
|
||||
|
||||
|
|
|
@ -48,6 +48,8 @@ const int BYTES_PER_KILOBYTE = 1000;
|
|||
const int BYTES_PER_KILOBIT = BYTES_PER_KILOBYTE / BITS_IN_BYTE;
|
||||
const int KILO_PER_MEGA = 1000;
|
||||
|
||||
const float SQRT_THREE = 1.73205080f;
|
||||
|
||||
#define KB_TO_BYTES_SHIFT 10
|
||||
#define MB_TO_BYTES_SHIFT 20
|
||||
#define GB_TO_BYTES_SHIFT 30
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
#include <queue>
|
||||
|
||||
#include "NumericalConstants.h"
|
||||
#include "ViewFrustum.h"
|
||||
#include "shared/ConicalViewFrustum.h"
|
||||
|
||||
/* PrioritySortUtil is a helper for sorting 3D things relative to a ViewFrustum. To use:
|
||||
|
||||
|
@ -84,14 +84,12 @@ namespace PrioritySortUtil {
|
|||
class PriorityQueue {
|
||||
public:
|
||||
PriorityQueue() = delete;
|
||||
|
||||
PriorityQueue(const ViewFrustum& view) : _view(view) { }
|
||||
|
||||
PriorityQueue(const ViewFrustum& view, float angularWeight, float centerWeight, float ageWeight)
|
||||
: _view(view), _angularWeight(angularWeight), _centerWeight(centerWeight), _ageWeight(ageWeight)
|
||||
PriorityQueue(const ConicalViewFrustums& views) : _views(views) { }
|
||||
PriorityQueue(const ConicalViewFrustums& views, float angularWeight, float centerWeight, float ageWeight)
|
||||
: _views(views), _angularWeight(angularWeight), _centerWeight(centerWeight), _ageWeight(ageWeight)
|
||||
{ }
|
||||
|
||||
void setView(const ViewFrustum& view) { _view = view; }
|
||||
void setViews(const ConicalViewFrustums& views) { _views = views; }
|
||||
|
||||
void setWeights(float angularWeight, float centerWeight, float ageWeight) {
|
||||
_angularWeight = angularWeight;
|
||||
|
@ -109,7 +107,18 @@ namespace PrioritySortUtil {
|
|||
bool empty() const { return _queue.empty(); }
|
||||
|
||||
private:
|
||||
|
||||
float computePriority(const T& thing) const {
|
||||
float priority = std::numeric_limits<float>::min();
|
||||
|
||||
for (const auto& view : _views) {
|
||||
priority = std::max(priority, computePriority(view, thing));
|
||||
}
|
||||
|
||||
return priority;
|
||||
}
|
||||
|
||||
float computePriority(const ConicalViewFrustum& view, const T& thing) const {
|
||||
// priority = weighted linear combination of multiple values:
|
||||
// (a) angular size
|
||||
// (b) proximity to center of view
|
||||
|
@ -117,11 +126,11 @@ namespace PrioritySortUtil {
|
|||
// where the relative "weights" are tuned to scale the contributing values into units of "priority".
|
||||
|
||||
glm::vec3 position = thing.getPosition();
|
||||
glm::vec3 offset = position - _view.getPosition();
|
||||
glm::vec3 offset = position - view.getPosition();
|
||||
float distance = glm::length(offset) + 0.001f; // add 1mm to avoid divide by zero
|
||||
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 cosineAngle = (glm::dot(offset, view.getDirection()) / distance);
|
||||
float age = (float)(usecTimestampNow() - thing.getTimestamp());
|
||||
|
||||
// we modulatate "age" drift rate by the cosineAngle term to make periphrial objects sort forward
|
||||
|
@ -134,8 +143,8 @@ namespace PrioritySortUtil {
|
|||
+ _ageWeight * cosineAngleFactor * age;
|
||||
|
||||
// decrement priority of things outside keyhole
|
||||
if (distance - radius > _view.getCenterRadius()) {
|
||||
if (!_view.sphereIntersectsFrustum(position, radius)) {
|
||||
if (distance - radius > view.getRadius()) {
|
||||
if (!view.intersects(offset, distance, radius)) {
|
||||
constexpr float OUT_OF_VIEW_PENALTY = -10.0f;
|
||||
priority += OUT_OF_VIEW_PENALTY;
|
||||
}
|
||||
|
@ -143,7 +152,7 @@ namespace PrioritySortUtil {
|
|||
return priority;
|
||||
}
|
||||
|
||||
ViewFrustum _view;
|
||||
ConicalViewFrustums _views;
|
||||
std::priority_queue<T> _queue;
|
||||
float _angularWeight { DEFAULT_ANGULAR_COEF };
|
||||
float _centerWeight { DEFAULT_CENTER_COEF };
|
||||
|
|
|
@ -75,6 +75,10 @@ void ViewFrustum::setProjection(const glm::mat4& projection) {
|
|||
_width = _corners[TOP_RIGHT_NEAR].x - _corners[TOP_LEFT_NEAR].x;
|
||||
}
|
||||
|
||||
void ViewFrustum::setProjection(float cameraFov, float cameraAspectRatio, float cameraNearClip, float cameraFarClip) {
|
||||
setProjection(glm::perspective(glm::radians(cameraFov), cameraAspectRatio, cameraNearClip, cameraFarClip));
|
||||
}
|
||||
|
||||
// ViewFrustum::calculate()
|
||||
//
|
||||
// Description: this will calculate the view frustum bounds for a given position and direction
|
||||
|
@ -134,71 +138,6 @@ const char* ViewFrustum::debugPlaneName (int plane) const {
|
|||
return "Unknown";
|
||||
}
|
||||
|
||||
void ViewFrustum::fromByteArray(const QByteArray& input) {
|
||||
|
||||
// From the wire!
|
||||
glm::vec3 cameraPosition;
|
||||
glm::quat cameraOrientation;
|
||||
float cameraCenterRadius;
|
||||
float cameraFov;
|
||||
float cameraAspectRatio;
|
||||
float cameraNearClip;
|
||||
float cameraFarClip;
|
||||
|
||||
const unsigned char* startPosition = reinterpret_cast<const unsigned char*>(input.constData());
|
||||
const unsigned char* sourceBuffer = startPosition;
|
||||
|
||||
// camera details
|
||||
memcpy(&cameraPosition, sourceBuffer, sizeof(cameraPosition));
|
||||
sourceBuffer += sizeof(cameraPosition);
|
||||
sourceBuffer += unpackOrientationQuatFromBytes(sourceBuffer, cameraOrientation);
|
||||
sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*)sourceBuffer, &cameraFov);
|
||||
sourceBuffer += unpackFloatRatioFromTwoByte(sourceBuffer, cameraAspectRatio);
|
||||
sourceBuffer += unpackClipValueFromTwoByte(sourceBuffer, cameraNearClip);
|
||||
sourceBuffer += unpackClipValueFromTwoByte(sourceBuffer, cameraFarClip);
|
||||
memcpy(&cameraCenterRadius, sourceBuffer, sizeof(cameraCenterRadius));
|
||||
sourceBuffer += sizeof(cameraCenterRadius);
|
||||
|
||||
setPosition(cameraPosition);
|
||||
setOrientation(cameraOrientation);
|
||||
setCenterRadius(cameraCenterRadius);
|
||||
|
||||
// Also make sure it's got the correct lens details from the camera
|
||||
if (0.0f != cameraAspectRatio &&
|
||||
0.0f != cameraNearClip &&
|
||||
0.0f != cameraFarClip &&
|
||||
cameraNearClip != cameraFarClip) {
|
||||
setProjection(glm::perspective(
|
||||
glm::radians(cameraFov),
|
||||
cameraAspectRatio,
|
||||
cameraNearClip,
|
||||
cameraFarClip));
|
||||
|
||||
calculate();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
QByteArray ViewFrustum::toByteArray() {
|
||||
static const int LARGE_ENOUGH = 1024;
|
||||
QByteArray viewFrustumDataByteArray(LARGE_ENOUGH, 0);
|
||||
unsigned char* destinationBuffer = reinterpret_cast<unsigned char*>(viewFrustumDataByteArray.data());
|
||||
unsigned char* startPosition = destinationBuffer;
|
||||
|
||||
// camera details
|
||||
memcpy(destinationBuffer, &_position, sizeof(_position));
|
||||
destinationBuffer += sizeof(_position);
|
||||
destinationBuffer += packOrientationQuatToBytes(destinationBuffer, _orientation);
|
||||
destinationBuffer += packFloatAngleToTwoByte(destinationBuffer, _fieldOfView);
|
||||
destinationBuffer += packFloatRatioToTwoByte(destinationBuffer, _aspectRatio);
|
||||
destinationBuffer += packClipValueToTwoByte(destinationBuffer, _nearClip);
|
||||
destinationBuffer += packClipValueToTwoByte(destinationBuffer, _farClip);
|
||||
memcpy(destinationBuffer, &_centerSphereRadius, sizeof(_centerSphereRadius));
|
||||
destinationBuffer += sizeof(_centerSphereRadius);
|
||||
|
||||
return viewFrustumDataByteArray.left(destinationBuffer - startPosition);
|
||||
}
|
||||
|
||||
ViewFrustum::intersection ViewFrustum::calculateCubeFrustumIntersection(const AACube& cube) const {
|
||||
// only check against frustum
|
||||
ViewFrustum::intersection result = INSIDE;
|
||||
|
@ -336,13 +275,6 @@ bool ViewFrustum::boxIntersectsKeyhole(const AABox& box) const {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool closeEnough(float a, float b, float relativeError) {
|
||||
assert(relativeError >= 0.0f);
|
||||
// NOTE: we add EPSILON to the denominator so we can avoid checking for division by zero.
|
||||
// This method works fine when: fabsf(a + b) >> EPSILON
|
||||
return fabsf(a - b) / (0.5f * fabsf(a + b) + EPSILON) < relativeError;
|
||||
}
|
||||
|
||||
// TODO: the slop and relative error should be passed in by argument rather than hard-coded.
|
||||
bool ViewFrustum::isVerySimilar(const ViewFrustum& other) const {
|
||||
const float MIN_POSITION_SLOP_SQUARED = 25.0f; // 5 meters squared
|
||||
|
|
|
@ -48,6 +48,7 @@ public:
|
|||
|
||||
// setters for lens attributes
|
||||
void setProjection(const glm::mat4 & projection);
|
||||
void setProjection(float cameraFov, float cameraAspectRatio, float cameraNearClip, float cameraFarClip);
|
||||
void setFocalLength(float focalLength) { _focalLength = focalLength; }
|
||||
bool isPerspective() const;
|
||||
|
||||
|
@ -146,9 +147,6 @@ public:
|
|||
|
||||
void invalidate(); // causes all reasonable intersection tests to fail
|
||||
|
||||
QByteArray toByteArray();
|
||||
void fromByteArray(const QByteArray& input);
|
||||
|
||||
private:
|
||||
glm::mat4 _view;
|
||||
glm::mat4 _projection;
|
||||
|
@ -188,5 +186,6 @@ private:
|
|||
|
||||
};
|
||||
using ViewFrustumPointer = std::shared_ptr<ViewFrustum>;
|
||||
using ViewFrustums = std::vector<ViewFrustum>;
|
||||
|
||||
#endif // hifi_ViewFrustum_h
|
||||
|
|
146
libraries/shared/src/shared/ConicalViewFrustum.cpp
Normal file
146
libraries/shared/src/shared/ConicalViewFrustum.cpp
Normal file
|
@ -0,0 +1,146 @@
|
|||
//
|
||||
// ConicalViewFrustum.cpp
|
||||
// libraries/shared/src/shared
|
||||
//
|
||||
// Created by Clement Brisset 4/26/18
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "ConicalViewFrustum.h"
|
||||
|
||||
|
||||
#include "../NumericalConstants.h"
|
||||
#include "../ViewFrustum.h"
|
||||
|
||||
void ConicalViewFrustum::set(const ViewFrustum& viewFrustum) {
|
||||
// The ConicalViewFrustum has two parts: a central sphere (same as ViewFrustum) and a circular cone that bounds the frustum part.
|
||||
// Why? Because approximate intersection tests are much faster to compute for a cone than for a frustum.
|
||||
_position = viewFrustum.getPosition();
|
||||
_radius = viewFrustum.getCenterRadius();
|
||||
_farClip = viewFrustum.getFarClip();
|
||||
|
||||
auto topLeft = viewFrustum.getNearTopLeft() - _position;
|
||||
auto topRight = viewFrustum.getNearTopRight() - _position;
|
||||
auto bottomLeft = viewFrustum.getNearBottomLeft() - _position;
|
||||
auto bottomRight = viewFrustum.getNearBottomRight() - _position;
|
||||
auto centerAxis = 0.25f * (topLeft + topRight + bottomLeft + bottomRight); // Take the average
|
||||
|
||||
_direction = glm::normalize(centerAxis);
|
||||
_angle = std::max(std::max(angleBetween(_direction, topLeft),
|
||||
angleBetween(_direction, topRight)),
|
||||
std::max(angleBetween(_direction, bottomLeft),
|
||||
angleBetween(_direction, bottomRight)));
|
||||
}
|
||||
|
||||
void ConicalViewFrustum::calculate() {
|
||||
// Pre-compute cos and sin for faster checks
|
||||
_cosAngle = cosf(_angle);
|
||||
_sinAngle = sqrtf(1.0f - _cosAngle * _cosAngle);
|
||||
}
|
||||
|
||||
bool ConicalViewFrustum::isVerySimilar(const ConicalViewFrustum& other) const {
|
||||
const float MIN_POSITION_SLOP_SQUARED = 25.0f; // 5 meters squared
|
||||
const float MIN_ANGLE_BETWEEN = 0.174533f; // radian angle between 2 vectors 10 degrees apart
|
||||
const float MIN_RELATIVE_ERROR = 0.01f; // 1%
|
||||
|
||||
return glm::distance2(_position, other._position) < MIN_POSITION_SLOP_SQUARED &&
|
||||
angleBetween(_direction, other._direction) < MIN_ANGLE_BETWEEN &&
|
||||
closeEnough(_angle, other._angle, MIN_RELATIVE_ERROR) &&
|
||||
closeEnough(_farClip, other._farClip, MIN_RELATIVE_ERROR) &&
|
||||
closeEnough(_radius, other._radius, MIN_RELATIVE_ERROR);
|
||||
}
|
||||
|
||||
bool ConicalViewFrustum::intersects(const AACube& cube) const {
|
||||
auto radius = 0.5f * SQRT_THREE * cube.getScale(); // radius of bounding sphere
|
||||
auto position = cube.calcCenter() - _position; // position of bounding sphere in view-frame
|
||||
float distance = glm::length(position);
|
||||
|
||||
return intersects(position, distance, radius);
|
||||
}
|
||||
|
||||
bool ConicalViewFrustum::intersects(const AABox& box) const {
|
||||
auto radius = 0.5f * glm::length(box.getScale()); // radius of bounding sphere
|
||||
auto position = box.calcCenter() - _position; // position of bounding sphere in view-frame
|
||||
float distance = glm::length(position);
|
||||
|
||||
return intersects(position, distance, radius);
|
||||
}
|
||||
|
||||
bool ConicalViewFrustum::getAngularSize(const AACube& cube) const {
|
||||
auto radius = 0.5f * SQRT_THREE * cube.getScale(); // radius of bounding sphere
|
||||
auto position = cube.calcCenter() - _position; // position of bounding sphere in view-frame
|
||||
float distance = glm::length(position);
|
||||
|
||||
return getAngularSize(distance, radius);
|
||||
}
|
||||
|
||||
bool ConicalViewFrustum::getAngularSize(const AABox& box) const {
|
||||
auto radius = 0.5f * glm::length(box.getScale()); // radius of bounding sphere
|
||||
auto position = box.calcCenter() - _position; // position of bounding sphere in view-frame
|
||||
float distance = glm::length(position);
|
||||
|
||||
return getAngularSize(distance, radius);
|
||||
}
|
||||
|
||||
|
||||
bool ConicalViewFrustum::intersects(const glm::vec3& relativePosition, float distance, float radius) const {
|
||||
if (distance < _radius + radius) {
|
||||
// Inside keyhole radius
|
||||
return true;
|
||||
}
|
||||
if (distance > _farClip + radius) {
|
||||
// Past far clip
|
||||
return false;
|
||||
}
|
||||
|
||||
// We check the angle between the center of the cube and the _direction of the view.
|
||||
// If it is less than the sum of the half-angle from center of cone to outer edge plus
|
||||
// the half apparent angle of the bounding sphere then it is in view.
|
||||
//
|
||||
// The math here is left as an exercise for the reader with the following hints:
|
||||
// (1) We actually check the dot product of the cube's local position rather than the angle and
|
||||
// (2) we take advantage of this trig identity: cos(A+B) = cos(A)*cos(B) - sin(A)*sin(B)
|
||||
return glm::dot(relativePosition, _direction) >
|
||||
sqrtf(distance * distance - radius * radius) * _cosAngle - radius * _sinAngle;
|
||||
}
|
||||
|
||||
bool ConicalViewFrustum::getAngularSize(float distance, float radius) const {
|
||||
const float AVOID_DIVIDE_BY_ZERO = 0.001f;
|
||||
float angularSize = radius / (distance + AVOID_DIVIDE_BY_ZERO);
|
||||
return angularSize;
|
||||
}
|
||||
|
||||
int ConicalViewFrustum::serialize(unsigned char* destinationBuffer) const {
|
||||
const unsigned char* startPosition = destinationBuffer;
|
||||
|
||||
memcpy(destinationBuffer, &_position, sizeof(_position));
|
||||
destinationBuffer += sizeof(_position);
|
||||
memcpy(destinationBuffer, &_direction, sizeof(_direction));
|
||||
destinationBuffer += sizeof(_direction);
|
||||
destinationBuffer += packFloatAngleToTwoByte(destinationBuffer, _angle);
|
||||
destinationBuffer += packClipValueToTwoByte(destinationBuffer, _farClip);
|
||||
memcpy(destinationBuffer, &_radius, sizeof(_radius));
|
||||
destinationBuffer += sizeof(_radius);
|
||||
|
||||
return destinationBuffer - startPosition;
|
||||
}
|
||||
|
||||
int ConicalViewFrustum::deserialize(const unsigned char* sourceBuffer) {
|
||||
const unsigned char* startPosition = sourceBuffer;
|
||||
|
||||
memcpy(&_position, sourceBuffer, sizeof(_position));
|
||||
sourceBuffer += sizeof(_position);
|
||||
memcpy(&_direction, sourceBuffer, sizeof(_direction));
|
||||
sourceBuffer += sizeof(_direction);
|
||||
sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*)sourceBuffer, &_angle);
|
||||
sourceBuffer += unpackClipValueFromTwoByte(sourceBuffer, _farClip);
|
||||
memcpy(&_radius, sourceBuffer, sizeof(_radius));
|
||||
sourceBuffer += sizeof(_radius);
|
||||
|
||||
calculate();
|
||||
|
||||
return sourceBuffer - startPosition;
|
||||
}
|
70
libraries/shared/src/shared/ConicalViewFrustum.h
Normal file
70
libraries/shared/src/shared/ConicalViewFrustum.h
Normal file
|
@ -0,0 +1,70 @@
|
|||
//
|
||||
// ConicalViewFrustum.h
|
||||
// libraries/shared/src/shared
|
||||
//
|
||||
// Created by Clement Brisset 4/26/18
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_ConicalViewFrustum_h
|
||||
#define hifi_ConicalViewFrustum_h
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
class AACube;
|
||||
class AABox;
|
||||
class ViewFrustum;
|
||||
using ViewFrustums = std::vector<ViewFrustum>;
|
||||
|
||||
const float SQRT_TWO_OVER_TWO = 0.7071067811865f;
|
||||
const float DEFAULT_VIEW_ANGLE = 1.0f;
|
||||
const float DEFAULT_VIEW_RADIUS = 10.0f;
|
||||
const float DEFAULT_VIEW_FAR_CLIP = 100.0f;
|
||||
|
||||
// ConicalViewFrustum is an approximation of a ViewFrustum for fast calculation of sort priority.
|
||||
class ConicalViewFrustum {
|
||||
public:
|
||||
ConicalViewFrustum() = default;
|
||||
ConicalViewFrustum(const ViewFrustum& viewFrustum) { set(viewFrustum); }
|
||||
|
||||
void set(const ViewFrustum& viewFrustum);
|
||||
void calculate();
|
||||
|
||||
const glm::vec3& getPosition() const { return _position; }
|
||||
const glm::vec3& getDirection() const { return _direction; }
|
||||
float getAngle() const { return _angle; }
|
||||
float getRadius() const { return _radius; }
|
||||
float getFarClip() const { return _farClip; }
|
||||
|
||||
bool isVerySimilar(const ConicalViewFrustum& other) const;
|
||||
|
||||
bool intersects(const AACube& cube) const;
|
||||
bool intersects(const AABox& box) const;
|
||||
bool getAngularSize(const AACube& cube) const;
|
||||
bool getAngularSize(const AABox& box) const;
|
||||
|
||||
bool intersects(const glm::vec3& relativePosition, float distance, float radius) const;
|
||||
bool getAngularSize(float distance, float radius) const;
|
||||
|
||||
int serialize(unsigned char* destinationBuffer) const;
|
||||
int deserialize(const unsigned char* sourceBuffer);
|
||||
|
||||
private:
|
||||
glm::vec3 _position { 0.0f, 0.0f, 0.0f };
|
||||
glm::vec3 _direction { 0.0f, 0.0f, 1.0f };
|
||||
float _angle { DEFAULT_VIEW_ANGLE };
|
||||
float _radius { DEFAULT_VIEW_RADIUS };
|
||||
float _farClip { DEFAULT_VIEW_FAR_CLIP };
|
||||
|
||||
float _sinAngle { SQRT_TWO_OVER_TWO };
|
||||
float _cosAngle { SQRT_TWO_OVER_TWO };
|
||||
};
|
||||
using ConicalViewFrustums = std::vector<ConicalViewFrustum>;
|
||||
|
||||
|
||||
#endif /* hifi_ConicalViewFrustum_h */
|
|
@ -36,17 +36,14 @@ protected:
|
|||
private:
|
||||
const FileLogger& _logger;
|
||||
QMutex _fileMutex;
|
||||
uint64_t _lastRollTime;
|
||||
};
|
||||
|
||||
|
||||
|
||||
static const QString FILENAME_FORMAT = "hifi-log_%1%2.txt";
|
||||
static const QString DATETIME_FORMAT = "yyyy-MM-dd_hh.mm.ss";
|
||||
static const QString LOGS_DIRECTORY = "Logs";
|
||||
static const QString IPADDR_WILDCARD = "[0-9]*.[0-9]*.[0-9]*.[0-9]*";
|
||||
static const QString DATETIME_WILDCARD = "20[0-9][0-9]-[0,1][0-9]-[0-3][0-9]_[0-2][0-9].[0-6][0-9].[0-6][0-9]";
|
||||
static const QString FILENAME_WILDCARD = "hifi-log_" + IPADDR_WILDCARD + "_" + DATETIME_WILDCARD + ".txt";
|
||||
static const QString DATETIME_WILDCARD = "20[0-9][0-9]-[01][0-9]-[0-3][0-9]_[0-2][0-9]\\.[0-6][0-9]\\.[0-6][0-9]";
|
||||
static const QString SESSION_WILDCARD = "[0-9a-z]{8}(-[0-9a-z]{4}){3}-[0-9a-z]{12}";
|
||||
static QRegExp LOG_FILENAME_REGEX { "hifi-log_" + DATETIME_WILDCARD + "(_" + SESSION_WILDCARD + ")?\\.txt" };
|
||||
static QUuid SESSION_ID;
|
||||
|
||||
// Max log size is 512 KB. We send log files to our crash reporter, so we want to keep this relatively
|
||||
|
@ -54,8 +51,6 @@ static QUuid SESSION_ID;
|
|||
static const qint64 MAX_LOG_SIZE = 512 * 1024;
|
||||
// Max log files found in the log directory is 100.
|
||||
static const qint64 MAX_LOG_DIR_SIZE = 512 * 1024 * 100;
|
||||
// Max log age is 1 hour
|
||||
static const uint64_t MAX_LOG_AGE_USECS = USECS_PER_SECOND * 3600;
|
||||
|
||||
static FilePersistThread* _persistThreadInstance;
|
||||
|
||||
|
@ -86,38 +81,34 @@ FilePersistThread::FilePersistThread(const FileLogger& logger) : _logger(logger)
|
|||
if (file.exists()) {
|
||||
rollFileIfNecessary(file, false);
|
||||
}
|
||||
_lastRollTime = usecTimestampNow();
|
||||
}
|
||||
|
||||
void FilePersistThread::rollFileIfNecessary(QFile& file, bool notifyListenersIfRolled) {
|
||||
uint64_t now = usecTimestampNow();
|
||||
if ((file.size() > MAX_LOG_SIZE) || (now - _lastRollTime) > MAX_LOG_AGE_USECS) {
|
||||
if (file.size() > MAX_LOG_SIZE) {
|
||||
QString newFileName = getLogRollerFilename();
|
||||
if (file.copy(newFileName)) {
|
||||
file.open(QIODevice::WriteOnly | QIODevice::Truncate);
|
||||
file.close();
|
||||
qCDebug(shared) << "Rolled log file:" << newFileName;
|
||||
|
||||
if (notifyListenersIfRolled) {
|
||||
emit rollingLogFile(newFileName);
|
||||
}
|
||||
|
||||
_lastRollTime = now;
|
||||
}
|
||||
QStringList nameFilters;
|
||||
nameFilters << FILENAME_WILDCARD;
|
||||
|
||||
QDir logQDir(FileUtils::standardPath(LOGS_DIRECTORY));
|
||||
logQDir.setNameFilters(nameFilters);
|
||||
logQDir.setSorting(QDir::Time);
|
||||
QFileInfoList filesInDir = logQDir.entryInfoList();
|
||||
QDir logDir(FileUtils::standardPath(LOGS_DIRECTORY));
|
||||
logDir.setSorting(QDir::Time);
|
||||
logDir.setFilter(QDir::Files);
|
||||
qint64 totalSizeOfDir = 0;
|
||||
foreach(QFileInfo dirItm, filesInDir){
|
||||
if (totalSizeOfDir < MAX_LOG_DIR_SIZE){
|
||||
totalSizeOfDir += dirItm.size();
|
||||
} else {
|
||||
QFile file(dirItm.filePath());
|
||||
file.remove();
|
||||
QFileInfoList filesInDir = logDir.entryInfoList();
|
||||
for (auto& fileInfo : filesInDir) {
|
||||
if (!LOG_FILENAME_REGEX.exactMatch(fileInfo.fileName())) {
|
||||
continue;
|
||||
}
|
||||
totalSizeOfDir += fileInfo.size();
|
||||
if (totalSizeOfDir > MAX_LOG_DIR_SIZE){
|
||||
qDebug() << "Removing log file: " << fileInfo.fileName();
|
||||
QFile oldLogFile(fileInfo.filePath());
|
||||
oldLogFile.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -129,7 +120,7 @@ bool FilePersistThread::processQueueItems(const Queue& messages) {
|
|||
rollFileIfNecessary(file);
|
||||
if (file.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
|
||||
QTextStream out(&file);
|
||||
foreach(const QString& message, messages) {
|
||||
for (const QString& message : messages) {
|
||||
out << message;
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue