mirror of
https://github.com/overte-org/overte.git
synced 2025-08-08 14:18:24 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into orange
This commit is contained in:
commit
11b6952eff
42 changed files with 533 additions and 296 deletions
|
@ -11,7 +11,7 @@
|
||||||
|
|
||||||
#include "AssignmentParentFinder.h"
|
#include "AssignmentParentFinder.h"
|
||||||
|
|
||||||
SpatiallyNestableWeakPointer AssignmentParentFinder::find(QUuid parentID, bool& success) const {
|
SpatiallyNestableWeakPointer AssignmentParentFinder::find(QUuid parentID, bool& success, SpatialParentTree* entityTree) const {
|
||||||
SpatiallyNestableWeakPointer parent;
|
SpatiallyNestableWeakPointer parent;
|
||||||
|
|
||||||
if (parentID.isNull()) {
|
if (parentID.isNull()) {
|
||||||
|
@ -20,7 +20,11 @@ SpatiallyNestableWeakPointer AssignmentParentFinder::find(QUuid parentID, bool&
|
||||||
}
|
}
|
||||||
|
|
||||||
// search entities
|
// search entities
|
||||||
parent = _tree->findEntityByEntityItemID(parentID);
|
if (entityTree) {
|
||||||
|
parent = entityTree->findByID(parentID);
|
||||||
|
} else {
|
||||||
|
parent = _tree->findEntityByEntityItemID(parentID);
|
||||||
|
}
|
||||||
if (parent.expired()) {
|
if (parent.expired()) {
|
||||||
success = false;
|
success = false;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -25,7 +25,7 @@ class AssignmentParentFinder : public SpatialParentFinder {
|
||||||
public:
|
public:
|
||||||
AssignmentParentFinder(EntityTreePointer tree) : _tree(tree) { }
|
AssignmentParentFinder(EntityTreePointer tree) : _tree(tree) { }
|
||||||
virtual ~AssignmentParentFinder() { }
|
virtual ~AssignmentParentFinder() { }
|
||||||
virtual SpatiallyNestableWeakPointer find(QUuid parentID, bool& success) const;
|
virtual SpatiallyNestableWeakPointer find(QUuid parentID, bool& success, SpatialParentTree* entityTree = nullptr) const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
EntityTreePointer _tree;
|
EntityTreePointer _tree;
|
||||||
|
|
|
@ -598,8 +598,18 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
||||||
audioThread->setObjectName("Audio Thread");
|
audioThread->setObjectName("Audio Thread");
|
||||||
|
|
||||||
auto audioIO = DependencyManager::get<AudioClient>();
|
auto audioIO = DependencyManager::get<AudioClient>();
|
||||||
audioIO->setPositionGetter([this]{ return getMyAvatar()->getPositionForAudio(); });
|
audioIO->setPositionGetter([]{
|
||||||
audioIO->setOrientationGetter([this]{ return getMyAvatar()->getOrientationForAudio(); });
|
auto avatarManager = DependencyManager::get<AvatarManager>();
|
||||||
|
auto myAvatar = avatarManager ? avatarManager->getMyAvatar() : nullptr;
|
||||||
|
|
||||||
|
return myAvatar ? myAvatar->getPositionForAudio() : Vectors::ZERO;
|
||||||
|
});
|
||||||
|
audioIO->setOrientationGetter([]{
|
||||||
|
auto avatarManager = DependencyManager::get<AvatarManager>();
|
||||||
|
auto myAvatar = avatarManager ? avatarManager->getMyAvatar() : nullptr;
|
||||||
|
|
||||||
|
return myAvatar ? myAvatar->getOrientationForAudio() : Quaternions::IDENTITY;
|
||||||
|
});
|
||||||
|
|
||||||
audioIO->moveToThread(audioThread);
|
audioIO->moveToThread(audioThread);
|
||||||
recording::Frame::registerFrameHandler(AudioConstants::getAudioFrameName(), [=](recording::Frame::ConstPointer frame) {
|
recording::Frame::registerFrameHandler(AudioConstants::getAudioFrameName(), [=](recording::Frame::ConstPointer frame) {
|
||||||
|
@ -2790,43 +2800,50 @@ void Application::calibrateEyeTracker5Points() {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
bool Application::exportEntities(const QString& filename, const QVector<EntityItemID>& entityIDs) {
|
bool Application::exportEntities(const QString& filename, const QVector<EntityItemID>& entityIDs, const glm::vec3* givenOffset) {
|
||||||
QVector<EntityItemPointer> entities;
|
QHash<EntityItemID, EntityItemPointer> entities;
|
||||||
|
|
||||||
auto entityTree = getEntities()->getTree();
|
auto entityTree = getEntities()->getTree();
|
||||||
auto exportTree = std::make_shared<EntityTree>();
|
auto exportTree = std::make_shared<EntityTree>();
|
||||||
exportTree->createRootElement();
|
exportTree->createRootElement();
|
||||||
|
|
||||||
glm::vec3 root(TREE_SCALE, TREE_SCALE, TREE_SCALE);
|
glm::vec3 root(TREE_SCALE, TREE_SCALE, TREE_SCALE);
|
||||||
for (auto entityID : entityIDs) {
|
for (auto entityID : entityIDs) { // Gather entities and properties.
|
||||||
auto entityItem = entityTree->findEntityByEntityItemID(entityID);
|
auto entityItem = entityTree->findEntityByEntityItemID(entityID);
|
||||||
if (!entityItem) {
|
if (!entityItem) {
|
||||||
|
qCWarning(interfaceapp) << "Skipping export of" << entityID << "that is not in scene.";
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto properties = entityItem->getProperties();
|
if (!givenOffset) {
|
||||||
auto position = properties.getPosition();
|
EntityItemID parentID = entityItem->getParentID();
|
||||||
|
if (parentID.isInvalidID() || !entityIDs.contains(parentID) || !entityTree->findEntityByEntityItemID(parentID)) {
|
||||||
root.x = glm::min(root.x, position.x);
|
auto position = entityItem->getPosition(); // If parent wasn't selected, we want absolute position, which isn't in properties.
|
||||||
root.y = glm::min(root.y, position.y);
|
root.x = glm::min(root.x, position.x);
|
||||||
root.z = glm::min(root.z, position.z);
|
root.y = glm::min(root.y, position.y);
|
||||||
|
root.z = glm::min(root.z, position.z);
|
||||||
entities << entityItem;
|
}
|
||||||
|
}
|
||||||
|
entities[entityID] = entityItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entities.size() == 0) {
|
if (entities.size() == 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto entityItem : entities) {
|
if (givenOffset) {
|
||||||
auto properties = entityItem->getProperties();
|
root = *givenOffset;
|
||||||
|
}
|
||||||
properties.setPosition(properties.getPosition() - root);
|
for (EntityItemPointer& entityDatum : entities) {
|
||||||
exportTree->addEntity(entityItem->getEntityItemID(), properties);
|
auto properties = entityDatum->getProperties();
|
||||||
|
EntityItemID parentID = properties.getParentID();
|
||||||
|
if (parentID.isInvalidID()) {
|
||||||
|
properties.setPosition(properties.getPosition() - root);
|
||||||
|
} else if (!entities.contains(parentID)) {
|
||||||
|
entityDatum->globalizeProperties(properties, "Parent %3 of %2 %1 is not selected for export.", -root);
|
||||||
|
} // else valid parent -- don't offset
|
||||||
|
exportTree->addEntity(entityDatum->getEntityItemID(), properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
// remap IDs on export so that we aren't publishing the IDs of entities in our domain
|
|
||||||
exportTree->remapIDs();
|
|
||||||
|
|
||||||
exportTree->writeToJSONFile(filename.toLocal8Bit().constData());
|
exportTree->writeToJSONFile(filename.toLocal8Bit().constData());
|
||||||
|
|
||||||
|
@ -2836,33 +2853,14 @@ bool Application::exportEntities(const QString& filename, const QVector<EntityIt
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Application::exportEntities(const QString& filename, float x, float y, float z, float scale) {
|
bool Application::exportEntities(const QString& filename, float x, float y, float z, float scale) {
|
||||||
|
glm::vec3 offset(x, y, z);
|
||||||
QVector<EntityItemPointer> entities;
|
QVector<EntityItemPointer> entities;
|
||||||
getEntities()->getTree()->findEntities(AACube(glm::vec3(x, y, z), scale), entities);
|
QVector<EntityItemID> ids;
|
||||||
|
getEntities()->getTree()->findEntities(AACube(offset, scale), entities);
|
||||||
if (entities.size() > 0) {
|
foreach(EntityItemPointer entity, entities) {
|
||||||
glm::vec3 root(x, y, z);
|
ids << entity->getEntityItemID();
|
||||||
auto exportTree = std::make_shared<EntityTree>();
|
|
||||||
exportTree->createRootElement();
|
|
||||||
|
|
||||||
for (int i = 0; i < entities.size(); i++) {
|
|
||||||
EntityItemProperties properties = entities.at(i)->getProperties();
|
|
||||||
EntityItemID id = entities.at(i)->getEntityItemID();
|
|
||||||
properties.setPosition(properties.getPosition() - root);
|
|
||||||
exportTree->addEntity(id, properties);
|
|
||||||
}
|
|
||||||
|
|
||||||
// remap IDs on export so that we aren't publishing the IDs of entities in our domain
|
|
||||||
exportTree->remapIDs();
|
|
||||||
|
|
||||||
exportTree->writeToSVOFile(filename.toLocal8Bit().constData());
|
|
||||||
} else {
|
|
||||||
qCDebug(interfaceapp) << "No models were selected";
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
return exportEntities(filename, ids, &offset);
|
||||||
// restore the main window's active state
|
|
||||||
_window->activateWindow();
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::loadSettings() {
|
void Application::loadSettings() {
|
||||||
|
@ -2895,7 +2893,6 @@ bool Application::importEntities(const QString& urlOrFilename) {
|
||||||
|
|
||||||
bool success = _entityClipboard->readFromURL(urlOrFilename);
|
bool success = _entityClipboard->readFromURL(urlOrFilename);
|
||||||
if (success) {
|
if (success) {
|
||||||
_entityClipboard->remapIDs();
|
|
||||||
_entityClipboard->reaverageOctreeElements();
|
_entityClipboard->reaverageOctreeElements();
|
||||||
}
|
}
|
||||||
return success;
|
return success;
|
||||||
|
@ -2978,6 +2975,11 @@ void Application::updateLOD() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Application::pushPreRenderLambda(void* key, std::function<void()> func) {
|
||||||
|
std::unique_lock<std::mutex> guard(_preRenderLambdasLock);
|
||||||
|
_preRenderLambdas[key] = func;
|
||||||
|
}
|
||||||
|
|
||||||
// Called during Application::update immediately before AvatarManager::updateMyAvatar, updating my data that is then sent to everyone.
|
// Called during Application::update immediately before AvatarManager::updateMyAvatar, updating my data that is then sent to everyone.
|
||||||
// (Maybe this code should be moved there?)
|
// (Maybe this code should be moved there?)
|
||||||
// The principal result is to call updateLookAtTargetAvatar() and then setLookAtPosition().
|
// The principal result is to call updateLookAtTargetAvatar() and then setLookAtPosition().
|
||||||
|
@ -3454,6 +3456,16 @@ void Application::update(float deltaTime) {
|
||||||
QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(), "sendDownstreamAudioStatsPacket", Qt::QueuedConnection);
|
QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(), "sendDownstreamAudioStatsPacket", Qt::QueuedConnection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
PROFILE_RANGE_EX("PreRenderLambdas", 0xffff0000, (uint64_t)0);
|
||||||
|
|
||||||
|
std::unique_lock<std::mutex> guard(_preRenderLambdasLock);
|
||||||
|
for (auto& iter : _preRenderLambdas) {
|
||||||
|
iter.second();
|
||||||
|
}
|
||||||
|
_preRenderLambdas.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -211,6 +211,8 @@ public:
|
||||||
render::EnginePointer getRenderEngine() override { return _renderEngine; }
|
render::EnginePointer getRenderEngine() override { return _renderEngine; }
|
||||||
gpu::ContextPointer getGPUContext() const { return _gpuContext; }
|
gpu::ContextPointer getGPUContext() const { return _gpuContext; }
|
||||||
|
|
||||||
|
virtual void pushPreRenderLambda(void* key, std::function<void()> func) override;
|
||||||
|
|
||||||
const QRect& getMirrorViewRect() const { return _mirrorViewRect; }
|
const QRect& getMirrorViewRect() const { return _mirrorViewRect; }
|
||||||
|
|
||||||
void updateMyAvatarLookAtPosition();
|
void updateMyAvatarLookAtPosition();
|
||||||
|
@ -233,7 +235,7 @@ signals:
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
QVector<EntityItemID> pasteEntities(float x, float y, float z);
|
QVector<EntityItemID> pasteEntities(float x, float y, float z);
|
||||||
bool exportEntities(const QString& filename, const QVector<EntityItemID>& entityIDs);
|
bool exportEntities(const QString& filename, const QVector<EntityItemID>& entityIDs, const glm::vec3* givenOffset = nullptr);
|
||||||
bool exportEntities(const QString& filename, float x, float y, float z, float scale);
|
bool exportEntities(const QString& filename, float x, float y, float z, float scale);
|
||||||
bool importEntities(const QString& url);
|
bool importEntities(const QString& url);
|
||||||
|
|
||||||
|
@ -510,6 +512,9 @@ private:
|
||||||
bool _cursorNeedsChanging { false };
|
bool _cursorNeedsChanging { false };
|
||||||
|
|
||||||
QThread* _deadlockWatchdogThread;
|
QThread* _deadlockWatchdogThread;
|
||||||
|
|
||||||
|
std::map<void*, std::function<void()>> _preRenderLambdas;
|
||||||
|
std::mutex _preRenderLambdasLock;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_Application_h
|
#endif // hifi_Application_h
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
#include "InterfaceParentFinder.h"
|
#include "InterfaceParentFinder.h"
|
||||||
|
|
||||||
SpatiallyNestableWeakPointer InterfaceParentFinder::find(QUuid parentID, bool& success) const {
|
SpatiallyNestableWeakPointer InterfaceParentFinder::find(QUuid parentID, bool& success, SpatialParentTree* entityTree) const {
|
||||||
SpatiallyNestableWeakPointer parent;
|
SpatiallyNestableWeakPointer parent;
|
||||||
|
|
||||||
if (parentID.isNull()) {
|
if (parentID.isNull()) {
|
||||||
|
@ -25,9 +25,13 @@ SpatiallyNestableWeakPointer InterfaceParentFinder::find(QUuid parentID, bool& s
|
||||||
}
|
}
|
||||||
|
|
||||||
// search entities
|
// search entities
|
||||||
EntityTreeRenderer* treeRenderer = qApp->getEntities();
|
if (entityTree) {
|
||||||
EntityTreePointer tree = treeRenderer ? treeRenderer->getTree() : nullptr;
|
parent = entityTree->findByID(parentID);
|
||||||
parent = tree ? tree->findEntityByEntityItemID(parentID) : nullptr;
|
} else {
|
||||||
|
EntityTreeRenderer* treeRenderer = qApp->getEntities();
|
||||||
|
EntityTreePointer tree = treeRenderer ? treeRenderer->getTree() : nullptr;
|
||||||
|
parent = tree ? tree->findEntityByEntityItemID(parentID) : nullptr;
|
||||||
|
}
|
||||||
if (!parent.expired()) {
|
if (!parent.expired()) {
|
||||||
success = true;
|
success = true;
|
||||||
return parent;
|
return parent;
|
||||||
|
|
|
@ -21,7 +21,7 @@ class InterfaceParentFinder : public SpatialParentFinder {
|
||||||
public:
|
public:
|
||||||
InterfaceParentFinder() { }
|
InterfaceParentFinder() { }
|
||||||
virtual ~InterfaceParentFinder() { }
|
virtual ~InterfaceParentFinder() { }
|
||||||
virtual SpatiallyNestableWeakPointer find(QUuid parentID, bool& success) const;
|
virtual SpatiallyNestableWeakPointer find(QUuid parentID, bool& success, SpatialParentTree* entityTree = nullptr) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_InterfaceParentFinder_h
|
#endif // hifi_InterfaceParentFinder_h
|
||||||
|
|
|
@ -18,22 +18,22 @@
|
||||||
QString const ModelOverlay::TYPE = "model";
|
QString const ModelOverlay::TYPE = "model";
|
||||||
|
|
||||||
ModelOverlay::ModelOverlay()
|
ModelOverlay::ModelOverlay()
|
||||||
: _model(std::make_shared<Rig>()),
|
: _model(std::make_shared<Model>(std::make_shared<Rig>())),
|
||||||
_modelTextures(QVariantMap()),
|
_modelTextures(QVariantMap()),
|
||||||
_updateModel(false)
|
_updateModel(false)
|
||||||
{
|
{
|
||||||
_model.init();
|
_model->init();
|
||||||
_isLoaded = false;
|
_isLoaded = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ModelOverlay::ModelOverlay(const ModelOverlay* modelOverlay) :
|
ModelOverlay::ModelOverlay(const ModelOverlay* modelOverlay) :
|
||||||
Volume3DOverlay(modelOverlay),
|
Volume3DOverlay(modelOverlay),
|
||||||
_model(std::make_shared<Rig>()),
|
_model(std::make_shared<Model>(std::make_shared<Rig>())),
|
||||||
_modelTextures(QVariantMap()),
|
_modelTextures(QVariantMap()),
|
||||||
_url(modelOverlay->_url),
|
_url(modelOverlay->_url),
|
||||||
_updateModel(false)
|
_updateModel(false)
|
||||||
{
|
{
|
||||||
_model.init();
|
_model->init();
|
||||||
if (_url.isValid()) {
|
if (_url.isValid()) {
|
||||||
_updateModel = true;
|
_updateModel = true;
|
||||||
_isLoaded = false;
|
_isLoaded = false;
|
||||||
|
@ -44,27 +44,27 @@ void ModelOverlay::update(float deltatime) {
|
||||||
if (_updateModel) {
|
if (_updateModel) {
|
||||||
_updateModel = false;
|
_updateModel = false;
|
||||||
|
|
||||||
_model.setSnapModelToCenter(true);
|
_model->setSnapModelToCenter(true);
|
||||||
_model.setScale(getDimensions());
|
_model->setScale(getDimensions());
|
||||||
_model.setRotation(getRotation());
|
_model->setRotation(getRotation());
|
||||||
_model.setTranslation(getPosition());
|
_model->setTranslation(getPosition());
|
||||||
_model.setURL(_url);
|
_model->setURL(_url);
|
||||||
_model.simulate(deltatime, true);
|
_model->simulate(deltatime, true);
|
||||||
} else {
|
} else {
|
||||||
_model.simulate(deltatime);
|
_model->simulate(deltatime);
|
||||||
}
|
}
|
||||||
_isLoaded = _model.isActive();
|
_isLoaded = _model->isActive();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ModelOverlay::addToScene(Overlay::Pointer overlay, std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges) {
|
bool ModelOverlay::addToScene(Overlay::Pointer overlay, std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges) {
|
||||||
Volume3DOverlay::addToScene(overlay, scene, pendingChanges);
|
Volume3DOverlay::addToScene(overlay, scene, pendingChanges);
|
||||||
_model.addToScene(scene, pendingChanges);
|
_model->addToScene(scene, pendingChanges);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModelOverlay::removeFromScene(Overlay::Pointer overlay, std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges) {
|
void ModelOverlay::removeFromScene(Overlay::Pointer overlay, std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges) {
|
||||||
Volume3DOverlay::removeFromScene(overlay, scene, pendingChanges);
|
Volume3DOverlay::removeFromScene(overlay, scene, pendingChanges);
|
||||||
_model.removeFromScene(scene, pendingChanges);
|
_model->removeFromScene(scene, pendingChanges);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModelOverlay::render(RenderArgs* args) {
|
void ModelOverlay::render(RenderArgs* args) {
|
||||||
|
@ -73,9 +73,9 @@ void ModelOverlay::render(RenderArgs* args) {
|
||||||
// fix them up in the scene
|
// fix them up in the scene
|
||||||
render::ScenePointer scene = qApp->getMain3DScene();
|
render::ScenePointer scene = qApp->getMain3DScene();
|
||||||
render::PendingChanges pendingChanges;
|
render::PendingChanges pendingChanges;
|
||||||
if (_model.needsFixupInScene()) {
|
if (_model->needsFixupInScene()) {
|
||||||
_model.removeFromScene(scene, pendingChanges);
|
_model->removeFromScene(scene, pendingChanges);
|
||||||
_model.addToScene(scene, pendingChanges);
|
_model->addToScene(scene, pendingChanges);
|
||||||
}
|
}
|
||||||
scene->enqueuePendingChanges(pendingChanges);
|
scene->enqueuePendingChanges(pendingChanges);
|
||||||
|
|
||||||
|
@ -100,7 +100,7 @@ void ModelOverlay::setProperties(const QVariantMap& properties) {
|
||||||
if (newScale.x <= 0 || newScale.y <= 0 || newScale.z <= 0) {
|
if (newScale.x <= 0 || newScale.y <= 0 || newScale.z <= 0) {
|
||||||
setDimensions(scale);
|
setDimensions(scale);
|
||||||
} else {
|
} else {
|
||||||
_model.setScaleToFit(true, getDimensions());
|
_model->setScaleToFit(true, getDimensions());
|
||||||
_updateModel = true;
|
_updateModel = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,7 +120,7 @@ void ModelOverlay::setProperties(const QVariantMap& properties) {
|
||||||
QUrl newTextureURL = textureMap[key].toUrl();
|
QUrl newTextureURL = textureMap[key].toUrl();
|
||||||
qDebug() << "Updating texture named" << key << "to texture at URL" << newTextureURL;
|
qDebug() << "Updating texture named" << key << "to texture at URL" << newTextureURL;
|
||||||
|
|
||||||
QMetaObject::invokeMethod(&_model, "setTextureWithNameToURL", Qt::AutoConnection,
|
QMetaObject::invokeMethod(_model.get(), "setTextureWithNameToURL", Qt::AutoConnection,
|
||||||
Q_ARG(const QString&, key),
|
Q_ARG(const QString&, key),
|
||||||
Q_ARG(const QUrl&, newTextureURL));
|
Q_ARG(const QUrl&, newTextureURL));
|
||||||
|
|
||||||
|
@ -134,7 +134,7 @@ QVariant ModelOverlay::getProperty(const QString& property) {
|
||||||
return _url.toString();
|
return _url.toString();
|
||||||
}
|
}
|
||||||
if (property == "dimensions" || property == "scale" || property == "size") {
|
if (property == "dimensions" || property == "scale" || property == "size") {
|
||||||
return vec3toVariant(_model.getScaleToFitDimensions());
|
return vec3toVariant(_model->getScaleToFitDimensions());
|
||||||
}
|
}
|
||||||
if (property == "textures") {
|
if (property == "textures") {
|
||||||
if (_modelTextures.size() > 0) {
|
if (_modelTextures.size() > 0) {
|
||||||
|
@ -155,13 +155,13 @@ bool ModelOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3&
|
||||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal) {
|
float& distance, BoxFace& face, glm::vec3& surfaceNormal) {
|
||||||
|
|
||||||
QString subMeshNameTemp;
|
QString subMeshNameTemp;
|
||||||
return _model.findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, subMeshNameTemp);
|
return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, subMeshNameTemp);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ModelOverlay::findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction,
|
bool ModelOverlay::findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction,
|
||||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, QString& extraInfo) {
|
float& distance, BoxFace& face, glm::vec3& surfaceNormal, QString& extraInfo) {
|
||||||
|
|
||||||
return _model.findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, extraInfo);
|
return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, extraInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
ModelOverlay* ModelOverlay::createClone() const {
|
ModelOverlay* ModelOverlay::createClone() const {
|
||||||
|
|
|
@ -41,11 +41,11 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
Model _model;
|
ModelPointer _model;
|
||||||
QVariantMap _modelTextures;
|
QVariantMap _modelTextures;
|
||||||
|
|
||||||
QUrl _url;
|
QUrl _url;
|
||||||
bool _updateModel;
|
bool _updateModel;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_ModelOverlay_h
|
#endif // hifi_ModelOverlay_h
|
||||||
|
|
|
@ -1056,7 +1056,9 @@ void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm
|
||||||
|
|
||||||
// limit rotation
|
// limit rotation
|
||||||
const float MAX_ANGLE = 30.0f * RADIANS_PER_DEGREE;
|
const float MAX_ANGLE = 30.0f * RADIANS_PER_DEGREE;
|
||||||
deltaQuat = glm::angleAxis(glm::clamp(glm::angle(deltaQuat), -MAX_ANGLE, MAX_ANGLE), glm::axis(deltaQuat));
|
if (fabsf(glm::angle(deltaQuat)) > MAX_ANGLE) {
|
||||||
|
deltaQuat = glm::angleAxis(glm::clamp(glm::angle(deltaQuat), -MAX_ANGLE, MAX_ANGLE), glm::axis(deltaQuat));
|
||||||
|
}
|
||||||
|
|
||||||
// directly set absolutePose rotation
|
// directly set absolutePose rotation
|
||||||
_internalPoseSet._absolutePoses[index].rot = deltaQuat * headQuat;
|
_internalPoseSet._absolutePoses[index].rot = deltaQuat * headQuat;
|
||||||
|
|
|
@ -221,6 +221,8 @@ public:
|
||||||
|
|
||||||
void setEnableInverseKinematics(bool enable);
|
void setEnableInverseKinematics(bool enable);
|
||||||
|
|
||||||
|
const glm::mat4& getGeometryToRigTransform() const { return _geometryToRigTransform; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool isIndexValid(int index) const { return _animSkeleton && index >= 0 && index < _animSkeleton->getNumJoints(); }
|
bool isIndexValid(int index) const { return _animSkeleton && index >= 0 && index < _animSkeleton->getNumJoints(); }
|
||||||
void updateAnimationStateHandlers();
|
void updateAnimationStateHandlers();
|
||||||
|
|
|
@ -50,6 +50,9 @@
|
||||||
|
|
||||||
static const int RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES = 100;
|
static const int RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES = 100;
|
||||||
|
|
||||||
|
static const auto DEFAULT_POSITION_GETTER = []{ return Vectors::ZERO; };
|
||||||
|
static const auto DEFAULT_ORIENTATION_GETTER = [] { return Quaternions::IDENTITY; };
|
||||||
|
|
||||||
Setting::Handle<bool> dynamicJitterBuffers("dynamicJitterBuffers", DEFAULT_DYNAMIC_JITTER_BUFFERS);
|
Setting::Handle<bool> dynamicJitterBuffers("dynamicJitterBuffers", DEFAULT_DYNAMIC_JITTER_BUFFERS);
|
||||||
Setting::Handle<int> maxFramesOverDesired("maxFramesOverDesired", DEFAULT_MAX_FRAMES_OVER_DESIRED);
|
Setting::Handle<int> maxFramesOverDesired("maxFramesOverDesired", DEFAULT_MAX_FRAMES_OVER_DESIRED);
|
||||||
Setting::Handle<int> staticDesiredJitterBufferFrames("staticDesiredJitterBufferFrames",
|
Setting::Handle<int> staticDesiredJitterBufferFrames("staticDesiredJitterBufferFrames",
|
||||||
|
@ -103,7 +106,9 @@ AudioClient::AudioClient() :
|
||||||
_outgoingAvatarAudioSequenceNumber(0),
|
_outgoingAvatarAudioSequenceNumber(0),
|
||||||
_audioOutputIODevice(_receivedAudioStream, this),
|
_audioOutputIODevice(_receivedAudioStream, this),
|
||||||
_stats(&_receivedAudioStream),
|
_stats(&_receivedAudioStream),
|
||||||
_inputGate()
|
_inputGate(),
|
||||||
|
_positionGetter(DEFAULT_POSITION_GETTER),
|
||||||
|
_orientationGetter(DEFAULT_ORIENTATION_GETTER)
|
||||||
{
|
{
|
||||||
// clear the array of locally injected samples
|
// clear the array of locally injected samples
|
||||||
memset(_localProceduralSamples, 0, AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL);
|
memset(_localProceduralSamples, 0, AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL);
|
||||||
|
|
|
@ -460,14 +460,17 @@ void EntityTreeRenderer::processEraseMessage(ReceivedMessage& message, const Sha
|
||||||
|
|
||||||
ModelPointer EntityTreeRenderer::allocateModel(const QString& url, const QString& collisionUrl) {
|
ModelPointer EntityTreeRenderer::allocateModel(const QString& url, const QString& collisionUrl) {
|
||||||
ModelPointer model = nullptr;
|
ModelPointer model = nullptr;
|
||||||
// Make sure we only create and delete models on the thread that owns the EntityTreeRenderer
|
|
||||||
|
// Only create and delete models on the thread that owns the EntityTreeRenderer
|
||||||
if (QThread::currentThread() != thread()) {
|
if (QThread::currentThread() != thread()) {
|
||||||
QMetaObject::invokeMethod(this, "allocateModel", Qt::BlockingQueuedConnection,
|
QMetaObject::invokeMethod(this, "allocateModel", Qt::BlockingQueuedConnection,
|
||||||
Q_RETURN_ARG(ModelPointer, model),
|
Q_RETURN_ARG(ModelPointer, model),
|
||||||
Q_ARG(const QString&, url));
|
Q_ARG(const QString&, url),
|
||||||
|
Q_ARG(const QString&, collisionUrl));
|
||||||
|
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
model = std::make_shared<Model>(std::make_shared<Rig>());
|
model = std::make_shared<Model>(std::make_shared<Rig>());
|
||||||
model->init();
|
model->init();
|
||||||
model->setURL(QUrl(url));
|
model->setURL(QUrl(url));
|
||||||
|
@ -475,37 +478,20 @@ ModelPointer EntityTreeRenderer::allocateModel(const QString& url, const QString
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
ModelPointer EntityTreeRenderer::updateModel(ModelPointer original, const QString& newUrl, const QString& collisionUrl) {
|
ModelPointer EntityTreeRenderer::updateModel(ModelPointer model, const QString& newUrl, const QString& collisionUrl) {
|
||||||
ModelPointer model = nullptr;
|
// Only create and delete models on the thread that owns the EntityTreeRenderer
|
||||||
|
|
||||||
// The caller shouldn't call us if the URL doesn't need to change. But if they
|
|
||||||
// do, we just return their original back to them.
|
|
||||||
if (!original || (QUrl(newUrl) == original->getURL())) {
|
|
||||||
return original;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Before we do any creating or deleting, make sure we're on our renderer thread
|
|
||||||
if (QThread::currentThread() != thread()) {
|
if (QThread::currentThread() != thread()) {
|
||||||
QMetaObject::invokeMethod(this, "updateModel", Qt::BlockingQueuedConnection,
|
QMetaObject::invokeMethod(this, "updateModel", Qt::BlockingQueuedConnection,
|
||||||
Q_RETURN_ARG(ModelPointer, model),
|
Q_RETURN_ARG(ModelPointer, model),
|
||||||
Q_ARG(ModelPointer, original),
|
Q_ARG(ModelPointer, model),
|
||||||
Q_ARG(const QString&, newUrl));
|
Q_ARG(const QString&, newUrl),
|
||||||
|
Q_ARG(const QString&, collisionUrl));
|
||||||
|
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
// at this point we know we need to replace the model, and we know we're on the
|
|
||||||
// correct thread, so we can do all our work.
|
|
||||||
if (original) {
|
|
||||||
original.reset(); // delete the old model...
|
|
||||||
}
|
|
||||||
|
|
||||||
// create the model and correctly initialize it with the new url
|
|
||||||
model = std::make_shared<Model>(std::make_shared<Rig>());
|
|
||||||
model->init();
|
|
||||||
model->setURL(QUrl(newUrl));
|
model->setURL(QUrl(newUrl));
|
||||||
model->setCollisionModelURL(QUrl(collisionUrl));
|
model->setCollisionModelURL(QUrl(collisionUrl));
|
||||||
|
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -69,14 +69,10 @@ void RenderableModelEntityItem::loader() {
|
||||||
_needsModelReload = true;
|
_needsModelReload = true;
|
||||||
EntityTreeRenderer* renderer = DependencyManager::get<EntityTreeRenderer>().data();
|
EntityTreeRenderer* renderer = DependencyManager::get<EntityTreeRenderer>().data();
|
||||||
assert(renderer);
|
assert(renderer);
|
||||||
if (!_model || _needsModelReload) {
|
{
|
||||||
PerformanceTimer perfTimer("getModel");
|
PerformanceTimer perfTimer("getModel");
|
||||||
getModel(renderer);
|
getModel(renderer);
|
||||||
}
|
}
|
||||||
if (_model) {
|
|
||||||
_model->setURL(getParsedModelURL());
|
|
||||||
_model->setCollisionModelURL(QUrl(getCompoundShapeURL()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderableModelEntityItem::setDimensions(const glm::vec3& value) {
|
void RenderableModelEntityItem::setDimensions(const glm::vec3& value) {
|
||||||
|
@ -109,7 +105,7 @@ int RenderableModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned c
|
||||||
|
|
||||||
QVariantMap RenderableModelEntityItem::parseTexturesToMap(QString textures) {
|
QVariantMap RenderableModelEntityItem::parseTexturesToMap(QString textures) {
|
||||||
// If textures are unset, revert to original textures
|
// If textures are unset, revert to original textures
|
||||||
if (textures == "") {
|
if (textures.isEmpty()) {
|
||||||
return _originalTextures;
|
return _originalTextures;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,7 +120,9 @@ QVariantMap RenderableModelEntityItem::parseTexturesToMap(QString textures) {
|
||||||
qCWarning(entitiesrenderer) << "Could not evaluate textures property value:" << _textures;
|
qCWarning(entitiesrenderer) << "Could not evaluate textures property value:" << _textures;
|
||||||
return _originalTextures;
|
return _originalTextures;
|
||||||
}
|
}
|
||||||
return texturesJson.object().toVariantMap();
|
|
||||||
|
auto parsed = texturesJson.toVariant();
|
||||||
|
return parsed.toMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderableModelEntityItem::remapTextures() {
|
void RenderableModelEntityItem::remapTextures() {
|
||||||
|
@ -364,13 +362,6 @@ void RenderableModelEntityItem::render(RenderArgs* args) {
|
||||||
|
|
||||||
if (hasModel()) {
|
if (hasModel()) {
|
||||||
if (_model) {
|
if (_model) {
|
||||||
// check if the URL has changed
|
|
||||||
auto& currentURL = getParsedModelURL();
|
|
||||||
if (currentURL != _model->getURL()) {
|
|
||||||
qCDebug(entitiesrenderer).noquote() << "Updating model URL: " << currentURL.toDisplayString();
|
|
||||||
_model->setURL(currentURL);
|
|
||||||
}
|
|
||||||
|
|
||||||
render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene();
|
render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene();
|
||||||
|
|
||||||
// check to see if when we added our models to the scene they were ready, if they were not ready, then
|
// check to see if when we added our models to the scene they were ready, if they were not ready, then
|
||||||
|
@ -435,6 +426,15 @@ void RenderableModelEntityItem::render(RenderArgs* args) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
updateModelBounds();
|
updateModelBounds();
|
||||||
|
|
||||||
|
// Check if the URL has changed
|
||||||
|
// Do this last as the getModel is queued for the next frame,
|
||||||
|
// and we need to keep state directing the model to reinitialize
|
||||||
|
auto& currentURL = getParsedModelURL();
|
||||||
|
if (currentURL != _model->getURL()) {
|
||||||
|
// Defer setting the url to the render thread
|
||||||
|
getModel(_myRenderer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -450,10 +450,8 @@ void RenderableModelEntityItem::render(RenderArgs* args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ModelPointer RenderableModelEntityItem::getModel(EntityTreeRenderer* renderer) {
|
ModelPointer RenderableModelEntityItem::getModel(EntityTreeRenderer* renderer) {
|
||||||
ModelPointer result = nullptr;
|
|
||||||
|
|
||||||
if (!renderer) {
|
if (!renderer) {
|
||||||
return result;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure our renderer is setup
|
// make sure our renderer is setup
|
||||||
|
@ -468,21 +466,22 @@ ModelPointer RenderableModelEntityItem::getModel(EntityTreeRenderer* renderer) {
|
||||||
|
|
||||||
_needsModelReload = false; // this is the reload
|
_needsModelReload = false; // this is the reload
|
||||||
|
|
||||||
// if we have a URL, then we will want to end up returning a model...
|
// If we have a URL, then we will want to end up returning a model...
|
||||||
if (!getModelURL().isEmpty()) {
|
if (!getModelURL().isEmpty()) {
|
||||||
|
// If we don't have a model, allocate one *immediately*
|
||||||
// if we have a previously allocated model, but its URL doesn't match
|
if (!_model) {
|
||||||
// then we need to let our renderer update our model for us.
|
_model = _myRenderer->allocateModel(getModelURL(), getCompoundShapeURL());
|
||||||
if (_model && (QUrl(getModelURL()) != _model->getURL() ||
|
|
||||||
QUrl(getCompoundShapeURL()) != _model->getCollisionURL())) {
|
|
||||||
result = _model = _myRenderer->updateModel(_model, getModelURL(), getCompoundShapeURL());
|
|
||||||
_needsInitialSimulation = true;
|
_needsInitialSimulation = true;
|
||||||
} else if (!_model) { // if we don't yet have a model, then we want our renderer to allocate one
|
// If we need to change URLs, update it *after rendering* (to avoid access violations)
|
||||||
result = _model = _myRenderer->allocateModel(getModelURL(), getCompoundShapeURL());
|
} else if ((QUrl(getModelURL()) != _model->getURL() || QUrl(getCompoundShapeURL()) != _model->getCollisionURL())) {
|
||||||
|
QMetaObject::invokeMethod(_myRenderer, "updateModel", Qt::QueuedConnection,
|
||||||
|
Q_ARG(ModelPointer, _model),
|
||||||
|
Q_ARG(const QString&, getModelURL()),
|
||||||
|
Q_ARG(const QString&, getCompoundShapeURL()));
|
||||||
_needsInitialSimulation = true;
|
_needsInitialSimulation = true;
|
||||||
} else { // we already have the model we want...
|
|
||||||
result = _model;
|
|
||||||
}
|
}
|
||||||
|
// Else we can just return the _model
|
||||||
|
// If we have no URL, then we can delete any model we do have...
|
||||||
} else if (_model) {
|
} else if (_model) {
|
||||||
// remove from scene
|
// remove from scene
|
||||||
render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene();
|
render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene();
|
||||||
|
@ -492,11 +491,11 @@ ModelPointer RenderableModelEntityItem::getModel(EntityTreeRenderer* renderer) {
|
||||||
|
|
||||||
// release interest
|
// release interest
|
||||||
_myRenderer->releaseModel(_model);
|
_myRenderer->releaseModel(_model);
|
||||||
result = _model = nullptr;
|
_model = nullptr;
|
||||||
_needsInitialSimulation = true;
|
_needsInitialSimulation = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return _model;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RenderableModelEntityItem::needsToCallUpdate() const {
|
bool RenderableModelEntityItem::needsToCallUpdate() const {
|
||||||
|
|
|
@ -55,8 +55,8 @@ bool RenderableWebEntityItem::buildWebSurface(EntityTreeRenderer* renderer) {
|
||||||
qWarning() << "Too many concurrent web views to create new view";
|
qWarning() << "Too many concurrent web views to create new view";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
qDebug() << "Building web surface";
|
qDebug() << "Building web surface";
|
||||||
|
|
||||||
++_currentWebCount;
|
++_currentWebCount;
|
||||||
// Save the original GL context, because creating a QML surface will create a new context
|
// Save the original GL context, because creating a QML surface will create a new context
|
||||||
QOpenGLContext * currentContext = QOpenGLContext::currentContext();
|
QOpenGLContext * currentContext = QOpenGLContext::currentContext();
|
||||||
|
|
|
@ -1010,6 +1010,10 @@ EntityTreePointer EntityItem::getTree() const {
|
||||||
return tree;
|
return tree;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SpatialParentTree* EntityItem::getParentTree() const {
|
||||||
|
return getTree().get();
|
||||||
|
}
|
||||||
|
|
||||||
bool EntityItem::wantTerseEditLogging() const {
|
bool EntityItem::wantTerseEditLogging() const {
|
||||||
EntityTreePointer tree = getTree();
|
EntityTreePointer tree = getTree();
|
||||||
return tree ? tree->wantTerseEditLogging() : false;
|
return tree ? tree->wantTerseEditLogging() : false;
|
||||||
|
@ -1978,3 +1982,25 @@ void EntityItem::dimensionsChanged() {
|
||||||
requiresRecalcBoxes();
|
requiresRecalcBoxes();
|
||||||
SpatiallyNestable::dimensionsChanged(); // Do what you have to do
|
SpatiallyNestable::dimensionsChanged(); // Do what you have to do
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EntityItem::globalizeProperties(EntityItemProperties& properties, const QString& messageTemplate, const glm::vec3& offset) const {
|
||||||
|
bool success;
|
||||||
|
auto globalPosition = getPosition(success);
|
||||||
|
if (success) {
|
||||||
|
properties.setPosition(globalPosition + offset);
|
||||||
|
properties.setRotation(getRotation());
|
||||||
|
properties.setDimensions(getDimensions());
|
||||||
|
// Should we do velocities and accelerations, too? This could end up being quite involved, which is why the method exists.
|
||||||
|
} else {
|
||||||
|
properties.setPosition(getQueryAACube().calcCenter() + offset); // best we can do
|
||||||
|
}
|
||||||
|
if (!messageTemplate.isEmpty()) {
|
||||||
|
QString name = properties.getName();
|
||||||
|
if (name.isEmpty()) {
|
||||||
|
name = EntityTypes::getEntityTypeName(properties.getType());
|
||||||
|
}
|
||||||
|
qCWarning(entities) << messageTemplate.arg(getEntityItemID().toString()).arg(name).arg(properties.getParentID().toString());
|
||||||
|
}
|
||||||
|
QUuid empty;
|
||||||
|
properties.setParentID(empty);
|
||||||
|
}
|
||||||
|
|
|
@ -86,6 +86,8 @@ public:
|
||||||
|
|
||||||
/// returns true if something changed
|
/// returns true if something changed
|
||||||
virtual bool setProperties(const EntityItemProperties& properties);
|
virtual bool setProperties(const EntityItemProperties& properties);
|
||||||
|
// Update properties with empty parent id and globalized/absolute values (applying offset), and apply (non-empty) log template to args id, name-or-type, parent id.
|
||||||
|
void globalizeProperties(EntityItemProperties& properties, const QString& messageTemplate = QString(), const glm::vec3& offset = glm::vec3(0.0f)) const;
|
||||||
|
|
||||||
/// Override this in your derived class if you'd like to be informed when something about the state of the entity
|
/// Override this in your derived class if you'd like to be informed when something about the state of the entity
|
||||||
/// has changed. This will be called with properties change or when new data is loaded from a stream
|
/// has changed. This will be called with properties change or when new data is loaded from a stream
|
||||||
|
@ -359,6 +361,7 @@ public:
|
||||||
void setPhysicsInfo(void* data) { _physicsInfo = data; }
|
void setPhysicsInfo(void* data) { _physicsInfo = data; }
|
||||||
EntityTreeElementPointer getElement() const { return _element; }
|
EntityTreeElementPointer getElement() const { return _element; }
|
||||||
EntityTreePointer getTree() const;
|
EntityTreePointer getTree() const;
|
||||||
|
virtual SpatialParentTree* getParentTree() const;
|
||||||
bool wantTerseEditLogging() const;
|
bool wantTerseEditLogging() const;
|
||||||
|
|
||||||
glm::mat4 getEntityToWorldMatrix() const;
|
glm::mat4 getEntityToWorldMatrix() const;
|
||||||
|
|
|
@ -24,7 +24,6 @@
|
||||||
#include "EntitiesLogging.h"
|
#include "EntitiesLogging.h"
|
||||||
#include "RecurseOctreeToMapOperator.h"
|
#include "RecurseOctreeToMapOperator.h"
|
||||||
#include "LogHandler.h"
|
#include "LogHandler.h"
|
||||||
#include "RemapIDOperator.h"
|
|
||||||
|
|
||||||
static const quint64 DELETED_ENTITIES_EXTRA_USECS_TO_CONSIDER = USECS_PER_MSEC * 50;
|
static const quint64 DELETED_ENTITIES_EXTRA_USECS_TO_CONSIDER = USECS_PER_MSEC * 50;
|
||||||
|
|
||||||
|
@ -1317,42 +1316,59 @@ QVector<EntityItemID> EntityTree::sendEntities(EntityEditPacketSender* packetSen
|
||||||
float x, float y, float z) {
|
float x, float y, float z) {
|
||||||
SendEntitiesOperationArgs args;
|
SendEntitiesOperationArgs args;
|
||||||
args.packetSender = packetSender;
|
args.packetSender = packetSender;
|
||||||
args.localTree = localTree;
|
args.ourTree = this;
|
||||||
|
args.otherTree = localTree;
|
||||||
args.root = glm::vec3(x, y, z);
|
args.root = glm::vec3(x, y, z);
|
||||||
QVector<EntityItemID> newEntityIDs;
|
// If this is called repeatedly (e.g., multiple pastes with the same data), the new elements will clash unless we use new identifiers.
|
||||||
args.newEntityIDs = &newEntityIDs;
|
// We need to keep a map so that we can map parent identifiers correctly.
|
||||||
|
QHash<EntityItemID, EntityItemID> map;
|
||||||
|
args.map = ↦
|
||||||
recurseTreeWithOperation(sendEntitiesOperation, &args);
|
recurseTreeWithOperation(sendEntitiesOperation, &args);
|
||||||
packetSender->releaseQueuedMessages();
|
packetSender->releaseQueuedMessages();
|
||||||
|
|
||||||
return newEntityIDs;
|
return map.values().toVector();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EntityTree::sendEntitiesOperation(OctreeElementPointer element, void* extraData) {
|
bool EntityTree::sendEntitiesOperation(OctreeElementPointer element, void* extraData) {
|
||||||
SendEntitiesOperationArgs* args = static_cast<SendEntitiesOperationArgs*>(extraData);
|
SendEntitiesOperationArgs* args = static_cast<SendEntitiesOperationArgs*>(extraData);
|
||||||
EntityTreeElementPointer entityTreeElement = std::static_pointer_cast<EntityTreeElement>(element);
|
EntityTreeElementPointer entityTreeElement = std::static_pointer_cast<EntityTreeElement>(element);
|
||||||
entityTreeElement->forEachEntity([&](EntityItemPointer entityItem) {
|
std::function<const EntityItemID(EntityItemPointer&)> getMapped = [&](EntityItemPointer& item) -> const EntityItemID {
|
||||||
EntityItemID newID(QUuid::createUuid());
|
EntityItemID oldID = item->getEntityItemID();
|
||||||
args->newEntityIDs->append(newID);
|
if (args->map->contains(oldID)) { // Already been handled (e.g., as a parent of somebody that we've processed).
|
||||||
EntityItemProperties properties = entityItem->getProperties();
|
return args->map->value(oldID);
|
||||||
properties.setPosition(properties.getPosition() + args->root);
|
}
|
||||||
|
EntityItemID newID = QUuid::createUuid();
|
||||||
|
EntityItemProperties properties = item->getProperties();
|
||||||
|
EntityItemID oldParentID = properties.getParentID();
|
||||||
|
if (oldParentID.isInvalidID()) { // no parent
|
||||||
|
properties.setPosition(properties.getPosition() + args->root);
|
||||||
|
} else {
|
||||||
|
EntityItemPointer parentEntity = args->ourTree->findEntityByEntityItemID(oldParentID);
|
||||||
|
if (parentEntity) { // map the parent
|
||||||
|
// Warning: (non-tail) recursion of getMapped could blow the call stack if the parent hierarchy is VERY deep.
|
||||||
|
properties.setParentID(getMapped(parentEntity));
|
||||||
|
// But do not add root offset in this case.
|
||||||
|
} else { // Should not happen, but let's try to be helpful...
|
||||||
|
item->globalizeProperties(properties, "Cannot find %3 parent of %2 %1", args->root);
|
||||||
|
}
|
||||||
|
}
|
||||||
properties.markAllChanged(); // so the entire property set is considered new, since we're making a new entity
|
properties.markAllChanged(); // so the entire property set is considered new, since we're making a new entity
|
||||||
|
|
||||||
// queue the packet to send to the server
|
// queue the packet to send to the server
|
||||||
args->packetSender->queueEditEntityMessage(PacketType::EntityAdd, newID, properties);
|
args->packetSender->queueEditEntityMessage(PacketType::EntityAdd, newID, properties);
|
||||||
|
|
||||||
// also update the local tree instantly (note: this is not our tree, but an alternate tree)
|
// also update the local tree instantly (note: this is not our tree, but an alternate tree)
|
||||||
if (args->localTree) {
|
if (args->otherTree) {
|
||||||
args->localTree->withWriteLock([&] {
|
args->otherTree->withWriteLock([&] {
|
||||||
args->localTree->addEntity(newID, properties);
|
args->otherTree->addEntity(newID, properties);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
args->map->insert(oldID, newID);
|
||||||
return true;
|
return newID;
|
||||||
}
|
};
|
||||||
|
|
||||||
void EntityTree::remapIDs() {
|
entityTreeElement->forEachEntity(getMapped);
|
||||||
RemapIDOperator theOperator;
|
return true;
|
||||||
recurseTreeWithOperator(&theOperator);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EntityTree::writeToMap(QVariantMap& entityDescription, OctreeElementPointer element, bool skipDefaultValues,
|
bool EntityTree::writeToMap(QVariantMap& entityDescription, OctreeElementPointer element, bool skipDefaultValues,
|
||||||
|
@ -1393,7 +1409,6 @@ bool EntityTree::readFromMap(QVariantMap& map) {
|
||||||
qCDebug(entities) << "adding Entity failed:" << entityItemID << properties.getType();
|
qCDebug(entities) << "adding Entity failed:" << entityItemID << properties.getType();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
#include <QVector>
|
#include <QVector>
|
||||||
|
|
||||||
#include <Octree.h>
|
#include <Octree.h>
|
||||||
|
#include <SpatialParentFinder.h>
|
||||||
|
|
||||||
class EntityTree;
|
class EntityTree;
|
||||||
typedef std::shared_ptr<EntityTree> EntityTreePointer;
|
typedef std::shared_ptr<EntityTree> EntityTreePointer;
|
||||||
|
@ -46,13 +47,14 @@ public:
|
||||||
class SendEntitiesOperationArgs {
|
class SendEntitiesOperationArgs {
|
||||||
public:
|
public:
|
||||||
glm::vec3 root;
|
glm::vec3 root;
|
||||||
EntityTreePointer localTree;
|
EntityTree* ourTree;
|
||||||
|
EntityTreePointer otherTree;
|
||||||
EntityEditPacketSender* packetSender;
|
EntityEditPacketSender* packetSender;
|
||||||
QVector<EntityItemID>* newEntityIDs;
|
QHash<EntityItemID, EntityItemID>* map;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
class EntityTree : public Octree {
|
class EntityTree : public Octree, public SpatialParentTree {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
EntityTree(bool shouldReaverage = false);
|
EntityTree(bool shouldReaverage = false);
|
||||||
|
@ -125,6 +127,7 @@ public:
|
||||||
EntityItemPointer findClosestEntity(glm::vec3 position, float targetRadius);
|
EntityItemPointer findClosestEntity(glm::vec3 position, float targetRadius);
|
||||||
EntityItemPointer findEntityByID(const QUuid& id);
|
EntityItemPointer findEntityByID(const QUuid& id);
|
||||||
EntityItemPointer findEntityByEntityItemID(const EntityItemID& entityID);
|
EntityItemPointer findEntityByEntityItemID(const EntityItemID& entityID);
|
||||||
|
virtual SpatiallyNestablePointer findByID(const QUuid& id) { return findEntityByID(id); }
|
||||||
|
|
||||||
EntityItemID assignEntityID(const EntityItemID& entityItemID); /// Assigns a known ID for a creator token ID
|
EntityItemID assignEntityID(const EntityItemID& entityItemID); /// Assigns a known ID for a creator token ID
|
||||||
|
|
||||||
|
@ -200,8 +203,6 @@ public:
|
||||||
bool wantTerseEditLogging() const { return _wantTerseEditLogging; }
|
bool wantTerseEditLogging() const { return _wantTerseEditLogging; }
|
||||||
void setWantTerseEditLogging(bool value) { _wantTerseEditLogging = value; }
|
void setWantTerseEditLogging(bool value) { _wantTerseEditLogging = value; }
|
||||||
|
|
||||||
void remapIDs();
|
|
||||||
|
|
||||||
virtual bool writeToMap(QVariantMap& entityDescription, OctreeElementPointer element, bool skipDefaultValues,
|
virtual bool writeToMap(QVariantMap& entityDescription, OctreeElementPointer element, bool skipDefaultValues,
|
||||||
bool skipThoseWithBadParents) override;
|
bool skipThoseWithBadParents) override;
|
||||||
virtual bool readFromMap(QVariantMap& entityDescription) override;
|
virtual bool readFromMap(QVariantMap& entityDescription) override;
|
||||||
|
|
|
@ -561,6 +561,12 @@ bool ParticleEffectEntityItem::needsToCallUpdate() const {
|
||||||
|
|
||||||
void ParticleEffectEntityItem::update(const quint64& now) {
|
void ParticleEffectEntityItem::update(const quint64& now) {
|
||||||
|
|
||||||
|
// we check for 'now' in the past in case users set their clock backward
|
||||||
|
if (now < _lastSimulated) {
|
||||||
|
_lastSimulated = now;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
float deltaTime = (float)(now - _lastSimulated) / (float)USECS_PER_SECOND;
|
float deltaTime = (float)(now - _lastSimulated) / (float)USECS_PER_SECOND;
|
||||||
_lastSimulated = now;
|
_lastSimulated = now;
|
||||||
|
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
//
|
|
||||||
// RemapIDOperator.cpp
|
|
||||||
// libraries/entities/src
|
|
||||||
//
|
|
||||||
// Created by Seth Alves on 2015-12-6.
|
|
||||||
// Copyright 2015 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 "EntityTree.h"
|
|
||||||
#include "RemapIDOperator.h"
|
|
||||||
|
|
||||||
QUuid RemapIDOperator::remap(const QUuid& oldID) {
|
|
||||||
if (oldID.isNull()) {
|
|
||||||
return oldID;
|
|
||||||
}
|
|
||||||
if (!_oldToNew.contains(oldID)) {
|
|
||||||
_oldToNew[oldID] = QUuid::createUuid();
|
|
||||||
}
|
|
||||||
return _oldToNew[oldID];
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RemapIDOperator::postRecursion(OctreeElementPointer element) {
|
|
||||||
EntityTreeElementPointer entityTreeElement = std::static_pointer_cast<EntityTreeElement>(element);
|
|
||||||
entityTreeElement->forEachEntity([&](EntityItemPointer entityItem) {
|
|
||||||
entityItem->setID(remap(entityItem->getID()));
|
|
||||||
entityItem->setParentID(remap(entityItem->getParentID()));
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
//
|
|
||||||
// RemapIDOperator.h
|
|
||||||
// libraries/entities/src
|
|
||||||
//
|
|
||||||
// Created by Seth Alves on 2015-12-6.
|
|
||||||
// Copyright 2015 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_RemapIDOperator_h
|
|
||||||
#define hifi_RemapIDOperator_h
|
|
||||||
|
|
||||||
#include "Octree.h"
|
|
||||||
|
|
||||||
// this will change all the IDs in an EntityTree. Parent/Child relationships are maintained.
|
|
||||||
|
|
||||||
class RemapIDOperator : public RecurseOctreeOperator {
|
|
||||||
public:
|
|
||||||
RemapIDOperator() : RecurseOctreeOperator() {}
|
|
||||||
~RemapIDOperator() {}
|
|
||||||
virtual bool preRecursion(OctreeElementPointer element) { return true; }
|
|
||||||
virtual bool postRecursion(OctreeElementPointer element);
|
|
||||||
private:
|
|
||||||
QUuid remap(const QUuid& oldID);
|
|
||||||
QHash<QUuid, QUuid> _oldToNew;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // hifi_RemapIDOperator_h
|
|
|
@ -70,7 +70,7 @@ public:
|
||||||
virtual bool event(QEvent *e) override;
|
virtual bool event(QEvent *e) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
class Queue : public QQueue<QEvent*> {
|
class Queue : private QQueue<QEvent*> {
|
||||||
public:
|
public:
|
||||||
void add(QEvent::Type type);
|
void add(QEvent::Type type);
|
||||||
QEvent* take();
|
QEvent* take();
|
||||||
|
@ -134,12 +134,14 @@ QEvent* OffscreenQmlRenderThread::Queue::take() {
|
||||||
}
|
}
|
||||||
|
|
||||||
OffscreenQmlRenderThread::OffscreenQmlRenderThread(OffscreenQmlSurface* surface, QOpenGLContext* shareContext) : _surface(surface) {
|
OffscreenQmlRenderThread::OffscreenQmlRenderThread(OffscreenQmlSurface* surface, QOpenGLContext* shareContext) : _surface(surface) {
|
||||||
|
qDebug() << "Building QML Renderer: creating context";
|
||||||
if (!_canvas.create(shareContext)) {
|
if (!_canvas.create(shareContext)) {
|
||||||
static const char* error = "Failed to create OffscreenGLCanvas";
|
static const char* error = "Failed to create OffscreenGLCanvas";
|
||||||
qWarning() << error;
|
qWarning() << error;
|
||||||
throw error;
|
throw error;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
qDebug() << "Building QML Renderer: creating render control";
|
||||||
_renderControl = new QMyQuickRenderControl();
|
_renderControl = new QMyQuickRenderControl();
|
||||||
QQuickWindow::setDefaultAlphaBuffer(true);
|
QQuickWindow::setDefaultAlphaBuffer(true);
|
||||||
// Create a QQuickWindow that is associated with our render control.
|
// Create a QQuickWindow that is associated with our render control.
|
||||||
|
@ -147,19 +149,25 @@ OffscreenQmlRenderThread::OffscreenQmlRenderThread(OffscreenQmlSurface* surface,
|
||||||
// NOTE: Must be created on the main thread so that OffscreenQmlSurface can send it events
|
// NOTE: Must be created on the main thread so that OffscreenQmlSurface can send it events
|
||||||
// NOTE: Must be created on the rendering thread or it will refuse to render,
|
// NOTE: Must be created on the rendering thread or it will refuse to render,
|
||||||
// so we wait until after its ctor to move object/context to this thread.
|
// so we wait until after its ctor to move object/context to this thread.
|
||||||
|
qDebug() << "Building QML Renderer: creating window";
|
||||||
_quickWindow = new QQuickWindow(_renderControl);
|
_quickWindow = new QQuickWindow(_renderControl);
|
||||||
_quickWindow->setColor(QColor(255, 255, 255, 0));
|
_quickWindow->setColor(QColor(255, 255, 255, 0));
|
||||||
_quickWindow->setFlags(_quickWindow->flags() | static_cast<Qt::WindowFlags>(Qt::WA_TranslucentBackground));
|
_quickWindow->setFlags(_quickWindow->flags() | static_cast<Qt::WindowFlags>(Qt::WA_TranslucentBackground));
|
||||||
|
|
||||||
// We can prepare, but we must wait to start() the thread until after the ctor
|
// We can prepare, but we must wait to start() the thread until after the ctor
|
||||||
|
qDebug() << "Building QML Renderer: moving to own thread";
|
||||||
_renderControl->prepareThread(this);
|
_renderControl->prepareThread(this);
|
||||||
_canvas.getContextObject()->moveToThread(this);
|
_canvas.getContextObject()->moveToThread(this);
|
||||||
moveToThread(this);
|
moveToThread(this);
|
||||||
|
|
||||||
|
qDebug() << "Building QML Renderer: complete";
|
||||||
|
|
||||||
_queue.add(INIT);
|
_queue.add(INIT);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OffscreenQmlRenderThread::run() {
|
void OffscreenQmlRenderThread::run() {
|
||||||
|
qDebug() << "Starting QML Renderer thread";
|
||||||
|
|
||||||
while (!_quit) {
|
while (!_quit) {
|
||||||
QEvent* e = _queue.take();
|
QEvent* e = _queue.take();
|
||||||
event(e);
|
event(e);
|
||||||
|
@ -208,12 +216,14 @@ void OffscreenQmlRenderThread::setupFbo() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void OffscreenQmlRenderThread::init() {
|
void OffscreenQmlRenderThread::init() {
|
||||||
|
qDebug() << "Initializing QML Renderer";
|
||||||
|
|
||||||
connect(_renderControl, &QQuickRenderControl::renderRequested, _surface, &OffscreenQmlSurface::requestRender);
|
connect(_renderControl, &QQuickRenderControl::renderRequested, _surface, &OffscreenQmlSurface::requestRender);
|
||||||
connect(_renderControl, &QQuickRenderControl::sceneChanged, _surface, &OffscreenQmlSurface::requestUpdate);
|
connect(_renderControl, &QQuickRenderControl::sceneChanged, _surface, &OffscreenQmlSurface::requestUpdate);
|
||||||
|
|
||||||
if (!_canvas.makeCurrent()) {
|
if (!_canvas.makeCurrent()) {
|
||||||
// Failed to make GL context current, this OffscreenQmlSurface is basically dead
|
|
||||||
qWarning("Failed to make context current on QML Renderer Thread");
|
qWarning("Failed to make context current on QML Renderer Thread");
|
||||||
|
_quit = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -360,6 +370,8 @@ void OffscreenQmlSurface::onAboutToQuit() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void OffscreenQmlSurface::create(QOpenGLContext* shareContext) {
|
void OffscreenQmlSurface::create(QOpenGLContext* shareContext) {
|
||||||
|
qDebug() << "Building QML surface";
|
||||||
|
|
||||||
_renderer = new OffscreenQmlRenderThread(this, shareContext);
|
_renderer = new OffscreenQmlRenderThread(this, shareContext);
|
||||||
_renderer->moveToThread(_renderer);
|
_renderer->moveToThread(_renderer);
|
||||||
_renderer->setObjectName("QML Renderer Thread");
|
_renderer->setObjectName("QML Renderer Thread");
|
||||||
|
|
|
@ -158,7 +158,6 @@ public:
|
||||||
~GLState();
|
~GLState();
|
||||||
|
|
||||||
// The state commands to reset to default,
|
// The state commands to reset to default,
|
||||||
// WARNING depending on the order of the State::Field enum
|
|
||||||
static const Commands _resetStateCommands;
|
static const Commands _resetStateCommands;
|
||||||
|
|
||||||
friend class GLBackend;
|
friend class GLBackend;
|
||||||
|
|
|
@ -35,6 +35,7 @@ const GLBackend::GLState::Commands makeResetStateCommands();
|
||||||
const GLBackend::GLState::Commands GLBackend::GLState::_resetStateCommands = makeResetStateCommands();
|
const GLBackend::GLState::Commands GLBackend::GLState::_resetStateCommands = makeResetStateCommands();
|
||||||
|
|
||||||
|
|
||||||
|
// NOTE: This must stay in sync with the ordering of the State::Field enum
|
||||||
const GLBackend::GLState::Commands makeResetStateCommands() {
|
const GLBackend::GLState::Commands makeResetStateCommands() {
|
||||||
// Since State::DEFAULT is a static defined in another .cpp the initialisation order is random
|
// Since State::DEFAULT is a static defined in another .cpp the initialisation order is random
|
||||||
// and we have a 50/50 chance that State::DEFAULT is not yet initialized.
|
// and we have a 50/50 chance that State::DEFAULT is not yet initialized.
|
||||||
|
@ -69,9 +70,9 @@ const GLBackend::GLState::Commands makeResetStateCommands() {
|
||||||
CommandPointer(stencilCommand),
|
CommandPointer(stencilCommand),
|
||||||
CommandPointer(stencilCommand),
|
CommandPointer(stencilCommand),
|
||||||
|
|
||||||
std::make_shared<Command1B>(&GLBackend::do_setStateAlphaToCoverageEnable, DEFAULT.alphaToCoverageEnable),
|
|
||||||
|
|
||||||
std::make_shared<Command1U>(&GLBackend::do_setStateSampleMask, DEFAULT.sampleMask),
|
std::make_shared<Command1U>(&GLBackend::do_setStateSampleMask, DEFAULT.sampleMask),
|
||||||
|
|
||||||
|
std::make_shared<Command1B>(&GLBackend::do_setStateAlphaToCoverageEnable, DEFAULT.alphaToCoverageEnable),
|
||||||
|
|
||||||
std::make_shared<CommandBlend>(&GLBackend::do_setStateBlend, DEFAULT.blendFunction),
|
std::make_shared<CommandBlend>(&GLBackend::do_setStateBlend, DEFAULT.blendFunction),
|
||||||
|
|
||||||
|
|
|
@ -345,6 +345,7 @@ public:
|
||||||
uint8 getColorWriteMask() const { return _values.colorWriteMask; }
|
uint8 getColorWriteMask() const { return _values.colorWriteMask; }
|
||||||
|
|
||||||
// All the possible fields
|
// All the possible fields
|
||||||
|
// NOTE: If you change this, you must update GLBackend::GLState::_resetStateCommands
|
||||||
enum Field {
|
enum Field {
|
||||||
FILL_MODE,
|
FILL_MODE,
|
||||||
CULL_MODE,
|
CULL_MODE,
|
||||||
|
@ -364,6 +365,7 @@ public:
|
||||||
STENCIL_TEST_BACK,
|
STENCIL_TEST_BACK,
|
||||||
|
|
||||||
SAMPLE_MASK,
|
SAMPLE_MASK,
|
||||||
|
|
||||||
ALPHA_TO_COVERAGE_ENABLE,
|
ALPHA_TO_COVERAGE_ENABLE,
|
||||||
|
|
||||||
BLEND_FUNCTION,
|
BLEND_FUNCTION,
|
||||||
|
|
|
@ -111,7 +111,7 @@ Box Mesh::evalPartBound(int partNum) const {
|
||||||
return box;
|
return box;
|
||||||
}
|
}
|
||||||
|
|
||||||
Box Mesh::evalPartBounds(int partStart, int partEnd, Boxes& bounds) const {
|
Box Mesh::evalPartsBound(int partStart, int partEnd) const {
|
||||||
Box totalBound;
|
Box totalBound;
|
||||||
auto part = _partBuffer.cbegin<Part>() + partStart;
|
auto part = _partBuffer.cbegin<Part>() + partStart;
|
||||||
auto partItEnd = _partBuffer.cbegin<Part>() + partEnd;
|
auto partItEnd = _partBuffer.cbegin<Part>() + partEnd;
|
||||||
|
|
|
@ -108,9 +108,9 @@ public:
|
||||||
|
|
||||||
// evaluate the bounding box of A part
|
// evaluate the bounding box of A part
|
||||||
Box evalPartBound(int partNum) const;
|
Box evalPartBound(int partNum) const;
|
||||||
// evaluate the bounding boxes of the parts in the range [start, end[ and fill the bounds parameter
|
// evaluate the bounding boxes of the parts in the range [start, end]
|
||||||
// the returned box is the bounding box of ALL the evaluated part bounds.
|
// the returned box is the bounding box of ALL the evaluated parts bound.
|
||||||
Box evalPartBounds(int partStart, int partEnd, Boxes& bounds) const;
|
Box evalPartsBound(int partStart, int partEnd) const;
|
||||||
|
|
||||||
static gpu::Primitive topologyToPrimitive(Topology topo) { return static_cast<gpu::Primitive>(topo); }
|
static gpu::Primitive topologyToPrimitive(Topology topo) { return static_cast<gpu::Primitive>(topo); }
|
||||||
|
|
||||||
|
|
|
@ -228,7 +228,8 @@ bool AssetClient::getAsset(const QString& hash, DataOffset start, DataOffset end
|
||||||
|
|
||||||
nodeList->sendPacket(std::move(packet), *assetServer);
|
nodeList->sendPacket(std::move(packet), *assetServer);
|
||||||
|
|
||||||
_pendingRequests[assetServer][messageID] = { callback, progressCallback };
|
_pendingRequests[assetServer][messageID] = { QSharedPointer<ReceivedMessage>(), callback, progressCallback };
|
||||||
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
@ -326,6 +327,9 @@ void AssetClient::handleAssetGetReply(QSharedPointer<ReceivedMessage> message, S
|
||||||
if (requestIt != messageCallbackMap.end()) {
|
if (requestIt != messageCallbackMap.end()) {
|
||||||
auto& callbacks = requestIt->second;
|
auto& callbacks = requestIt->second;
|
||||||
|
|
||||||
|
// Store message in case we need to disconnect from it later.
|
||||||
|
callbacks.message = message;
|
||||||
|
|
||||||
if (message->isComplete()) {
|
if (message->isComplete()) {
|
||||||
callbacks.completeCallback(true, error, message->readAll());
|
callbacks.completeCallback(true, error, message->readAll());
|
||||||
} else {
|
} else {
|
||||||
|
@ -550,6 +554,12 @@ void AssetClient::handleNodeKilled(SharedNodePointer node) {
|
||||||
auto messageMapIt = _pendingRequests.find(node);
|
auto messageMapIt = _pendingRequests.find(node);
|
||||||
if (messageMapIt != _pendingRequests.end()) {
|
if (messageMapIt != _pendingRequests.end()) {
|
||||||
for (const auto& value : messageMapIt->second) {
|
for (const auto& value : messageMapIt->second) {
|
||||||
|
auto& message = value.second.message;
|
||||||
|
if (message) {
|
||||||
|
// Disconnect from all signals emitting from the pending message
|
||||||
|
disconnect(message.data(), nullptr, this, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
value.second.completeCallback(false, AssetServerError::NoError, QByteArray());
|
value.second.completeCallback(false, AssetServerError::NoError, QByteArray());
|
||||||
}
|
}
|
||||||
messageMapIt->second.clear();
|
messageMapIt->second.clear();
|
||||||
|
|
|
@ -86,14 +86,15 @@ private:
|
||||||
ReceivedAssetCallback callback, ProgressCallback progressCallback);
|
ReceivedAssetCallback callback, ProgressCallback progressCallback);
|
||||||
bool uploadAsset(const QByteArray& data, UploadResultCallback callback);
|
bool uploadAsset(const QByteArray& data, UploadResultCallback callback);
|
||||||
|
|
||||||
struct GetAssetCallbacks {
|
struct GetAssetRequestData {
|
||||||
|
QSharedPointer<ReceivedMessage> message;
|
||||||
ReceivedAssetCallback completeCallback;
|
ReceivedAssetCallback completeCallback;
|
||||||
ProgressCallback progressCallback;
|
ProgressCallback progressCallback;
|
||||||
};
|
};
|
||||||
|
|
||||||
static MessageID _currentID;
|
static MessageID _currentID;
|
||||||
std::unordered_map<SharedNodePointer, std::unordered_map<MessageID, MappingOperationCallback>> _pendingMappingRequests;
|
std::unordered_map<SharedNodePointer, std::unordered_map<MessageID, MappingOperationCallback>> _pendingMappingRequests;
|
||||||
std::unordered_map<SharedNodePointer, std::unordered_map<MessageID, GetAssetCallbacks>> _pendingRequests;
|
std::unordered_map<SharedNodePointer, std::unordered_map<MessageID, GetAssetRequestData>> _pendingRequests;
|
||||||
std::unordered_map<SharedNodePointer, std::unordered_map<MessageID, GetInfoCallback>> _pendingInfoRequests;
|
std::unordered_map<SharedNodePointer, std::unordered_map<MessageID, GetInfoCallback>> _pendingInfoRequests;
|
||||||
std::unordered_map<SharedNodePointer, std::unordered_map<MessageID, UploadResultCallback>> _pendingUploads;
|
std::unordered_map<SharedNodePointer, std::unordered_map<MessageID, UploadResultCallback>> _pendingUploads;
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,8 @@ public:
|
||||||
virtual render::ScenePointer getMain3DScene() = 0;
|
virtual render::ScenePointer getMain3DScene() = 0;
|
||||||
virtual render::EnginePointer getRenderEngine() = 0;
|
virtual render::EnginePointer getRenderEngine() = 0;
|
||||||
|
|
||||||
|
virtual void pushPreRenderLambda(void* key, std::function<void()> func) = 0;
|
||||||
|
|
||||||
// FIXME - we shouldn't assume that there's a single instance of an AbstractViewStateInterface
|
// FIXME - we shouldn't assume that there's a single instance of an AbstractViewStateInterface
|
||||||
static AbstractViewStateInterface* instance();
|
static AbstractViewStateInterface* instance();
|
||||||
static void setInstance(AbstractViewStateInterface* instance);
|
static void setInstance(AbstractViewStateInterface* instance);
|
||||||
|
|
|
@ -58,7 +58,6 @@ void MeshPartPayload::updateMeshPart(const std::shared_ptr<const model::Mesh>& d
|
||||||
auto vertexFormat = _drawMesh->getVertexFormat();
|
auto vertexFormat = _drawMesh->getVertexFormat();
|
||||||
_hasColorAttrib = vertexFormat->hasAttribute(gpu::Stream::COLOR);
|
_hasColorAttrib = vertexFormat->hasAttribute(gpu::Stream::COLOR);
|
||||||
_drawPart = _drawMesh->getPartBuffer().get<model::Mesh::Part>(partIndex);
|
_drawPart = _drawMesh->getPartBuffer().get<model::Mesh::Part>(partIndex);
|
||||||
|
|
||||||
_localBound = _drawMesh->evalPartBound(partIndex);
|
_localBound = _drawMesh->evalPartBound(partIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -352,7 +351,23 @@ void ModelMeshPartPayload::initCache() {
|
||||||
|
|
||||||
|
|
||||||
void ModelMeshPartPayload::notifyLocationChanged() {
|
void ModelMeshPartPayload::notifyLocationChanged() {
|
||||||
_model->_needsUpdateClusterMatrices = true;
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModelMeshPartPayload::updateTransformForSkinnedMesh(const Transform& transform, const Transform& offsetTransform, const QVector<glm::mat4>& clusterMatrices) {
|
||||||
|
ModelMeshPartPayload::updateTransform(transform, offsetTransform);
|
||||||
|
|
||||||
|
if (clusterMatrices.size() > 0) {
|
||||||
|
_worldBound = AABox();
|
||||||
|
for (auto& clusterMatrix : clusterMatrices) {
|
||||||
|
AABox clusterBound = _localBound;
|
||||||
|
clusterBound.transform(clusterMatrix);
|
||||||
|
_worldBound += clusterBound;
|
||||||
|
}
|
||||||
|
|
||||||
|
// clusterMatrix has world rotation but not world translation.
|
||||||
|
_worldBound.translate(transform.getTranslation());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemKey ModelMeshPartPayload::getKey() const {
|
ItemKey ModelMeshPartPayload::getKey() const {
|
||||||
|
@ -377,12 +392,6 @@ ItemKey ModelMeshPartPayload::getKey() const {
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
Item::Bound ModelMeshPartPayload::getBound() const {
|
|
||||||
// NOTE: we can't cache this bounds because we need to handle the case of a moving
|
|
||||||
// entity or mesh part.
|
|
||||||
return _model->getPartBounds(_meshIndex, _partIndex, _transform.getTranslation(), _transform.getRotation());
|
|
||||||
}
|
|
||||||
|
|
||||||
ShapeKey ModelMeshPartPayload::getShapeKey() const {
|
ShapeKey ModelMeshPartPayload::getShapeKey() const {
|
||||||
assert(_model->isLoaded());
|
assert(_model->isLoaded());
|
||||||
const FBXGeometry& geometry = _model->getFBXGeometry();
|
const FBXGeometry& geometry = _model->getFBXGeometry();
|
||||||
|
|
|
@ -74,15 +74,15 @@ namespace render {
|
||||||
class ModelMeshPartPayload : public MeshPartPayload {
|
class ModelMeshPartPayload : public MeshPartPayload {
|
||||||
public:
|
public:
|
||||||
ModelMeshPartPayload(Model* model, int meshIndex, int partIndex, int shapeIndex, const Transform& transform, const Transform& offsetTransform);
|
ModelMeshPartPayload(Model* model, int meshIndex, int partIndex, int shapeIndex, const Transform& transform, const Transform& offsetTransform);
|
||||||
|
|
||||||
typedef render::Payload<ModelMeshPartPayload> Payload;
|
typedef render::Payload<ModelMeshPartPayload> Payload;
|
||||||
typedef Payload::DataPointer Pointer;
|
typedef Payload::DataPointer Pointer;
|
||||||
|
|
||||||
void notifyLocationChanged() override;
|
void notifyLocationChanged() override;
|
||||||
|
void updateTransformForSkinnedMesh(const Transform& transform, const Transform& offsetTransform, const QVector<glm::mat4>& clusterMatrices);
|
||||||
|
|
||||||
// Render Item interface
|
// Render Item interface
|
||||||
render::ItemKey getKey() const override;
|
render::ItemKey getKey() const override;
|
||||||
render::Item::Bound getBound() const override;
|
|
||||||
render::ShapeKey getShapeKey() const override; // shape interface
|
render::ShapeKey getShapeKey() const override; // shape interface
|
||||||
void render(RenderArgs* args) const override;
|
void render(RenderArgs* args) const override;
|
||||||
|
|
||||||
|
@ -101,4 +101,11 @@ public:
|
||||||
bool _isBlendShaped{ false };
|
bool _isBlendShaped{ false };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
namespace render {
|
||||||
|
template <> const ItemKey payloadGetKey(const ModelMeshPartPayload::Pointer& payload);
|
||||||
|
template <> const Item::Bound payloadGetBound(const ModelMeshPartPayload::Pointer& payload);
|
||||||
|
template <> const ShapeKey shapeGetShapeKey(const ModelMeshPartPayload::Pointer& payload);
|
||||||
|
template <> void payloadRender(const ModelMeshPartPayload::Pointer& payload, RenderArgs* args);
|
||||||
|
}
|
||||||
|
|
||||||
#endif // hifi_MeshPartPayload_h
|
#endif // hifi_MeshPartPayload_h
|
||||||
|
|
|
@ -130,25 +130,60 @@ void Model::setOffset(const glm::vec3& offset) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Model::enqueueLocationChange() {
|
void Model::enqueueLocationChange() {
|
||||||
render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene();
|
|
||||||
|
|
||||||
Transform transform;
|
// queue up this work for later processing, at the end of update and just before rendering.
|
||||||
transform.setTranslation(_translation);
|
// the application will ensure only the last lambda is actually invoked.
|
||||||
transform.setRotation(_rotation);
|
void* key = (void*)this;
|
||||||
|
std::weak_ptr<Model> weakSelf = shared_from_this();
|
||||||
|
AbstractViewStateInterface::instance()->pushPreRenderLambda(key, [weakSelf]() {
|
||||||
|
|
||||||
Transform offset;
|
// do nothing, if the model has already been destroyed.
|
||||||
offset.setScale(_scale);
|
auto self = weakSelf.lock();
|
||||||
offset.postTranslate(_offset);
|
if (!self) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
render::PendingChanges pendingChanges;
|
render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene();
|
||||||
foreach (auto itemID, _renderItems.keys()) {
|
|
||||||
pendingChanges.updateItem<MeshPartPayload>(itemID, [transform, offset](MeshPartPayload& data) {
|
|
||||||
data.updateTransform(transform, offset);
|
|
||||||
data.notifyLocationChanged();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
scene->enqueuePendingChanges(pendingChanges);
|
Transform modelTransform;
|
||||||
|
modelTransform.setScale(self->_scale);
|
||||||
|
modelTransform.setTranslation(self->_translation);
|
||||||
|
modelTransform.setRotation(self->_rotation);
|
||||||
|
|
||||||
|
Transform modelMeshOffset;
|
||||||
|
if (self->isLoaded()) {
|
||||||
|
// includes model offset and unitScale.
|
||||||
|
modelMeshOffset = Transform(self->_rig->getGeometryToRigTransform());
|
||||||
|
} else {
|
||||||
|
modelMeshOffset.postTranslate(self->_offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
// only apply offset only, collision mesh does not share the same unit scale as the FBX file's mesh.
|
||||||
|
Transform collisionMeshOffset;
|
||||||
|
collisionMeshOffset.postTranslate(self->_offset);
|
||||||
|
|
||||||
|
render::PendingChanges pendingChanges;
|
||||||
|
foreach (auto itemID, self->_modelMeshRenderItems.keys()) {
|
||||||
|
pendingChanges.updateItem<ModelMeshPartPayload>(itemID, [modelTransform, modelMeshOffset](ModelMeshPartPayload& data) {
|
||||||
|
|
||||||
|
// lazy update of cluster matrices used for rendering. We need to update them here, so we can correctly update the bounding box.
|
||||||
|
data._model->updateClusterMatrices(modelTransform.getTranslation(), modelTransform.getRotation());
|
||||||
|
|
||||||
|
// update the model transform and bounding box for this render item.
|
||||||
|
const Model::MeshState& state = data._model->_meshStates.at(data._meshIndex);
|
||||||
|
data.updateTransformForSkinnedMesh(modelTransform, modelMeshOffset, state.clusterMatrices);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (auto itemID, self->_collisionRenderItems.keys()) {
|
||||||
|
pendingChanges.updateItem<MeshPartPayload>(itemID, [modelTransform, collisionMeshOffset](MeshPartPayload& data) {
|
||||||
|
// update the model transform for this render item.
|
||||||
|
data.updateTransform(modelTransform, collisionMeshOffset);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
scene->enqueuePendingChanges(pendingChanges);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void Model::initJointTransforms() {
|
void Model::initJointTransforms() {
|
||||||
|
@ -497,8 +532,11 @@ void Model::setVisibleInScene(bool newValue, std::shared_ptr<render::Scene> scen
|
||||||
_isVisible = newValue;
|
_isVisible = newValue;
|
||||||
|
|
||||||
render::PendingChanges pendingChanges;
|
render::PendingChanges pendingChanges;
|
||||||
foreach (auto item, _renderItems.keys()) {
|
foreach (auto item, _modelMeshRenderItems.keys()) {
|
||||||
pendingChanges.resetItem(item, _renderItems[item]);
|
pendingChanges.resetItem(item, _modelMeshRenderItems[item]);
|
||||||
|
}
|
||||||
|
foreach (auto item, _collisionRenderItems.keys()) {
|
||||||
|
pendingChanges.resetItem(item, _modelMeshRenderItems[item]);
|
||||||
}
|
}
|
||||||
scene->enqueuePendingChanges(pendingChanges);
|
scene->enqueuePendingChanges(pendingChanges);
|
||||||
}
|
}
|
||||||
|
@ -514,14 +552,25 @@ bool Model::addToScene(std::shared_ptr<render::Scene> scene, render::PendingChan
|
||||||
|
|
||||||
bool somethingAdded = false;
|
bool somethingAdded = false;
|
||||||
|
|
||||||
foreach (auto renderItem, _renderItemsSet) {
|
foreach (auto renderItem, _modelMeshRenderItemsSet) {
|
||||||
|
auto item = scene->allocateID();
|
||||||
|
auto renderPayload = std::make_shared<ModelMeshPartPayload::Payload>(renderItem);
|
||||||
|
pendingChanges.resetItem(item, renderPayload);
|
||||||
|
pendingChanges.updateItem<ModelMeshPartPayload>(item, [](ModelMeshPartPayload& data) {
|
||||||
|
data.notifyLocationChanged();
|
||||||
|
});
|
||||||
|
_modelMeshRenderItems.insert(item, renderPayload);
|
||||||
|
somethingAdded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (auto renderItem, _collisionRenderItemsSet) {
|
||||||
auto item = scene->allocateID();
|
auto item = scene->allocateID();
|
||||||
auto renderPayload = std::make_shared<MeshPartPayload::Payload>(renderItem);
|
auto renderPayload = std::make_shared<MeshPartPayload::Payload>(renderItem);
|
||||||
pendingChanges.resetItem(item, renderPayload);
|
pendingChanges.resetItem(item, renderPayload);
|
||||||
pendingChanges.updateItem<MeshPartPayload>(item, [](MeshPartPayload& data) {
|
pendingChanges.updateItem<MeshPartPayload>(item, [](MeshPartPayload& data) {
|
||||||
data.notifyLocationChanged();
|
data.notifyLocationChanged();
|
||||||
});
|
});
|
||||||
_renderItems.insert(item, renderPayload);
|
_collisionRenderItems.insert(item, renderPayload);
|
||||||
somethingAdded = true;
|
somethingAdded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -541,7 +590,19 @@ bool Model::addToScene(std::shared_ptr<render::Scene> scene,
|
||||||
|
|
||||||
bool somethingAdded = false;
|
bool somethingAdded = false;
|
||||||
|
|
||||||
foreach (auto renderItem, _renderItemsSet) {
|
foreach (auto renderItem, _modelMeshRenderItemsSet) {
|
||||||
|
auto item = scene->allocateID();
|
||||||
|
auto renderPayload = std::make_shared<ModelMeshPartPayload::Payload>(renderItem);
|
||||||
|
renderPayload->addStatusGetters(statusGetters);
|
||||||
|
pendingChanges.resetItem(item, renderPayload);
|
||||||
|
pendingChanges.updateItem<ModelMeshPartPayload>(item, [](ModelMeshPartPayload& data) {
|
||||||
|
data.notifyLocationChanged();
|
||||||
|
});
|
||||||
|
_modelMeshRenderItems.insert(item, renderPayload);
|
||||||
|
somethingAdded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (auto renderItem, _collisionRenderItemsSet) {
|
||||||
auto item = scene->allocateID();
|
auto item = scene->allocateID();
|
||||||
auto renderPayload = std::make_shared<MeshPartPayload::Payload>(renderItem);
|
auto renderPayload = std::make_shared<MeshPartPayload::Payload>(renderItem);
|
||||||
renderPayload->addStatusGetters(statusGetters);
|
renderPayload->addStatusGetters(statusGetters);
|
||||||
|
@ -549,7 +610,7 @@ bool Model::addToScene(std::shared_ptr<render::Scene> scene,
|
||||||
pendingChanges.updateItem<MeshPartPayload>(item, [](MeshPartPayload& data) {
|
pendingChanges.updateItem<MeshPartPayload>(item, [](MeshPartPayload& data) {
|
||||||
data.notifyLocationChanged();
|
data.notifyLocationChanged();
|
||||||
});
|
});
|
||||||
_renderItems.insert(item, renderPayload);
|
_collisionRenderItems.insert(item, renderPayload);
|
||||||
somethingAdded = true;
|
somethingAdded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -559,11 +620,16 @@ bool Model::addToScene(std::shared_ptr<render::Scene> scene,
|
||||||
}
|
}
|
||||||
|
|
||||||
void Model::removeFromScene(std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges) {
|
void Model::removeFromScene(std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges) {
|
||||||
foreach (auto item, _renderItems.keys()) {
|
foreach (auto item, _modelMeshRenderItems.keys()) {
|
||||||
pendingChanges.removeItem(item);
|
pendingChanges.removeItem(item);
|
||||||
}
|
}
|
||||||
_renderItems.clear();
|
_modelMeshRenderItems.clear();
|
||||||
_renderItemsSet.clear();
|
_modelMeshRenderItemsSet.clear();
|
||||||
|
foreach (auto item, _collisionRenderItems.keys()) {
|
||||||
|
pendingChanges.removeItem(item);
|
||||||
|
}
|
||||||
|
_collisionRenderItems.clear();
|
||||||
|
_collisionRenderItemsSet.clear();
|
||||||
_meshGroupsKnown = false;
|
_meshGroupsKnown = false;
|
||||||
_readyWhenAdded = false;
|
_readyWhenAdded = false;
|
||||||
}
|
}
|
||||||
|
@ -1175,10 +1241,14 @@ void Model::segregateMeshGroups() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Q_ASSERT(_renderItems.isEmpty()); // We should not have any existing renderItems if we enter this section of code
|
// We should not have any existing renderItems if we enter this section of code
|
||||||
Q_ASSERT(_renderItemsSet.isEmpty()); // We should not have any existing renderItemsSet if we enter this section of code
|
Q_ASSERT(_modelMeshRenderItems.isEmpty());
|
||||||
|
Q_ASSERT(_modelMeshRenderItemsSet.isEmpty());
|
||||||
|
Q_ASSERT(_collisionRenderItems.isEmpty());
|
||||||
|
Q_ASSERT(_collisionRenderItemsSet.isEmpty());
|
||||||
|
|
||||||
_renderItemsSet.clear();
|
_modelMeshRenderItemsSet.clear();
|
||||||
|
_collisionRenderItemsSet.clear();
|
||||||
|
|
||||||
Transform transform;
|
Transform transform;
|
||||||
transform.setTranslation(_translation);
|
transform.setTranslation(_translation);
|
||||||
|
@ -1204,9 +1274,9 @@ void Model::segregateMeshGroups() {
|
||||||
_collisionHullMaterial->setMetallic(0.02f);
|
_collisionHullMaterial->setMetallic(0.02f);
|
||||||
_collisionHullMaterial->setRoughness(0.5f);
|
_collisionHullMaterial->setRoughness(0.5f);
|
||||||
}
|
}
|
||||||
_renderItemsSet << std::make_shared<MeshPartPayload>(networkMesh, partIndex, _collisionHullMaterial, transform, offset);
|
_collisionRenderItemsSet << std::make_shared<MeshPartPayload>(networkMesh, partIndex, _collisionHullMaterial, transform, offset);
|
||||||
} else {
|
} else {
|
||||||
_renderItemsSet << std::make_shared<ModelMeshPartPayload>(this, i, partIndex, shapeID, transform, offset);
|
_modelMeshRenderItemsSet << std::make_shared<ModelMeshPartPayload>(this, i, partIndex, shapeID, transform, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
shapeID++;
|
shapeID++;
|
||||||
|
@ -1229,13 +1299,21 @@ bool Model::initWhenReady(render::ScenePointer scene) {
|
||||||
offset.setScale(_scale);
|
offset.setScale(_scale);
|
||||||
offset.postTranslate(_offset);
|
offset.postTranslate(_offset);
|
||||||
|
|
||||||
foreach (auto renderItem, _renderItemsSet) {
|
foreach (auto renderItem, _modelMeshRenderItemsSet) {
|
||||||
|
auto item = scene->allocateID();
|
||||||
|
auto renderPayload = std::make_shared<ModelMeshPartPayload::Payload>(renderItem);
|
||||||
|
_modelMeshRenderItems.insert(item, renderPayload);
|
||||||
|
pendingChanges.resetItem(item, renderPayload);
|
||||||
|
pendingChanges.updateItem<ModelMeshPartPayload>(item, [transform, offset](MeshPartPayload& data) {
|
||||||
|
data.notifyLocationChanged();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
foreach (auto renderItem, _collisionRenderItemsSet) {
|
||||||
auto item = scene->allocateID();
|
auto item = scene->allocateID();
|
||||||
auto renderPayload = std::make_shared<MeshPartPayload::Payload>(renderItem);
|
auto renderPayload = std::make_shared<MeshPartPayload::Payload>(renderItem);
|
||||||
_renderItems.insert(item, renderPayload);
|
_collisionRenderItems.insert(item, renderPayload);
|
||||||
pendingChanges.resetItem(item, renderPayload);
|
pendingChanges.resetItem(item, renderPayload);
|
||||||
pendingChanges.updateItem<MeshPartPayload>(item, [transform,offset](MeshPartPayload& data) {
|
pendingChanges.updateItem<MeshPartPayload>(item, [transform, offset](MeshPartPayload& data) {
|
||||||
data.updateTransform(transform, offset);
|
|
||||||
data.notifyLocationChanged();
|
data.notifyLocationChanged();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,7 @@ namespace render {
|
||||||
typedef unsigned int ItemID;
|
typedef unsigned int ItemID;
|
||||||
}
|
}
|
||||||
class MeshPartPayload;
|
class MeshPartPayload;
|
||||||
|
class ModelMeshPartPayload;
|
||||||
class ModelRenderLocations;
|
class ModelRenderLocations;
|
||||||
|
|
||||||
inline uint qHash(const std::shared_ptr<MeshPartPayload>& a, uint seed) {
|
inline uint qHash(const std::shared_ptr<MeshPartPayload>& a, uint seed) {
|
||||||
|
@ -73,6 +74,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the URL of the model to render.
|
/// Sets the URL of the model to render.
|
||||||
|
// Should only be called from the model's rendering thread to avoid access violations of changed geometry.
|
||||||
Q_INVOKABLE void setURL(const QUrl& url);
|
Q_INVOKABLE void setURL(const QUrl& url);
|
||||||
const QUrl& getURL() const { return _url; }
|
const QUrl& getURL() const { return _url; }
|
||||||
|
|
||||||
|
@ -133,7 +135,8 @@ public:
|
||||||
/// Provided as a convenience, will crash if !isCollisionLoaded()
|
/// Provided as a convenience, will crash if !isCollisionLoaded()
|
||||||
const FBXGeometry& getCollisionFBXGeometry() const { assert(isCollisionLoaded()); return getCollisionGeometry()->getGeometry()->getGeometry(); }
|
const FBXGeometry& getCollisionFBXGeometry() const { assert(isCollisionLoaded()); return getCollisionGeometry()->getGeometry()->getGeometry(); }
|
||||||
|
|
||||||
// Set the model to use for collisions
|
// Set the model to use for collisions.
|
||||||
|
// Should only be called from the model's rendering thread to avoid access violations of changed geometry.
|
||||||
Q_INVOKABLE void setCollisionModelURL(const QUrl& url);
|
Q_INVOKABLE void setCollisionModelURL(const QUrl& url);
|
||||||
const QUrl& getCollisionURL() const { return _collisionUrl; }
|
const QUrl& getCollisionURL() const { return _collisionUrl; }
|
||||||
|
|
||||||
|
@ -373,8 +376,12 @@ protected:
|
||||||
bool _renderCollisionHull;
|
bool _renderCollisionHull;
|
||||||
|
|
||||||
|
|
||||||
QSet<std::shared_ptr<MeshPartPayload>> _renderItemsSet;
|
QSet<std::shared_ptr<MeshPartPayload>> _collisionRenderItemsSet;
|
||||||
QMap<render::ItemID, render::PayloadPointer> _renderItems;
|
QMap<render::ItemID, render::PayloadPointer> _collisionRenderItems;
|
||||||
|
|
||||||
|
QSet<std::shared_ptr<ModelMeshPartPayload>> _modelMeshRenderItemsSet;
|
||||||
|
QMap<render::ItemID, render::PayloadPointer> _modelMeshRenderItems;
|
||||||
|
|
||||||
bool _readyWhenAdded { false };
|
bool _readyWhenAdded { false };
|
||||||
bool _needsReload { true };
|
bool _needsReload { true };
|
||||||
bool _needsUpdateClusterMatrices { true };
|
bool _needsUpdateClusterMatrices { true };
|
||||||
|
|
|
@ -673,11 +673,14 @@ void ScriptEngine::run() {
|
||||||
}
|
}
|
||||||
|
|
||||||
qint64 now = usecTimestampNow();
|
qint64 now = usecTimestampNow();
|
||||||
float deltaTime = (float) (now - lastUpdate) / (float) USECS_PER_SECOND;
|
|
||||||
|
|
||||||
if (!_isFinished) {
|
// we check for 'now' in the past in case people set their clock back
|
||||||
if (_wantSignals) {
|
if (lastUpdate < now) {
|
||||||
emit update(deltaTime);
|
float deltaTime = (float) (now - lastUpdate) / (float) USECS_PER_SECOND;
|
||||||
|
if (!_isFinished) {
|
||||||
|
if (_wantSignals) {
|
||||||
|
emit update(deltaTime);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lastUpdate = now;
|
lastUpdate = now;
|
||||||
|
|
|
@ -17,6 +17,8 @@
|
||||||
#include "GeometryUtil.h"
|
#include "GeometryUtil.h"
|
||||||
#include "NumericalConstants.h"
|
#include "NumericalConstants.h"
|
||||||
|
|
||||||
|
const glm::vec3 AABox::INFINITY_VECTOR(std::numeric_limits<float>::infinity());
|
||||||
|
|
||||||
AABox::AABox(const AACube& other) :
|
AABox::AABox(const AACube& other) :
|
||||||
_corner(other.getCorner()), _scale(other.getScale(), other.getScale(), other.getScale()) {
|
_corner(other.getCorner()), _scale(other.getScale(), other.getScale(), other.getScale()) {
|
||||||
}
|
}
|
||||||
|
@ -34,7 +36,7 @@ AABox::AABox(const glm::vec3& corner, const glm::vec3& dimensions) :
|
||||||
_corner(corner), _scale(dimensions) {
|
_corner(corner), _scale(dimensions) {
|
||||||
};
|
};
|
||||||
|
|
||||||
AABox::AABox() : _corner(std::numeric_limits<float>::infinity()), _scale(0.0f) {
|
AABox::AABox() : _corner(INFINITY_VECTOR), _scale(0.0f) {
|
||||||
};
|
};
|
||||||
|
|
||||||
glm::vec3 AABox::calcCenter() const {
|
glm::vec3 AABox::calcCenter() const {
|
||||||
|
@ -475,8 +477,15 @@ AABox AABox::clamp(float min, float max) const {
|
||||||
}
|
}
|
||||||
|
|
||||||
AABox& AABox::operator += (const glm::vec3& point) {
|
AABox& AABox::operator += (const glm::vec3& point) {
|
||||||
_corner = glm::min(_corner, point);
|
|
||||||
_scale = glm::max(_scale, point - _corner);
|
if (isInvalid()) {
|
||||||
|
_corner = glm::min(_corner, point);
|
||||||
|
} else {
|
||||||
|
glm::vec3 maximum(_corner + _scale);
|
||||||
|
_corner = glm::min(_corner, point);
|
||||||
|
maximum = glm::max(maximum, point);
|
||||||
|
_scale = maximum - _corner;
|
||||||
|
}
|
||||||
|
|
||||||
return (*this);
|
return (*this);
|
||||||
}
|
}
|
||||||
|
@ -484,17 +493,31 @@ AABox& AABox::operator += (const glm::vec3& point) {
|
||||||
AABox& AABox::operator += (const AABox& box) {
|
AABox& AABox::operator += (const AABox& box) {
|
||||||
if (!box.isInvalid()) {
|
if (!box.isInvalid()) {
|
||||||
(*this) += box._corner;
|
(*this) += box._corner;
|
||||||
_scale = glm::max(_scale, box.calcTopFarLeft() - _corner);
|
(*this) += box.calcTopFarLeft();
|
||||||
}
|
}
|
||||||
return (*this);
|
return (*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AABox::embiggen(float scale) {
|
||||||
|
_corner += scale * (-0.5f * _scale);
|
||||||
|
_scale *= scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AABox::embiggen(const glm::vec3& scale) {
|
||||||
|
_corner += scale * (-0.5f * _scale);
|
||||||
|
_scale *= scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AABox::scale(float scale) {
|
||||||
|
_corner *= scale;
|
||||||
|
_scale *= scale;
|
||||||
|
}
|
||||||
|
|
||||||
void AABox::scale(const glm::vec3& scale) {
|
void AABox::scale(const glm::vec3& scale) {
|
||||||
_corner *= scale;
|
_corner *= scale;
|
||||||
_scale *= scale;
|
_scale *= scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void AABox::rotate(const glm::quat& rotation) {
|
void AABox::rotate(const glm::quat& rotation) {
|
||||||
auto minimum = _corner;
|
auto minimum = _corner;
|
||||||
auto maximum = _corner + _scale;
|
auto maximum = _corner + _scale;
|
||||||
|
@ -544,3 +567,47 @@ void AABox::transform(const Transform& transform) {
|
||||||
rotate(transform.getRotation());
|
rotate(transform.getRotation());
|
||||||
translate(transform.getTranslation());
|
translate(transform.getTranslation());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AABox::transform(const glm::mat4& matrix) {
|
||||||
|
auto minimum = _corner;
|
||||||
|
auto maximum = _corner + _scale;
|
||||||
|
|
||||||
|
glm::vec3 bottomLeftNear(minimum.x, minimum.y, minimum.z);
|
||||||
|
glm::vec3 bottomRightNear(maximum.x, minimum.y, minimum.z);
|
||||||
|
glm::vec3 bottomLeftFar(minimum.x, minimum.y, maximum.z);
|
||||||
|
glm::vec3 bottomRightFar(maximum.x, minimum.y, maximum.z);
|
||||||
|
glm::vec3 topLeftNear(minimum.x, maximum.y, minimum.z);
|
||||||
|
glm::vec3 topRightNear(maximum.x, maximum.y, minimum.z);
|
||||||
|
glm::vec3 topLeftFar(minimum.x, maximum.y, maximum.z);
|
||||||
|
glm::vec3 topRightFar(maximum.x, maximum.y, maximum.z);
|
||||||
|
|
||||||
|
glm::vec3 bottomLeftNearTransformed = transformPoint(matrix, bottomLeftNear);
|
||||||
|
glm::vec3 bottomRightNearTransformed = transformPoint(matrix, bottomRightNear);
|
||||||
|
glm::vec3 bottomLeftFarTransformed = transformPoint(matrix, bottomLeftFar);
|
||||||
|
glm::vec3 bottomRightFarTransformed = transformPoint(matrix, bottomRightFar);
|
||||||
|
glm::vec3 topLeftNearTransformed = transformPoint(matrix, topLeftNear);
|
||||||
|
glm::vec3 topRightNearTransformed = transformPoint(matrix, topRightNear);
|
||||||
|
glm::vec3 topLeftFarTransformed = transformPoint(matrix, topLeftFar);
|
||||||
|
glm::vec3 topRightFarTransformed = transformPoint(matrix, topRightFar);
|
||||||
|
|
||||||
|
minimum = glm::min(bottomLeftNearTransformed,
|
||||||
|
glm::min(bottomRightNearTransformed,
|
||||||
|
glm::min(bottomLeftFarTransformed,
|
||||||
|
glm::min(bottomRightFarTransformed,
|
||||||
|
glm::min(topLeftNearTransformed,
|
||||||
|
glm::min(topRightNearTransformed,
|
||||||
|
glm::min(topLeftFarTransformed,
|
||||||
|
topRightFarTransformed)))))));
|
||||||
|
|
||||||
|
maximum = glm::max(bottomLeftNearTransformed,
|
||||||
|
glm::max(bottomRightNearTransformed,
|
||||||
|
glm::max(bottomLeftFarTransformed,
|
||||||
|
glm::max(bottomRightFarTransformed,
|
||||||
|
glm::max(topLeftNearTransformed,
|
||||||
|
glm::max(topRightNearTransformed,
|
||||||
|
glm::max(topLeftFarTransformed,
|
||||||
|
topRightFarTransformed)))))));
|
||||||
|
|
||||||
|
_corner = minimum;
|
||||||
|
_scale = maximum - minimum;
|
||||||
|
}
|
||||||
|
|
|
@ -58,7 +58,6 @@ public:
|
||||||
const glm::vec3& getMinimumPoint() const { return _corner; }
|
const glm::vec3& getMinimumPoint() const { return _corner; }
|
||||||
glm::vec3 getMaximumPoint() const { return calcTopFarLeft(); }
|
glm::vec3 getMaximumPoint() const { return calcTopFarLeft(); }
|
||||||
|
|
||||||
|
|
||||||
bool contains(const glm::vec3& point) const;
|
bool contains(const glm::vec3& point) const;
|
||||||
bool contains(const AABox& otherBox) const;
|
bool contains(const AABox& otherBox) const;
|
||||||
bool touches(const AABox& otherBox) const;
|
bool touches(const AABox& otherBox) const;
|
||||||
|
@ -93,10 +92,19 @@ public:
|
||||||
void scale(float scale);
|
void scale(float scale);
|
||||||
void scale(const glm::vec3& scale);
|
void scale(const glm::vec3& scale);
|
||||||
|
|
||||||
|
/// make the AABox bigger (scale about it's center)
|
||||||
|
void embiggen(float scale);
|
||||||
|
void embiggen(const glm::vec3& scale);
|
||||||
|
|
||||||
// Transform the extents with transform
|
// Transform the extents with transform
|
||||||
void transform(const Transform& transform);
|
void transform(const Transform& transform);
|
||||||
|
|
||||||
bool isInvalid() const { return _corner == glm::vec3(std::numeric_limits<float>::infinity()); }
|
// Transform the extents with matrix
|
||||||
|
void transform(const glm::mat4& matrix);
|
||||||
|
|
||||||
|
static const glm::vec3 INFINITY_VECTOR;
|
||||||
|
|
||||||
|
bool isInvalid() const { return _corner == INFINITY_VECTOR; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
glm::vec3 getClosestPointOnFace(const glm::vec3& point, BoxFace face) const;
|
glm::vec3 getClosestPointOnFace(const glm::vec3& point, BoxFace face) const;
|
||||||
|
|
|
@ -19,6 +19,10 @@
|
||||||
class SpatiallyNestable;
|
class SpatiallyNestable;
|
||||||
using SpatiallyNestableWeakPointer = std::weak_ptr<SpatiallyNestable>;
|
using SpatiallyNestableWeakPointer = std::weak_ptr<SpatiallyNestable>;
|
||||||
using SpatiallyNestablePointer = std::shared_ptr<SpatiallyNestable>;
|
using SpatiallyNestablePointer = std::shared_ptr<SpatiallyNestable>;
|
||||||
|
class SpatialParentTree {
|
||||||
|
public:
|
||||||
|
virtual SpatiallyNestablePointer findByID(const QUuid& id) { return nullptr; }
|
||||||
|
};
|
||||||
class SpatialParentFinder : public Dependency {
|
class SpatialParentFinder : public Dependency {
|
||||||
|
|
||||||
|
|
||||||
|
@ -31,7 +35,7 @@ public:
|
||||||
SpatialParentFinder() { }
|
SpatialParentFinder() { }
|
||||||
virtual ~SpatialParentFinder() { }
|
virtual ~SpatialParentFinder() { }
|
||||||
|
|
||||||
virtual SpatiallyNestableWeakPointer find(QUuid parentID, bool& success) const = 0;
|
virtual SpatiallyNestableWeakPointer find(QUuid parentID, bool& success, SpatialParentTree* entityTree = nullptr) const = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_SpatialParentFinder_h
|
#endif // hifi_SpatialParentFinder_h
|
||||||
|
|
|
@ -105,7 +105,7 @@ SpatiallyNestablePointer SpatiallyNestable::getParentPointer(bool& success) cons
|
||||||
success = false;
|
success = false;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
_parent = parentFinder->find(parentID, success);
|
_parent = parentFinder->find(parentID, success, getParentTree());
|
||||||
if (!success) {
|
if (!success) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
|
@ -140,6 +140,7 @@ public:
|
||||||
bool isDead() const { return _isDead; }
|
bool isDead() const { return _isDead; }
|
||||||
|
|
||||||
bool isParentIDValid() const { bool success = false; getParentPointer(success); return success; }
|
bool isParentIDValid() const { bool success = false; getParentPointer(success); return success; }
|
||||||
|
virtual SpatialParentTree* getParentTree() const { return nullptr; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
const NestableType _nestableType; // EntityItem or an AvatarData
|
const NestableType _nestableType; // EntityItem or an AvatarData
|
||||||
|
|
|
@ -151,3 +151,21 @@ void AABoxTests::testTouchesSphere() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AABoxTests::testScale() {
|
||||||
|
AABox box1(glm::vec3(2.0f), glm::vec3(1.0f));
|
||||||
|
QCOMPARE(box1.contains(glm::vec3(0.0f)), false);
|
||||||
|
box1.scale(glm::vec3(10.0f));
|
||||||
|
QCOMPARE(box1.contains(glm::vec3(0.0f)), false);
|
||||||
|
QCOMPARE(box1.contains(glm::vec3(2.0f * 10.0f)), true);
|
||||||
|
|
||||||
|
AABox box2(glm::vec3(2.0f), glm::vec3(1.0f));
|
||||||
|
QCOMPARE(box2.contains(glm::vec3(0.0f)), false);
|
||||||
|
box2.embiggen(glm::vec3(10.0f));
|
||||||
|
QCOMPARE(box2.contains(glm::vec3(0.0f)), true);
|
||||||
|
|
||||||
|
AABox box3;
|
||||||
|
box3 += glm::vec3(0.0f, 0.0f, 0.0f);
|
||||||
|
box3 += glm::vec3(1.0f, 1.0f, 1.0f);
|
||||||
|
box3 += glm::vec3(-1.0f, -1.0f, -1.0f);
|
||||||
|
QCOMPARE(box3.contains(glm::vec3(0.5f, 0.5f, 0.5f)), true);
|
||||||
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ private slots:
|
||||||
void testCtorsAndSetters();
|
void testCtorsAndSetters();
|
||||||
void testContainsPoint();
|
void testContainsPoint();
|
||||||
void testTouchesSphere();
|
void testTouchesSphere();
|
||||||
|
void testScale();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_AABoxTests_h
|
#endif // hifi_AABoxTests_h
|
||||||
|
|
Loading…
Reference in a new issue