//// Model.cpp// interface/src/renderer//// Created by Andrzej Kapolka on 10/18/13.// Copyright 2013 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 "Model.h" #include <QMetaType>#include <QRunnable>#include <QThreadPool> #include <glm/gtx/transform.hpp>#include <glm/gtx/norm.hpp> #include <shared/QtHelpers.h>#include <GeometryUtil.h>#include <PathUtils.h>#include <PerfStat.h>#include <ViewFrustum.h>#include <GLMHelpers.h>#include <TBBHelpers.h> #include <model-networking/SimpleMeshProxy.h>#include <graphics-scripting/Forward.h>#include <graphics/BufferViewHelpers.h>#include <DualQuaternion.h> #include <glm/gtc/packing.hpp> #include "AbstractViewStateInterface.h"#include "MeshPartPayload.h" #include "RenderUtilsLogging.h"#include <Trace.h> #include <BlendshapeConstants.h> using namespace std; int nakedModelPointerTypeId = qRegisterMetaType<ModelPointer>();int weakGeometryResourceBridgePointerTypeId = qRegisterMetaType<Geometry::WeakPointer>();int vec3VectorTypeId = qRegisterMetaType<QVector<glm::vec3>>();int normalTypeVecTypeId = qRegisterMetaType<QVector<NormalType>>("QVector<NormalType>");float Model::FAKE_DIMENSION_PLACEHOLDER = -1.0f;#define HTTP_INVALID_COM "http://invalid.com" Model::Model(QObject* parent, SpatiallyNestable* spatiallyNestableOverride, uint64_t created) : QObject(parent), _renderGeometry(), _renderWatcher(_renderGeometry), _spatiallyNestableOverride(spatiallyNestableOverride), _translation(0.0f), _rotation(), _scale(1.0f, 1.0f, 1.0f), _scaleToFit(false), _scaleToFitDimensions(1.0f), _scaledToFit(false), _snapModelToRegistrationPoint(false), _snappedToRegistrationPoint(false), _url(HTTP_INVALID_COM), _renderItemKeyGlobalFlags(render::ItemKey::Builder().withVisible().withTagBits(render::hifi::TAG_ALL_VIEWS).build()), _created(created){ // we may have been created in the network thread, but we live in the main thread if (_viewState) { moveToThread(_viewState->getMainThread()); } setSnapModelToRegistrationPoint(true, glm::vec3(0.5f)); connect(&_renderWatcher, &GeometryResourceWatcher::finished, this, &Model::loadURLFinished);} Model::~Model() { deleteGeometry();} AbstractViewStateInterface* Model::_viewState = NULL; bool Model::needsFixupInScene() const { return (_needsFixupInScene || !_addedToScene) && !_needsReload && isLoaded();} void Model::setTranslation(const glm::vec3& translation) { _translation = translation; updateRenderItems();} void Model::setRotation(const glm::quat& rotation) { _rotation = rotation; updateRenderItems();} // temporary HACK: set transform while avoiding implicit calls to updateRenderItems()// TODO: make setRotation() and friends set flag to be used later to decide to updateRenderItems()void Model::setTransformNoUpdateRenderItems(const Transform& transform) { _translation = transform.getTranslation(); _rotation = transform.getRotation(); // DO NOT call updateRenderItems() here!} Transform Model::getTransform() const { if (_overrideModelTransform) { Transform transform; transform.setTranslation(getOverrideTranslation()); transform.setRotation(getOverrideRotation()); transform.setScale(getScale()); return transform; } else if (_spatiallyNestableOverride) { bool success; Transform transform = _billboardMode == BillboardMode::NONE ? _spatiallyNestableOverride->getTransform(success) : _spatiallyNestableOverride->getTransformWithOnlyLocalRotation(success); if (success) { transform.setScale(getScale()); return transform; } } Transform transform; transform.setScale(getScale()); transform.setTranslation(getTranslation()); transform.setRotation(getRotation()); return transform;} void Model::setScale(const glm::vec3& scale) { setScaleInternal(scale); // if anyone sets scale manually, then we are no longer scaled to fit _scaleToFit = false; _scaledToFit = false;} const float SCALE_CHANGE_EPSILON = 0.0000001f; void Model::setScaleInternal(const glm::vec3& scale) { if (glm::distance(_scale, scale) > SCALE_CHANGE_EPSILON) { _scale = scale; assert(_scale.x != 0.0f && scale.y != 0.0f && scale.z != 0.0f); simulate(0.0f, true); }} void Model::setOffset(const glm::vec3& offset) { _offset = offset; // if someone manually sets our offset, then we are no longer snapped to center _snapModelToRegistrationPoint = false; _snappedToRegistrationPoint = false; _forceOffset = true;} void Model::calculateTextureInfo() { if (!_hasCalculatedTextureInfo && isLoaded() && getGeometry()->areTexturesLoaded() && !_modelMeshRenderItems.isEmpty()) { size_t textureSize = 0; int textureCount = 0; bool allTexturesLoaded = true; foreach(auto renderItem, _modelMeshRenderItems) { auto meshPart = renderItem.get(); textureSize += meshPart->getMaterialTextureSize(); textureCount += meshPart->getMaterialTextureCount(); allTexturesLoaded = allTexturesLoaded & meshPart->hasTextureInfo(); } _renderInfoTextureSize = textureSize; _renderInfoTextureCount = textureCount; _hasCalculatedTextureInfo = allTexturesLoaded; // only do this once }} size_t Model::getRenderInfoTextureSize() { calculateTextureInfo(); return _renderInfoTextureSize;} int Model::getRenderInfoTextureCount() { calculateTextureInfo(); return _renderInfoTextureCount;} bool Model::shouldInvalidatePayloadShapeKey(int meshIndex) { if (!getGeometry()) { return true; } const HFMModel& hfmModel = getHFMModel(); const auto& networkMeshes = getGeometry()->getMeshes(); // if our index is ever out of range for either meshes or networkMeshes, then skip it, and set our _meshGroupsKnown // to false to rebuild out mesh groups. if (meshIndex < 0 || meshIndex >= (int)networkMeshes.size() || meshIndex >= (int)hfmModel.meshes.size() || meshIndex >= (int)_meshStates.size()) { _needsFixupInScene = true; // trigger remove/add cycle invalidCalculatedMeshBoxes(); // if we have to reload, we need to assume our mesh boxes are all invalid return true; } return false;} void Model::updateRenderItems() { if (!_addedToScene) { return; } _needsUpdateClusterMatrices = true; _renderItemsNeedUpdate = false; // queue up this work for later processing, at the end of update and just before rendering. // the application will ensure only the last lambda is actually invoked. void* key = (void*)this; std::weak_ptr<Model> weakSelf = shared_from_this(); AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [weakSelf]() { // do nothing, if the model has already been destroyed. auto self = weakSelf.lock(); if (!self || !self->isLoaded()) { return; } // lazy update of cluster matrices used for rendering. // We need to update them here so we can correctly update the bounding box. self->updateClusterMatrices(); Transform modelTransform = self->getTransform(); modelTransform.setScale(glm::vec3(1.0f)); PrimitiveMode primitiveMode = self->getPrimitiveMode(); BillboardMode billboardMode = self->getBillboardMode(); auto renderWithZones = self->getRenderWithZones(); auto renderItemKeyGlobalFlags = self->getRenderItemKeyGlobalFlags(); bool cauterized = self->isCauterized(); auto mirrorMode = self->getMirrorMode(); const QUuid& portalExitID = self->getPortalExitID(); render::Transaction transaction; for (int i = 0; i < (int) self->_modelMeshRenderItemIDs.size(); i++) { auto itemID = self->_modelMeshRenderItemIDs[i]; auto meshIndex = self->_modelMeshRenderItemShapes[i].meshIndex; const auto& meshState = self->getMeshState(meshIndex); bool invalidatePayloadShapeKey = self->shouldInvalidatePayloadShapeKey(meshIndex); bool useDualQuaternionSkinning = self->getUseDualQuaternionSkinning(); transaction.updateItem<ModelMeshPartPayload>(itemID, [modelTransform, meshState, useDualQuaternionSkinning, invalidatePayloadShapeKey, primitiveMode, billboardMode, renderItemKeyGlobalFlags, cauterized, renderWithZones, mirrorMode, portalExitID](ModelMeshPartPayload& data) { if (useDualQuaternionSkinning) { data.updateClusterBuffer(meshState.clusterDualQuaternions); data.computeAdjustedLocalBound(meshState.clusterDualQuaternions); } else { data.updateClusterBuffer(meshState.clusterMatrices); data.computeAdjustedLocalBound(meshState.clusterMatrices); } data.updateTransformForSkinnedMesh(modelTransform, meshState, useDualQuaternionSkinning); data.setCauterized(cauterized); data.setRenderWithZones(renderWithZones); data.setBillboardMode(billboardMode); data.setMirrorMode(mirrorMode); data.setPortalExitID(portalExitID); data.updateKey(renderItemKeyGlobalFlags); data.setShapeKey(invalidatePayloadShapeKey, primitiveMode, useDualQuaternionSkinning); }); } AbstractViewStateInterface::instance()->getMain3DScene()->enqueueTransaction(transaction); });} void Model::setRenderItemsNeedUpdate() { _renderItemsNeedUpdate = true; emit requestRenderUpdate();} void Model::reset() { if (isLoaded()) { const HFMModel& hfmModel = getHFMModel(); _rig.reset(hfmModel); emit rigReset(); emit rigReady(); }} bool Model::updateGeometry() { bool needFullUpdate = false; if (!isLoaded()) { return false; } _needsReload = false; // TODO: should all Models have a valid _rig? if (_rig.jointStatesEmpty() && getHFMModel().joints.size() > 0) { initJointStates(); if (!_meshStates.empty()) { // See https://github.com/vircadia/vircadia/issues/958 for a discussion of the issues found here with // a previously existing assert, and the likely reasons for things going wrong here. // // TL;DR: There may be a threading issue, or it could be due to something lacking joints, which would cause // initJointStates() to fail to make _rig.jointStatesEmpty() false, causing things to end up here twice. // // In any case it appears to be safe to simply clear _meshStates here, even though this shouldn't happen. qCWarning(renderutils) << "_meshStates has" << _meshStates.size() << "items when it should have none. Model with URL " << _url.toString() << "; translation" << _translation << "; rotation" << _rotation << "; scale" << _scale << "; joint state count" << _rig.getJointStateCount() << "; type" << (modelProviderType == NestableType::Avatar ? "Avatar" : "Entity"); _meshStates.clear(); } const HFMModel& hfmModel = getHFMModel(); foreach (const HFMMesh& mesh, hfmModel.meshes) { MeshState state; state.clusterDualQuaternions.resize(mesh.clusters.size()); state.clusterMatrices.resize(mesh.clusters.size()); _meshStates.push_back(state); } needFullUpdate = true; emit rigReady(); } return needFullUpdate;} // virtualvoid Model::initJointStates() { const HFMModel& hfmModel = getHFMModel(); glm::mat4 modelOffset = glm::scale(_scale) * glm::translate(_offset); _rig.initJointStates(hfmModel, modelOffset);} bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& viewFrustumPos, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool pickAgainstTriangles, bool allowBackface) { bool intersectedSomething = false; // if we aren't active, we can't pick yet... if (!isLoaded()) { return intersectedSomething; } // extents is the entity relative, scaled, centered extents of the entity glm::quat rotation = BillboardModeHelpers::getBillboardRotation(_translation, _rotation, _billboardMode, viewFrustumPos); glm::mat4 transRot = createMatFromQuatAndPos(rotation, _translation); glm::mat4 modelToWorldMatrix = transRot; if (!_snapModelToRegistrationPoint) { modelToWorldMatrix = modelToWorldMatrix * glm::translate(getOriginalOffset()); } glm::mat4 worldToModelMatrix = glm::inverse(modelToWorldMatrix); Extents modelExtents = getMeshExtents(); // NOTE: unrotated glm::vec3 dimensions = modelExtents.maximum - modelExtents.minimum; glm::vec3 corner = -(dimensions * _registrationPoint); // since we're going to do the picking in the model frame of reference AABox modelFrameBox(corner, dimensions); glm::vec3 modelFrameOrigin = glm::vec3(worldToModelMatrix * glm::vec4(origin, 1.0f)); glm::vec3 modelFrameDirection = glm::vec3(worldToModelMatrix * glm::vec4(direction, 0.0f)); // we can use the AABox's intersection by mapping our origin and direction into the model frame // and testing intersection there. if (modelFrameBox.findRayIntersection(modelFrameOrigin, modelFrameDirection, 1.0f / modelFrameDirection, distance, face, surfaceNormal)) { QMutexLocker locker(&_mutex); float bestDistance = FLT_MAX; BoxFace bestFace; Triangle bestModelTriangle; Triangle bestWorldTriangle; glm::vec3 bestWorldIntersectionPoint; glm::vec3 bestMeshIntersectionPoint; int bestPartIndex = 0; int bestShapeID = 0; int bestSubMeshIndex = 0; const HFMModel& hfmModel = getHFMModel(); if (!_triangleSetsValid) { calculateTriangleSets(hfmModel); } glm::mat4 meshToWorldMatrix = transRot; if (_snapModelToRegistrationPoint || _forceOffset) { meshToWorldMatrix = meshToWorldMatrix * (glm::scale(_scale) * glm::translate(_offset)); } else { meshToWorldMatrix = meshToWorldMatrix * (glm::scale(_scale) * glm::translate(getNaturalDimensions() * (0.5f - _registrationPoint))); } glm::mat4 worldToMeshMatrix = glm::inverse(meshToWorldMatrix); glm::vec3 meshFrameOrigin = glm::vec3(worldToMeshMatrix * glm::vec4(origin, 1.0f)); glm::vec3 meshFrameDirection = glm::vec3(worldToMeshMatrix * glm::vec4(direction, 0.0f)); glm::vec3 meshFrameInvDirection = 1.0f / meshFrameDirection; int shapeID = 0; int subMeshIndex = 0; std::vector<SortedTriangleSet> sortedTriangleSets; for (auto& meshTriangleSets : _modelSpaceMeshTriangleSets) { int partIndex = 0; for (auto& partTriangleSet : meshTriangleSets) { float priority = FLT_MAX; if (partTriangleSet.getBounds().contains(meshFrameOrigin)) { priority = 0.0f; } else { float partBoundDistance = FLT_MAX; BoxFace partBoundFace; glm::vec3 partBoundNormal; if (partTriangleSet.getBounds().findRayIntersection(meshFrameOrigin, meshFrameDirection, meshFrameInvDirection, partBoundDistance, partBoundFace, partBoundNormal)) { priority = partBoundDistance; } } if (priority < FLT_MAX) { sortedTriangleSets.emplace_back(priority, &partTriangleSet, partIndex, shapeID, subMeshIndex); } partIndex++; shapeID++; } subMeshIndex++; } if (sortedTriangleSets.size() > 1) { static auto comparator = [](const SortedTriangleSet& left, const SortedTriangleSet& right) { return left.distance < right.distance; }; std::sort(sortedTriangleSets.begin(), sortedTriangleSets.end(), comparator); } for (auto it = sortedTriangleSets.begin(); it != sortedTriangleSets.end(); ++it) { const SortedTriangleSet& sortedTriangleSet = *it; // We can exit once triangleSetDistance > bestDistance if (sortedTriangleSet.distance > bestDistance) { break; } float triangleSetDistance = FLT_MAX; BoxFace triangleSetFace; Triangle triangleSetTriangle; if (sortedTriangleSet.triangleSet->findRayIntersection(meshFrameOrigin, meshFrameDirection, meshFrameInvDirection, triangleSetDistance, triangleSetFace, triangleSetTriangle, pickAgainstTriangles, allowBackface)) { if (triangleSetDistance < bestDistance) { bestDistance = triangleSetDistance; intersectedSomething = true; bestFace = triangleSetFace; bestModelTriangle = triangleSetTriangle; bestWorldTriangle = triangleSetTriangle * meshToWorldMatrix; glm::vec3 meshIntersectionPoint = meshFrameOrigin + (meshFrameDirection * triangleSetDistance); glm::vec3 worldIntersectionPoint = glm::vec3(meshToWorldMatrix * glm::vec4(meshIntersectionPoint, 1.0f)); bestWorldIntersectionPoint = worldIntersectionPoint; bestMeshIntersectionPoint = meshIntersectionPoint; bestPartIndex = sortedTriangleSet.partIndex; bestShapeID = sortedTriangleSet.shapeID; bestSubMeshIndex = sortedTriangleSet.subMeshIndex; } } } /*@jsdoc * A submesh intersection point. * @typedef {object} SubmeshIntersection * @property {Vec3} worldIntersectionPoint - The intersection point in world coordinates. * @property {Vec3} meshIntersectionPoint - The intersection point in model coordinates. * @property {number} partIndex - The index of the intersected mesh part within the submesh. * @property {number} shapeID - The index of the mesh part within the model. * @property {number} subMeshIndex - The index of the intersected submesh within the model. * @property {string} subMeshName - The name of the intersected submesh. * @property {Triangle} subMeshTriangleWorld - The vertices of the intersected mesh part triangle in world coordinates. * @property {Vec3} subMeshNormal - The normal of the intersected mesh part triangle in model coordinates. * @property {Triangle} subMeshTriangle - The vertices of the intersected mesh part triangle in model coordinates. */ if (intersectedSomething) { distance = bestDistance; face = bestFace; surfaceNormal = bestWorldTriangle.getNormal(); extraInfo["worldIntersectionPoint"] = vec3toVariant(bestWorldIntersectionPoint); extraInfo["meshIntersectionPoint"] = vec3toVariant(bestMeshIntersectionPoint); extraInfo["partIndex"] = bestPartIndex; extraInfo["shapeID"] = bestShapeID; if (pickAgainstTriangles) { extraInfo["subMeshIndex"] = bestSubMeshIndex; extraInfo["subMeshName"] = hfmModel.getModelNameOfMesh(bestSubMeshIndex); extraInfo["subMeshTriangleWorld"] = QVariantMap{ { "v0", vec3toVariant(bestWorldTriangle.v0) }, { "v1", vec3toVariant(bestWorldTriangle.v1) }, { "v2", vec3toVariant(bestWorldTriangle.v2) }, }; extraInfo["subMeshNormal"] = vec3toVariant(bestModelTriangle.getNormal()); extraInfo["subMeshTriangle"] = QVariantMap{ { "v0", vec3toVariant(bestModelTriangle.v0) }, { "v1", vec3toVariant(bestModelTriangle.v1) }, { "v2", vec3toVariant(bestModelTriangle.v2) }, }; } } } Lines 1–500 of 2061. Load more
// // Model.cpp // interface/src/renderer // // Created by Andrzej Kapolka on 10/18/13. // Copyright 2013 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 "Model.h" #include <QMetaType> #include <QRunnable> #include <QThreadPool> #include <glm/gtx/transform.hpp> #include <glm/gtx/norm.hpp> #include <shared/QtHelpers.h> #include <GeometryUtil.h> #include <PathUtils.h> #include <PerfStat.h> #include <ViewFrustum.h> #include <GLMHelpers.h> #include <TBBHelpers.h> #include <model-networking/SimpleMeshProxy.h> #include <graphics-scripting/Forward.h> #include <graphics/BufferViewHelpers.h> #include <DualQuaternion.h> #include <glm/gtc/packing.hpp> #include "AbstractViewStateInterface.h" #include "MeshPartPayload.h" #include "RenderUtilsLogging.h" #include <Trace.h> #include <BlendshapeConstants.h> using namespace std; int nakedModelPointerTypeId = qRegisterMetaType<ModelPointer>(); int weakGeometryResourceBridgePointerTypeId = qRegisterMetaType<Geometry::WeakPointer>(); int vec3VectorTypeId = qRegisterMetaType<QVector<glm::vec3>>(); int normalTypeVecTypeId = qRegisterMetaType<QVector<NormalType>>("QVector<NormalType>"); float Model::FAKE_DIMENSION_PLACEHOLDER = -1.0f; #define HTTP_INVALID_COM "http://invalid.com" Model::Model(QObject* parent, SpatiallyNestable* spatiallyNestableOverride, uint64_t created) : QObject(parent), _renderGeometry(), _renderWatcher(_renderGeometry), _spatiallyNestableOverride(spatiallyNestableOverride), _translation(0.0f), _rotation(), _scale(1.0f, 1.0f, 1.0f), _scaleToFit(false), _scaleToFitDimensions(1.0f), _scaledToFit(false), _snapModelToRegistrationPoint(false), _snappedToRegistrationPoint(false), _url(HTTP_INVALID_COM), _renderItemKeyGlobalFlags(render::ItemKey::Builder().withVisible().withTagBits(render::hifi::TAG_ALL_VIEWS).build()), _created(created) { // we may have been created in the network thread, but we live in the main thread if (_viewState) { moveToThread(_viewState->getMainThread()); } setSnapModelToRegistrationPoint(true, glm::vec3(0.5f)); connect(&_renderWatcher, &GeometryResourceWatcher::finished, this, &Model::loadURLFinished); } Model::~Model() { deleteGeometry(); } AbstractViewStateInterface* Model::_viewState = NULL; bool Model::needsFixupInScene() const { return (_needsFixupInScene || !_addedToScene) && !_needsReload && isLoaded(); } void Model::setTranslation(const glm::vec3& translation) { _translation = translation; updateRenderItems(); } void Model::setRotation(const glm::quat& rotation) { _rotation = rotation; updateRenderItems(); } // temporary HACK: set transform while avoiding implicit calls to updateRenderItems() // TODO: make setRotation() and friends set flag to be used later to decide to updateRenderItems() void Model::setTransformNoUpdateRenderItems(const Transform& transform) { _translation = transform.getTranslation(); _rotation = transform.getRotation(); // DO NOT call updateRenderItems() here! } Transform Model::getTransform() const { if (_overrideModelTransform) { Transform transform; transform.setTranslation(getOverrideTranslation()); transform.setRotation(getOverrideRotation()); transform.setScale(getScale()); return transform; } else if (_spatiallyNestableOverride) { bool success; Transform transform = _billboardMode == BillboardMode::NONE ? _spatiallyNestableOverride->getTransform(success) : _spatiallyNestableOverride->getTransformWithOnlyLocalRotation(success); if (success) { transform.setScale(getScale()); return transform; } } Transform transform; transform.setScale(getScale()); transform.setTranslation(getTranslation()); transform.setRotation(getRotation()); return transform; } void Model::setScale(const glm::vec3& scale) { setScaleInternal(scale); // if anyone sets scale manually, then we are no longer scaled to fit _scaleToFit = false; _scaledToFit = false; } const float SCALE_CHANGE_EPSILON = 0.0000001f; void Model::setScaleInternal(const glm::vec3& scale) { if (glm::distance(_scale, scale) > SCALE_CHANGE_EPSILON) { _scale = scale; assert(_scale.x != 0.0f && scale.y != 0.0f && scale.z != 0.0f); simulate(0.0f, true); } } void Model::setOffset(const glm::vec3& offset) { _offset = offset; // if someone manually sets our offset, then we are no longer snapped to center _snapModelToRegistrationPoint = false; _snappedToRegistrationPoint = false; _forceOffset = true; } void Model::calculateTextureInfo() { if (!_hasCalculatedTextureInfo && isLoaded() && getGeometry()->areTexturesLoaded() && !_modelMeshRenderItems.isEmpty()) { size_t textureSize = 0; int textureCount = 0; bool allTexturesLoaded = true; foreach(auto renderItem, _modelMeshRenderItems) { auto meshPart = renderItem.get(); textureSize += meshPart->getMaterialTextureSize(); textureCount += meshPart->getMaterialTextureCount(); allTexturesLoaded = allTexturesLoaded & meshPart->hasTextureInfo(); } _renderInfoTextureSize = textureSize; _renderInfoTextureCount = textureCount; _hasCalculatedTextureInfo = allTexturesLoaded; // only do this once } } size_t Model::getRenderInfoTextureSize() { calculateTextureInfo(); return _renderInfoTextureSize; } int Model::getRenderInfoTextureCount() { calculateTextureInfo(); return _renderInfoTextureCount; } bool Model::shouldInvalidatePayloadShapeKey(int meshIndex) { if (!getGeometry()) { return true; } const HFMModel& hfmModel = getHFMModel(); const auto& networkMeshes = getGeometry()->getMeshes(); // if our index is ever out of range for either meshes or networkMeshes, then skip it, and set our _meshGroupsKnown // to false to rebuild out mesh groups. if (meshIndex < 0 || meshIndex >= (int)networkMeshes.size() || meshIndex >= (int)hfmModel.meshes.size() || meshIndex >= (int)_meshStates.size()) { _needsFixupInScene = true; // trigger remove/add cycle invalidCalculatedMeshBoxes(); // if we have to reload, we need to assume our mesh boxes are all invalid return true; } return false; } void Model::updateRenderItems() { if (!_addedToScene) { return; } _needsUpdateClusterMatrices = true; _renderItemsNeedUpdate = false; // queue up this work for later processing, at the end of update and just before rendering. // the application will ensure only the last lambda is actually invoked. void* key = (void*)this; std::weak_ptr<Model> weakSelf = shared_from_this(); AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [weakSelf]() { // do nothing, if the model has already been destroyed. auto self = weakSelf.lock(); if (!self || !self->isLoaded()) { return; } // lazy update of cluster matrices used for rendering. // We need to update them here so we can correctly update the bounding box. self->updateClusterMatrices(); Transform modelTransform = self->getTransform(); modelTransform.setScale(glm::vec3(1.0f)); PrimitiveMode primitiveMode = self->getPrimitiveMode(); BillboardMode billboardMode = self->getBillboardMode(); auto renderWithZones = self->getRenderWithZones(); auto renderItemKeyGlobalFlags = self->getRenderItemKeyGlobalFlags(); bool cauterized = self->isCauterized(); auto mirrorMode = self->getMirrorMode(); const QUuid& portalExitID = self->getPortalExitID(); render::Transaction transaction; for (int i = 0; i < (int) self->_modelMeshRenderItemIDs.size(); i++) { auto itemID = self->_modelMeshRenderItemIDs[i]; auto meshIndex = self->_modelMeshRenderItemShapes[i].meshIndex; const auto& meshState = self->getMeshState(meshIndex); bool invalidatePayloadShapeKey = self->shouldInvalidatePayloadShapeKey(meshIndex); bool useDualQuaternionSkinning = self->getUseDualQuaternionSkinning(); transaction.updateItem<ModelMeshPartPayload>(itemID, [modelTransform, meshState, useDualQuaternionSkinning, invalidatePayloadShapeKey, primitiveMode, billboardMode, renderItemKeyGlobalFlags, cauterized, renderWithZones, mirrorMode, portalExitID](ModelMeshPartPayload& data) { if (useDualQuaternionSkinning) { data.updateClusterBuffer(meshState.clusterDualQuaternions); data.computeAdjustedLocalBound(meshState.clusterDualQuaternions); } else { data.updateClusterBuffer(meshState.clusterMatrices); data.computeAdjustedLocalBound(meshState.clusterMatrices); } data.updateTransformForSkinnedMesh(modelTransform, meshState, useDualQuaternionSkinning); data.setCauterized(cauterized); data.setRenderWithZones(renderWithZones); data.setBillboardMode(billboardMode); data.setMirrorMode(mirrorMode); data.setPortalExitID(portalExitID); data.updateKey(renderItemKeyGlobalFlags); data.setShapeKey(invalidatePayloadShapeKey, primitiveMode, useDualQuaternionSkinning); }); } AbstractViewStateInterface::instance()->getMain3DScene()->enqueueTransaction(transaction); }); } void Model::setRenderItemsNeedUpdate() { _renderItemsNeedUpdate = true; emit requestRenderUpdate(); } void Model::reset() { if (isLoaded()) { const HFMModel& hfmModel = getHFMModel(); _rig.reset(hfmModel); emit rigReset(); emit rigReady(); } } bool Model::updateGeometry() { bool needFullUpdate = false; if (!isLoaded()) { return false; } _needsReload = false; // TODO: should all Models have a valid _rig? if (_rig.jointStatesEmpty() && getHFMModel().joints.size() > 0) { initJointStates(); if (!_meshStates.empty()) { // See https://github.com/vircadia/vircadia/issues/958 for a discussion of the issues found here with // a previously existing assert, and the likely reasons for things going wrong here. // // TL;DR: There may be a threading issue, or it could be due to something lacking joints, which would cause // initJointStates() to fail to make _rig.jointStatesEmpty() false, causing things to end up here twice. // // In any case it appears to be safe to simply clear _meshStates here, even though this shouldn't happen. qCWarning(renderutils) << "_meshStates has" << _meshStates.size() << "items when it should have none. Model with URL " << _url.toString() << "; translation" << _translation << "; rotation" << _rotation << "; scale" << _scale << "; joint state count" << _rig.getJointStateCount() << "; type" << (modelProviderType == NestableType::Avatar ? "Avatar" : "Entity"); _meshStates.clear(); } const HFMModel& hfmModel = getHFMModel(); foreach (const HFMMesh& mesh, hfmModel.meshes) { MeshState state; state.clusterDualQuaternions.resize(mesh.clusters.size()); state.clusterMatrices.resize(mesh.clusters.size()); _meshStates.push_back(state); } needFullUpdate = true; emit rigReady(); } return needFullUpdate; } // virtual void Model::initJointStates() { const HFMModel& hfmModel = getHFMModel(); glm::mat4 modelOffset = glm::scale(_scale) * glm::translate(_offset); _rig.initJointStates(hfmModel, modelOffset); } bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& viewFrustumPos, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool pickAgainstTriangles, bool allowBackface) { bool intersectedSomething = false; // if we aren't active, we can't pick yet... if (!isLoaded()) { return intersectedSomething; } // extents is the entity relative, scaled, centered extents of the entity glm::quat rotation = BillboardModeHelpers::getBillboardRotation(_translation, _rotation, _billboardMode, viewFrustumPos); glm::mat4 transRot = createMatFromQuatAndPos(rotation, _translation); glm::mat4 modelToWorldMatrix = transRot; if (!_snapModelToRegistrationPoint) { modelToWorldMatrix = modelToWorldMatrix * glm::translate(getOriginalOffset()); } glm::mat4 worldToModelMatrix = glm::inverse(modelToWorldMatrix); Extents modelExtents = getMeshExtents(); // NOTE: unrotated glm::vec3 dimensions = modelExtents.maximum - modelExtents.minimum; glm::vec3 corner = -(dimensions * _registrationPoint); // since we're going to do the picking in the model frame of reference AABox modelFrameBox(corner, dimensions); glm::vec3 modelFrameOrigin = glm::vec3(worldToModelMatrix * glm::vec4(origin, 1.0f)); glm::vec3 modelFrameDirection = glm::vec3(worldToModelMatrix * glm::vec4(direction, 0.0f)); // we can use the AABox's intersection by mapping our origin and direction into the model frame // and testing intersection there. if (modelFrameBox.findRayIntersection(modelFrameOrigin, modelFrameDirection, 1.0f / modelFrameDirection, distance, face, surfaceNormal)) { QMutexLocker locker(&_mutex); float bestDistance = FLT_MAX; BoxFace bestFace; Triangle bestModelTriangle; Triangle bestWorldTriangle; glm::vec3 bestWorldIntersectionPoint; glm::vec3 bestMeshIntersectionPoint; int bestPartIndex = 0; int bestShapeID = 0; int bestSubMeshIndex = 0; const HFMModel& hfmModel = getHFMModel(); if (!_triangleSetsValid) { calculateTriangleSets(hfmModel); } glm::mat4 meshToWorldMatrix = transRot; if (_snapModelToRegistrationPoint || _forceOffset) { meshToWorldMatrix = meshToWorldMatrix * (glm::scale(_scale) * glm::translate(_offset)); } else { meshToWorldMatrix = meshToWorldMatrix * (glm::scale(_scale) * glm::translate(getNaturalDimensions() * (0.5f - _registrationPoint))); } glm::mat4 worldToMeshMatrix = glm::inverse(meshToWorldMatrix); glm::vec3 meshFrameOrigin = glm::vec3(worldToMeshMatrix * glm::vec4(origin, 1.0f)); glm::vec3 meshFrameDirection = glm::vec3(worldToMeshMatrix * glm::vec4(direction, 0.0f)); glm::vec3 meshFrameInvDirection = 1.0f / meshFrameDirection; int shapeID = 0; int subMeshIndex = 0; std::vector<SortedTriangleSet> sortedTriangleSets; for (auto& meshTriangleSets : _modelSpaceMeshTriangleSets) { int partIndex = 0; for (auto& partTriangleSet : meshTriangleSets) { float priority = FLT_MAX; if (partTriangleSet.getBounds().contains(meshFrameOrigin)) { priority = 0.0f; } else { float partBoundDistance = FLT_MAX; BoxFace partBoundFace; glm::vec3 partBoundNormal; if (partTriangleSet.getBounds().findRayIntersection(meshFrameOrigin, meshFrameDirection, meshFrameInvDirection, partBoundDistance, partBoundFace, partBoundNormal)) { priority = partBoundDistance; } } if (priority < FLT_MAX) { sortedTriangleSets.emplace_back(priority, &partTriangleSet, partIndex, shapeID, subMeshIndex); } partIndex++; shapeID++; } subMeshIndex++; } if (sortedTriangleSets.size() > 1) { static auto comparator = [](const SortedTriangleSet& left, const SortedTriangleSet& right) { return left.distance < right.distance; }; std::sort(sortedTriangleSets.begin(), sortedTriangleSets.end(), comparator); } for (auto it = sortedTriangleSets.begin(); it != sortedTriangleSets.end(); ++it) { const SortedTriangleSet& sortedTriangleSet = *it; // We can exit once triangleSetDistance > bestDistance if (sortedTriangleSet.distance > bestDistance) { break; } float triangleSetDistance = FLT_MAX; BoxFace triangleSetFace; Triangle triangleSetTriangle; if (sortedTriangleSet.triangleSet->findRayIntersection(meshFrameOrigin, meshFrameDirection, meshFrameInvDirection, triangleSetDistance, triangleSetFace, triangleSetTriangle, pickAgainstTriangles, allowBackface)) { if (triangleSetDistance < bestDistance) { bestDistance = triangleSetDistance; intersectedSomething = true; bestFace = triangleSetFace; bestModelTriangle = triangleSetTriangle; bestWorldTriangle = triangleSetTriangle * meshToWorldMatrix; glm::vec3 meshIntersectionPoint = meshFrameOrigin + (meshFrameDirection * triangleSetDistance); glm::vec3 worldIntersectionPoint = glm::vec3(meshToWorldMatrix * glm::vec4(meshIntersectionPoint, 1.0f)); bestWorldIntersectionPoint = worldIntersectionPoint; bestMeshIntersectionPoint = meshIntersectionPoint; bestPartIndex = sortedTriangleSet.partIndex; bestShapeID = sortedTriangleSet.shapeID; bestSubMeshIndex = sortedTriangleSet.subMeshIndex; } } } /*@jsdoc * A submesh intersection point. * @typedef {object} SubmeshIntersection * @property {Vec3} worldIntersectionPoint - The intersection point in world coordinates. * @property {Vec3} meshIntersectionPoint - The intersection point in model coordinates. * @property {number} partIndex - The index of the intersected mesh part within the submesh. * @property {number} shapeID - The index of the mesh part within the model. * @property {number} subMeshIndex - The index of the intersected submesh within the model. * @property {string} subMeshName - The name of the intersected submesh. * @property {Triangle} subMeshTriangleWorld - The vertices of the intersected mesh part triangle in world coordinates. * @property {Vec3} subMeshNormal - The normal of the intersected mesh part triangle in model coordinates. * @property {Triangle} subMeshTriangle - The vertices of the intersected mesh part triangle in model coordinates. */ if (intersectedSomething) { distance = bestDistance; face = bestFace; surfaceNormal = bestWorldTriangle.getNormal(); extraInfo["worldIntersectionPoint"] = vec3toVariant(bestWorldIntersectionPoint); extraInfo["meshIntersectionPoint"] = vec3toVariant(bestMeshIntersectionPoint); extraInfo["partIndex"] = bestPartIndex; extraInfo["shapeID"] = bestShapeID; if (pickAgainstTriangles) { extraInfo["subMeshIndex"] = bestSubMeshIndex; extraInfo["subMeshName"] = hfmModel.getModelNameOfMesh(bestSubMeshIndex); extraInfo["subMeshTriangleWorld"] = QVariantMap{ { "v0", vec3toVariant(bestWorldTriangle.v0) }, { "v1", vec3toVariant(bestWorldTriangle.v1) }, { "v2", vec3toVariant(bestWorldTriangle.v2) }, }; extraInfo["subMeshNormal"] = vec3toVariant(bestModelTriangle.getNormal()); extraInfo["subMeshTriangle"] = QVariantMap{ { "v0", vec3toVariant(bestModelTriangle.v0) }, { "v1", vec3toVariant(bestModelTriangle.v1) }, { "v2", vec3toVariant(bestModelTriangle.v2) }, }; } } }