//// 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 <QMetaType>#include <QRunnable>#include <QThreadPool> #include <glm/gtx/transform.hpp>#include <glm/gtx/norm.hpp> #include <GeometryUtil.h>#include <PathUtils.h>#include <PerfStat.h>#include <ViewFrustum.h>#include <GLMHelpers.h> #include "AbstractViewStateInterface.h"#include "MeshPartPayload.h"#include "Model.h" #include "RenderUtilsLogging.h"#include <Trace.h> using namespace std; int nakedModelPointerTypeId = qRegisterMetaType<ModelPointer>();int weakGeometryResourceBridgePointerTypeId = qRegisterMetaType<Geometry::WeakPointer >();int vec3VectorTypeId = qRegisterMetaType<QVector<glm::vec3> >();float Model::FAKE_DIMENSION_PLACEHOLDER = -1.0f;#define HTTP_INVALID_COM "http://invalid.com" const int NUM_COLLISION_HULL_COLORS = 24;std::vector<model::MaterialPointer> _collisionMaterials; void initCollisionMaterials() { // generates bright colors in red, green, blue, yellow, magenta, and cyan spectrums // (no browns, greys, or dark shades) float component[NUM_COLLISION_HULL_COLORS] = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.2f, 0.4f, 0.6f, 0.8f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.8f, 0.6f, 0.4f, 0.2f }; _collisionMaterials.reserve(NUM_COLLISION_HULL_COLORS); // each component gets the same cuve // but offset by a multiple of one third the full width int numComponents = 3; int sectionWidth = NUM_COLLISION_HULL_COLORS / numComponents; int greenPhase = sectionWidth; int bluePhase = 2 * sectionWidth; // we stride through the colors to scatter adjacent shades // so they don't tend to group together for large models for (int i = 0; i < sectionWidth; ++i) { for (int j = 0; j < numComponents; ++j) { model::MaterialPointer material; material = std::make_shared<model::Material>(); int index = j * sectionWidth + i; float red = component[index]; float green = component[(index + greenPhase) % NUM_COLLISION_HULL_COLORS]; float blue = component[(index + bluePhase) % NUM_COLLISION_HULL_COLORS]; material->setAlbedo(glm::vec3(red, green, blue)); material->setMetallic(0.02f); material->setRoughness(0.5f); _collisionMaterials.push_back(material); } }} Model::Model(QObject* parent, SpatiallyNestable* spatiallyNestableOverride) : QObject(parent), _renderGeometry(), _collisionGeometry(), _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), _isVisible(true), _blendNumber(0), _appliedBlendNumber(0), _isWireframe(false){ // 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();} // TODO?: should we combine translation and rotation into single method to avoid double-work?// (figure out where we call these)void Model::setTranslation(const glm::vec3& translation) { _translation = translation; updateRenderItems();} void Model::setRotation(const glm::quat& rotation) { _rotation = rotation; updateRenderItems();} Transform Model::getTransform() const { if (_spatiallyNestableOverride) { bool success; Transform transform = _spatiallyNestableOverride->getTransform(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.01f; void Model::setScaleInternal(const glm::vec3& scale) { if (glm::distance(_scale, scale) > SCALE_CHANGE_EPSILON) { _scale = scale; if (_scale.x == 0.0f || _scale.y == 0.0f || _scale.z == 0.0f) { assert(false); } 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;} void Model::calculateTextureInfo() { if (!_hasCalculatedTextureInfo && isLoaded() && getGeometry()->areTexturesLoaded() && !_modelMeshRenderItemsMap.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;} void Model::updateRenderItems() { if (!_addedToScene) { return; } glm::vec3 scale = getScale(); if (_collisionGeometry) { // _collisionGeometry is already scaled scale = glm::vec3(1.0f); } _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, scale]() { // do nothing, if the model has already been destroyed. auto self = weakSelf.lock(); if (!self) { 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(); uint32_t deleteGeometryCounter = self->_deleteGeometryCounter; render::Transaction transaction; foreach (auto itemID, self->_modelMeshRenderItemsMap.keys()) { transaction.updateItem<ModelMeshPartPayload>(itemID, [deleteGeometryCounter](ModelMeshPartPayload& data) { ModelPointer model = data._model.lock(); if (model && model->isLoaded()) { // Ensure the model geometry was not reset between frames if (deleteGeometryCounter == model->_deleteGeometryCounter) { Transform modelTransform = model->getTransform(); modelTransform.setScale(glm::vec3(1.0f)); const Model::MeshState& state = model->getMeshState(data._meshIndex); Transform renderTransform = modelTransform; if (state.clusterMatrices.size() == 1) { renderTransform = modelTransform.worldTransform(Transform(state.clusterMatrices[0])); } data.updateTransformForSkinnedMesh(renderTransform, modelTransform, state.clusterBuffer); } } }); } // collision mesh does not share the same unit scale as the FBX file's mesh: only apply offset Transform collisionMeshOffset; collisionMeshOffset.setIdentity(); Transform modelTransform = self->getTransform(); foreach(auto itemID, self->_collisionRenderItemsMap.keys()) { transaction.updateItem<MeshPartPayload>(itemID, [modelTransform, collisionMeshOffset](MeshPartPayload& data) { // update the model transform for this render item. data.updateTransform(modelTransform, collisionMeshOffset); }); } AbstractViewStateInterface::instance()->getMain3DScene()->enqueueTransaction(transaction); });} void Model::initJointTransforms() { if (isLoaded()) { glm::mat4 modelOffset = glm::scale(_scale) * glm::translate(_offset); _rig.setModelOffset(modelOffset); }} void Model::init() {} void Model::reset() { if (isLoaded()) { const FBXGeometry& geometry = getFBXGeometry(); _rig.reset(geometry); }} bool Model::updateGeometry() { bool needFullUpdate = false; if (!isLoaded()) { return false; } _needsReload = false; // TODO: should all Models have a valid _rig? if (_rig.jointStatesEmpty() && getFBXGeometry().joints.size() > 0) { initJointStates(); assert(_meshStates.empty()); const FBXGeometry& fbxGeometry = getFBXGeometry(); foreach (const FBXMesh& mesh, fbxGeometry.meshes) { MeshState state; state.clusterMatrices.resize(mesh.clusters.size()); _meshStates.append(state); // Note: we add empty buffers for meshes that lack blendshapes so we can access the buffers by index // later in ModelMeshPayload, however the vast majority of meshes will not have them. // TODO? make _blendedVertexBuffers a map instead of vector and only add for meshes with blendshapes? auto buffer = std::make_shared<gpu::Buffer>(); if (!mesh.blendshapes.isEmpty()) { buffer->resize((mesh.vertices.size() + mesh.normals.size()) * sizeof(glm::vec3)); buffer->setSubData(0, mesh.vertices.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.vertices.constData()); buffer->setSubData(mesh.vertices.size() * sizeof(glm::vec3), mesh.normals.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.normals.constData()); } _blendedVertexBuffers.push_back(buffer); } needFullUpdate = true; } return needFullUpdate;} // virtualvoid Model::initJointStates() { const FBXGeometry& geometry = getFBXGeometry(); glm::mat4 modelOffset = glm::scale(_scale) * glm::translate(_offset); _rig.initJointStates(geometry, modelOffset);} bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QString& extraInfo, bool pickAgainstTriangles, bool allowBackface) { bool intersectedSomething = false; // if we aren't active, we can't ray pick yet... if (!isActive()) { return intersectedSomething; } // extents is the entity relative, scaled, centered extents of the entity glm::vec3 position = _translation; glm::mat4 rotation = glm::mat4_cast(_rotation); glm::mat4 translation = glm::translate(position); glm::mat4 modelToWorldMatrix = translation * rotation; 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 ray 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 ray intersection by mapping our origin and direction into the model frame // and testing intersection there. if (modelFrameBox.findRayIntersection(modelFrameOrigin, modelFrameDirection, distance, face, surfaceNormal)) { QMutexLocker locker(&_mutex); float bestDistance = std::numeric_limits<float>::max(); int subMeshIndex = 0; const FBXGeometry& geometry = getFBXGeometry(); if (!_triangleSetsValid) { calculateTriangleSets(); } glm::mat4 meshToModelMatrix = glm::scale(_scale) * glm::translate(_offset); glm::mat4 meshToWorldMatrix = createMatFromQuatAndPos(_rotation, _translation) * meshToModelMatrix; 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)); for (auto& triangleSet : _modelSpaceMeshTriangleSets) { float triangleSetDistance = 0.0f; BoxFace triangleSetFace; glm::vec3 triangleSetNormal; if (triangleSet.findRayIntersection(meshFrameOrigin, meshFrameDirection, triangleSetDistance, triangleSetFace, triangleSetNormal, pickAgainstTriangles, allowBackface)) { glm::vec3 meshIntersectionPoint = meshFrameOrigin + (meshFrameDirection * triangleSetDistance); glm::vec3 worldIntersectionPoint = glm::vec3(meshToWorldMatrix * glm::vec4(meshIntersectionPoint, 1.0f)); float worldDistance = glm::distance(origin, worldIntersectionPoint); if (worldDistance < bestDistance) { bestDistance = worldDistance; intersectedSomething = true; face = triangleSetFace; surfaceNormal = glm::vec3(meshToWorldMatrix * glm::vec4(triangleSetNormal, 0.0f)); extraInfo = geometry.getModelNameOfMesh(subMeshIndex); } } subMeshIndex++; } if (intersectedSomething) { distance = bestDistance; } return intersectedSomething; } return intersectedSomething;} bool Model::convexHullContains(glm::vec3 point) { // if we aren't active, we can't compute that yet... if (!isActive()) { return false; } // extents is the entity relative, scaled, centered extents of the entity glm::vec3 position = _translation; glm::mat4 rotation = glm::mat4_cast(_rotation); glm::mat4 translation = glm::translate(position); glm::mat4 modelToWorldMatrix = translation * rotation; glm::mat4 worldToModelMatrix = glm::inverse(modelToWorldMatrix); Extents modelExtents = getMeshExtents(); // NOTE: unrotated glm::vec3 dimensions = modelExtents.maximum - modelExtents.minimum; glm::vec3 corner = -(dimensions * _registrationPoint); AABox modelFrameBox(corner, dimensions); glm::vec3 modelFramePoint = glm::vec3(worldToModelMatrix * glm::vec4(point, 1.0f)); // we can use the AABox's contains() by mapping our point into the model frame // and testing there. if (modelFrameBox.contains(modelFramePoint)){ QMutexLocker locker(&_mutex); if (!_triangleSetsValid) { calculateTriangleSets(); } // If we are inside the models box, then consider the submeshes... glm::mat4 meshToModelMatrix = glm::scale(_scale) * glm::translate(_offset); glm::mat4 meshToWorldMatrix = createMatFromQuatAndPos(_rotation, _translation) * meshToModelMatrix; glm::mat4 worldToMeshMatrix = glm::inverse(meshToWorldMatrix); glm::vec3 meshFramePoint = glm::vec3(worldToMeshMatrix * glm::vec4(point, 1.0f)); for (const auto& triangleSet : _modelSpaceMeshTriangleSets) { const AABox& box = triangleSet.getBounds(); if (box.contains(meshFramePoint)) { if (triangleSet.convexHullContains(meshFramePoint)) { // It's inside this mesh, return true. return true; } } } } // It wasn't in any mesh, return false. return false;} void Model::calculateTriangleSets() { PROFILE_RANGE(render, __FUNCTION__); const FBXGeometry& geometry = getFBXGeometry(); int numberOfMeshes = geometry.meshes.size(); _triangleSetsValid = true; _modelSpaceMeshTriangleSets.clear(); _modelSpaceMeshTriangleSets.resize(numberOfMeshes); for (int i = 0; i < numberOfMeshes; i++) { const FBXMesh& mesh = geometry.meshes.at(i); for (int j = 0; j < mesh.parts.size(); j++) { const FBXMeshPart& part = mesh.parts.at(j); const int INDICES_PER_TRIANGLE = 3; const int INDICES_PER_QUAD = 4; const int TRIANGLES_PER_QUAD = 2; // tell our triangleSet how many triangles to expect. int numberOfQuads = part.quadIndices.size() / INDICES_PER_QUAD; int numberOfTris = part.triangleIndices.size() / INDICES_PER_TRIANGLE; int totalTriangles = (numberOfQuads * TRIANGLES_PER_QUAD) + numberOfTris; _modelSpaceMeshTriangleSets[i].reserve(totalTriangles); auto meshTransform = getFBXGeometry().offset * mesh.modelTransform; if (part.quadIndices.size() > 0) { int vIndex = 0; for (int q = 0; q < numberOfQuads; q++) { int i0 = part.quadIndices[vIndex++]; int i1 = part.quadIndices[vIndex++]; int i2 = part.quadIndices[vIndex++]; int i3 = part.quadIndices[vIndex++]; // track the model space version... these points will be transformed by the FST's offset, // which includes the scaling, rotation, and translation specified by the FST/FBX, Lines 1–500 of 1389. 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 <QMetaType> #include <QRunnable> #include <QThreadPool> #include <glm/gtx/transform.hpp> #include <glm/gtx/norm.hpp> #include <GeometryUtil.h> #include <PathUtils.h> #include <PerfStat.h> #include <ViewFrustum.h> #include <GLMHelpers.h> #include "AbstractViewStateInterface.h" #include "MeshPartPayload.h" #include "Model.h" #include "RenderUtilsLogging.h" #include <Trace.h> using namespace std; int nakedModelPointerTypeId = qRegisterMetaType<ModelPointer>(); int weakGeometryResourceBridgePointerTypeId = qRegisterMetaType<Geometry::WeakPointer >(); int vec3VectorTypeId = qRegisterMetaType<QVector<glm::vec3> >(); float Model::FAKE_DIMENSION_PLACEHOLDER = -1.0f; #define HTTP_INVALID_COM "http://invalid.com" const int NUM_COLLISION_HULL_COLORS = 24; std::vector<model::MaterialPointer> _collisionMaterials; void initCollisionMaterials() { // generates bright colors in red, green, blue, yellow, magenta, and cyan spectrums // (no browns, greys, or dark shades) float component[NUM_COLLISION_HULL_COLORS] = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.2f, 0.4f, 0.6f, 0.8f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.8f, 0.6f, 0.4f, 0.2f }; _collisionMaterials.reserve(NUM_COLLISION_HULL_COLORS); // each component gets the same cuve // but offset by a multiple of one third the full width int numComponents = 3; int sectionWidth = NUM_COLLISION_HULL_COLORS / numComponents; int greenPhase = sectionWidth; int bluePhase = 2 * sectionWidth; // we stride through the colors to scatter adjacent shades // so they don't tend to group together for large models for (int i = 0; i < sectionWidth; ++i) { for (int j = 0; j < numComponents; ++j) { model::MaterialPointer material; material = std::make_shared<model::Material>(); int index = j * sectionWidth + i; float red = component[index]; float green = component[(index + greenPhase) % NUM_COLLISION_HULL_COLORS]; float blue = component[(index + bluePhase) % NUM_COLLISION_HULL_COLORS]; material->setAlbedo(glm::vec3(red, green, blue)); material->setMetallic(0.02f); material->setRoughness(0.5f); _collisionMaterials.push_back(material); } } } Model::Model(QObject* parent, SpatiallyNestable* spatiallyNestableOverride) : QObject(parent), _renderGeometry(), _collisionGeometry(), _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), _isVisible(true), _blendNumber(0), _appliedBlendNumber(0), _isWireframe(false) { // 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(); } // TODO?: should we combine translation and rotation into single method to avoid double-work? // (figure out where we call these) void Model::setTranslation(const glm::vec3& translation) { _translation = translation; updateRenderItems(); } void Model::setRotation(const glm::quat& rotation) { _rotation = rotation; updateRenderItems(); } Transform Model::getTransform() const { if (_spatiallyNestableOverride) { bool success; Transform transform = _spatiallyNestableOverride->getTransform(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.01f; void Model::setScaleInternal(const glm::vec3& scale) { if (glm::distance(_scale, scale) > SCALE_CHANGE_EPSILON) { _scale = scale; if (_scale.x == 0.0f || _scale.y == 0.0f || _scale.z == 0.0f) { assert(false); } 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; } void Model::calculateTextureInfo() { if (!_hasCalculatedTextureInfo && isLoaded() && getGeometry()->areTexturesLoaded() && !_modelMeshRenderItemsMap.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; } void Model::updateRenderItems() { if (!_addedToScene) { return; } glm::vec3 scale = getScale(); if (_collisionGeometry) { // _collisionGeometry is already scaled scale = glm::vec3(1.0f); } _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, scale]() { // do nothing, if the model has already been destroyed. auto self = weakSelf.lock(); if (!self) { 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(); uint32_t deleteGeometryCounter = self->_deleteGeometryCounter; render::Transaction transaction; foreach (auto itemID, self->_modelMeshRenderItemsMap.keys()) { transaction.updateItem<ModelMeshPartPayload>(itemID, [deleteGeometryCounter](ModelMeshPartPayload& data) { ModelPointer model = data._model.lock(); if (model && model->isLoaded()) { // Ensure the model geometry was not reset between frames if (deleteGeometryCounter == model->_deleteGeometryCounter) { Transform modelTransform = model->getTransform(); modelTransform.setScale(glm::vec3(1.0f)); const Model::MeshState& state = model->getMeshState(data._meshIndex); Transform renderTransform = modelTransform; if (state.clusterMatrices.size() == 1) { renderTransform = modelTransform.worldTransform(Transform(state.clusterMatrices[0])); } data.updateTransformForSkinnedMesh(renderTransform, modelTransform, state.clusterBuffer); } } }); } // collision mesh does not share the same unit scale as the FBX file's mesh: only apply offset Transform collisionMeshOffset; collisionMeshOffset.setIdentity(); Transform modelTransform = self->getTransform(); foreach(auto itemID, self->_collisionRenderItemsMap.keys()) { transaction.updateItem<MeshPartPayload>(itemID, [modelTransform, collisionMeshOffset](MeshPartPayload& data) { // update the model transform for this render item. data.updateTransform(modelTransform, collisionMeshOffset); }); } AbstractViewStateInterface::instance()->getMain3DScene()->enqueueTransaction(transaction); }); } void Model::initJointTransforms() { if (isLoaded()) { glm::mat4 modelOffset = glm::scale(_scale) * glm::translate(_offset); _rig.setModelOffset(modelOffset); } } void Model::init() { } void Model::reset() { if (isLoaded()) { const FBXGeometry& geometry = getFBXGeometry(); _rig.reset(geometry); } } bool Model::updateGeometry() { bool needFullUpdate = false; if (!isLoaded()) { return false; } _needsReload = false; // TODO: should all Models have a valid _rig? if (_rig.jointStatesEmpty() && getFBXGeometry().joints.size() > 0) { initJointStates(); assert(_meshStates.empty()); const FBXGeometry& fbxGeometry = getFBXGeometry(); foreach (const FBXMesh& mesh, fbxGeometry.meshes) { MeshState state; state.clusterMatrices.resize(mesh.clusters.size()); _meshStates.append(state); // Note: we add empty buffers for meshes that lack blendshapes so we can access the buffers by index // later in ModelMeshPayload, however the vast majority of meshes will not have them. // TODO? make _blendedVertexBuffers a map instead of vector and only add for meshes with blendshapes? auto buffer = std::make_shared<gpu::Buffer>(); if (!mesh.blendshapes.isEmpty()) { buffer->resize((mesh.vertices.size() + mesh.normals.size()) * sizeof(glm::vec3)); buffer->setSubData(0, mesh.vertices.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.vertices.constData()); buffer->setSubData(mesh.vertices.size() * sizeof(glm::vec3), mesh.normals.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.normals.constData()); } _blendedVertexBuffers.push_back(buffer); } needFullUpdate = true; } return needFullUpdate; } // virtual void Model::initJointStates() { const FBXGeometry& geometry = getFBXGeometry(); glm::mat4 modelOffset = glm::scale(_scale) * glm::translate(_offset); _rig.initJointStates(geometry, modelOffset); } bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QString& extraInfo, bool pickAgainstTriangles, bool allowBackface) { bool intersectedSomething = false; // if we aren't active, we can't ray pick yet... if (!isActive()) { return intersectedSomething; } // extents is the entity relative, scaled, centered extents of the entity glm::vec3 position = _translation; glm::mat4 rotation = glm::mat4_cast(_rotation); glm::mat4 translation = glm::translate(position); glm::mat4 modelToWorldMatrix = translation * rotation; 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 ray 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 ray intersection by mapping our origin and direction into the model frame // and testing intersection there. if (modelFrameBox.findRayIntersection(modelFrameOrigin, modelFrameDirection, distance, face, surfaceNormal)) { QMutexLocker locker(&_mutex); float bestDistance = std::numeric_limits<float>::max(); int subMeshIndex = 0; const FBXGeometry& geometry = getFBXGeometry(); if (!_triangleSetsValid) { calculateTriangleSets(); } glm::mat4 meshToModelMatrix = glm::scale(_scale) * glm::translate(_offset); glm::mat4 meshToWorldMatrix = createMatFromQuatAndPos(_rotation, _translation) * meshToModelMatrix; 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)); for (auto& triangleSet : _modelSpaceMeshTriangleSets) { float triangleSetDistance = 0.0f; BoxFace triangleSetFace; glm::vec3 triangleSetNormal; if (triangleSet.findRayIntersection(meshFrameOrigin, meshFrameDirection, triangleSetDistance, triangleSetFace, triangleSetNormal, pickAgainstTriangles, allowBackface)) { glm::vec3 meshIntersectionPoint = meshFrameOrigin + (meshFrameDirection * triangleSetDistance); glm::vec3 worldIntersectionPoint = glm::vec3(meshToWorldMatrix * glm::vec4(meshIntersectionPoint, 1.0f)); float worldDistance = glm::distance(origin, worldIntersectionPoint); if (worldDistance < bestDistance) { bestDistance = worldDistance; intersectedSomething = true; face = triangleSetFace; surfaceNormal = glm::vec3(meshToWorldMatrix * glm::vec4(triangleSetNormal, 0.0f)); extraInfo = geometry.getModelNameOfMesh(subMeshIndex); } } subMeshIndex++; } if (intersectedSomething) { distance = bestDistance; } return intersectedSomething; } return intersectedSomething; } bool Model::convexHullContains(glm::vec3 point) { // if we aren't active, we can't compute that yet... if (!isActive()) { return false; } // extents is the entity relative, scaled, centered extents of the entity glm::vec3 position = _translation; glm::mat4 rotation = glm::mat4_cast(_rotation); glm::mat4 translation = glm::translate(position); glm::mat4 modelToWorldMatrix = translation * rotation; glm::mat4 worldToModelMatrix = glm::inverse(modelToWorldMatrix); Extents modelExtents = getMeshExtents(); // NOTE: unrotated glm::vec3 dimensions = modelExtents.maximum - modelExtents.minimum; glm::vec3 corner = -(dimensions * _registrationPoint); AABox modelFrameBox(corner, dimensions); glm::vec3 modelFramePoint = glm::vec3(worldToModelMatrix * glm::vec4(point, 1.0f)); // we can use the AABox's contains() by mapping our point into the model frame // and testing there. if (modelFrameBox.contains(modelFramePoint)){ QMutexLocker locker(&_mutex); if (!_triangleSetsValid) { calculateTriangleSets(); } // If we are inside the models box, then consider the submeshes... glm::mat4 meshToModelMatrix = glm::scale(_scale) * glm::translate(_offset); glm::mat4 meshToWorldMatrix = createMatFromQuatAndPos(_rotation, _translation) * meshToModelMatrix; glm::mat4 worldToMeshMatrix = glm::inverse(meshToWorldMatrix); glm::vec3 meshFramePoint = glm::vec3(worldToMeshMatrix * glm::vec4(point, 1.0f)); for (const auto& triangleSet : _modelSpaceMeshTriangleSets) { const AABox& box = triangleSet.getBounds(); if (box.contains(meshFramePoint)) { if (triangleSet.convexHullContains(meshFramePoint)) { // It's inside this mesh, return true. return true; } } } } // It wasn't in any mesh, return false. return false; } void Model::calculateTriangleSets() { PROFILE_RANGE(render, __FUNCTION__); const FBXGeometry& geometry = getFBXGeometry(); int numberOfMeshes = geometry.meshes.size(); _triangleSetsValid = true; _modelSpaceMeshTriangleSets.clear(); _modelSpaceMeshTriangleSets.resize(numberOfMeshes); for (int i = 0; i < numberOfMeshes; i++) { const FBXMesh& mesh = geometry.meshes.at(i); for (int j = 0; j < mesh.parts.size(); j++) { const FBXMeshPart& part = mesh.parts.at(j); const int INDICES_PER_TRIANGLE = 3; const int INDICES_PER_QUAD = 4; const int TRIANGLES_PER_QUAD = 2; // tell our triangleSet how many triangles to expect. int numberOfQuads = part.quadIndices.size() / INDICES_PER_QUAD; int numberOfTris = part.triangleIndices.size() / INDICES_PER_TRIANGLE; int totalTriangles = (numberOfQuads * TRIANGLES_PER_QUAD) + numberOfTris; _modelSpaceMeshTriangleSets[i].reserve(totalTriangles); auto meshTransform = getFBXGeometry().offset * mesh.modelTransform; if (part.quadIndices.size() > 0) { int vIndex = 0; for (int q = 0; q < numberOfQuads; q++) { int i0 = part.quadIndices[vIndex++]; int i1 = part.quadIndices[vIndex++]; int i2 = part.quadIndices[vIndex++]; int i3 = part.quadIndices[vIndex++]; // track the model space version... these points will be transformed by the FST's offset, // which includes the scaling, rotation, and translation specified by the FST/FBX,