//// 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 "AbstractViewStateInterface.h"#include "AnimationHandle.h"#include "DeferredLightingEffect.h"#include "Model.h"#include "ModelRenderItem.h" #include "RenderUtilsLogging.h" using namespace std; static int modelPointerTypeId = qRegisterMetaType<QPointer<Model> >();static int weakNetworkGeometryPointerTypeId = qRegisterMetaType<QWeakPointer<NetworkGeometry> >();static int vec3VectorTypeId = qRegisterMetaType<QVector<glm::vec3> >();float Model::FAKE_DIMENSION_PLACEHOLDER = -1.0f;#define HTTP_INVALID_COM "http://invalid.com" Model::Model(RigPointer rig, QObject* parent) : QObject(parent), _translation(0.0f), _rotation(), _scale(1.0f, 1.0f, 1.0f), _scaleToFit(false), _scaleToFitDimensions(0.0f), _scaledToFit(false), _snapModelToRegistrationPoint(false), _snappedToRegistrationPoint(false), _cauterizeBones(false), _pupilDilation(0.0f), _url(HTTP_INVALID_COM), _isVisible(true), _blendNumber(0), _appliedBlendNumber(0), _calculatedMeshPartBoxesValid(false), _calculatedMeshBoxesValid(false), _calculatedMeshTrianglesValid(false), _meshGroupsKnown(false), _isWireframe(false), _renderCollisionHull(false), _rig(rig) { // 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));} Model::~Model() { deleteGeometry();} AbstractViewStateInterface* Model::_viewState = NULL; void Model::setTranslation(const glm::vec3& translation) { _translation = translation;} void Model::setRotation(const glm::quat& rotation) { _rotation = rotation;} 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;} void Model::setScaleInternal(const glm::vec3& scale) { float scaleLength = glm::length(_scale); float relativeDeltaScale = glm::length(_scale - scale) / scaleLength; const float ONE_PERCENT = 0.01f; if (relativeDeltaScale > ONE_PERCENT || scaleLength < EPSILON) { _scale = scale; initJointTransforms(); }} 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;} QVector<JointState> Model::createJointStates(const FBXGeometry& geometry) { QVector<JointState> jointStates; for (int i = 0; i < geometry.joints.size(); ++i) { const FBXJoint& joint = geometry.joints[i]; // store a pointer to the FBXJoint in the JointState JointState state(joint); jointStates.append(state); } return jointStates;}; void Model::initJointTransforms() { if (!_geometry || !_geometry->isLoaded()) { return; } const FBXGeometry& geometry = _geometry->getFBXGeometry(); glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset; _rig->initJointTransforms(parentTransform);} void Model::init() {} void Model::reset() { if (_geometry && _geometry->isLoaded()) { const FBXGeometry& geometry = _geometry->getFBXGeometry(); _rig->reset(geometry.joints); } _meshGroupsKnown = false; _readyWhenAdded = false; // in case any of our users are using scenes invalidCalculatedMeshBoxes(); // if we have to reload, we need to assume our mesh boxes are all invalid} bool Model::updateGeometry() { PROFILE_RANGE(__FUNCTION__); bool needFullUpdate = false; bool needToRebuild = false; if (!_geometry || !_geometry->isLoaded()) { // geometry is not ready return false; } _needsReload = false; QSharedPointer<NetworkGeometry> geometry = _geometry; if (_rig->jointStatesEmpty()) { const FBXGeometry& fbxGeometry = geometry->getFBXGeometry(); if (fbxGeometry.joints.size() > 0) { initJointStates(createJointStates(fbxGeometry)); needToRebuild = true; } } if (needToRebuild) { const FBXGeometry& fbxGeometry = geometry->getFBXGeometry(); foreach (const FBXMesh& mesh, fbxGeometry.meshes) { MeshState state; state.clusterMatrices.resize(mesh.clusters.size()); state.cauterizedClusterMatrices.resize(mesh.clusters.size()); if (mesh.clusters.size() > 1) { state.clusterBuffer = std::make_shared<gpu::Buffer>(mesh.clusters.size() * sizeof(glm::mat4), nullptr); } _meshStates.append(state); 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(QVector<JointState> states) { const FBXGeometry& geometry = _geometry->getFBXGeometry(); glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset; int rootJointIndex = geometry.rootJointIndex; int leftHandJointIndex = geometry.leftHandJointIndex; int leftElbowJointIndex = leftHandJointIndex >= 0 ? geometry.joints.at(leftHandJointIndex).parentIndex : -1; int leftShoulderJointIndex = leftElbowJointIndex >= 0 ? geometry.joints.at(leftElbowJointIndex).parentIndex : -1; int rightHandJointIndex = geometry.rightHandJointIndex; int rightElbowJointIndex = rightHandJointIndex >= 0 ? geometry.joints.at(rightHandJointIndex).parentIndex : -1; int rightShoulderJointIndex = rightElbowJointIndex >= 0 ? geometry.joints.at(rightElbowJointIndex).parentIndex : -1; _rig->initJointStates(states, parentTransform, rootJointIndex, leftHandJointIndex, leftElbowJointIndex, leftShoulderJointIndex, rightHandJointIndex, rightElbowJointIndex, rightShoulderJointIndex);} bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QString& extraInfo, bool pickAgainstTriangles) { 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)) { float bestDistance = std::numeric_limits<float>::max(); float distanceToSubMesh; BoxFace subMeshFace; glm::vec3 subMeshSurfaceNormal; int subMeshIndex = 0; const FBXGeometry& geometry = _geometry->getFBXGeometry(); // If we hit the models box, then consider the submeshes... _mutex.lock(); if (!_calculatedMeshBoxesValid) { recalculateMeshBoxes(pickAgainstTriangles); } foreach (const AABox& subMeshBox, _calculatedMeshBoxes) { if (subMeshBox.findRayIntersection(origin, direction, distanceToSubMesh, subMeshFace, subMeshSurfaceNormal)) { if (distanceToSubMesh < bestDistance) { if (pickAgainstTriangles) { if (!_calculatedMeshTrianglesValid) { recalculateMeshBoxes(pickAgainstTriangles); } // check our triangles here.... const QVector<Triangle>& meshTriangles = _calculatedMeshTriangles[subMeshIndex]; int t = 0; foreach (const Triangle& triangle, meshTriangles) { t++; float thisTriangleDistance; if (findRayTriangleIntersection(origin, direction, triangle, thisTriangleDistance)) { if (thisTriangleDistance < bestDistance) { bestDistance = thisTriangleDistance; intersectedSomething = true; face = subMeshFace; surfaceNormal = triangle.getNormal(); extraInfo = geometry.getModelNameOfMesh(subMeshIndex); } } } } else { // this is the non-triangle picking case... bestDistance = distanceToSubMesh; intersectedSomething = true; face = subMeshFace; surfaceNormal = subMeshSurfaceNormal; extraInfo = geometry.getModelNameOfMesh(subMeshIndex); } } } subMeshIndex++; } _mutex.unlock(); 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)){ _mutex.lock(); if (!_calculatedMeshTrianglesValid) { recalculateMeshBoxes(true); } // If we are inside the models box, then consider the submeshes... int subMeshIndex = 0; foreach(const AABox& subMeshBox, _calculatedMeshBoxes) { if (subMeshBox.contains(point)) { bool insideMesh = true; // To be inside the sub mesh, we need to be behind every triangles' planes const QVector<Triangle>& meshTriangles = _calculatedMeshTriangles[subMeshIndex]; foreach (const Triangle& triangle, meshTriangles) { if (!isPointBehindTrianglesPlane(point, triangle.v0, triangle.v1, triangle.v2)) { // it's not behind at least one so we bail insideMesh = false; break; } } if (insideMesh) { // It's inside this mesh, return true. _mutex.unlock(); return true; } } subMeshIndex++; } _mutex.unlock(); } // It wasn't in any mesh, return false. return false;} // TODO: we seem to call this too often when things haven't actually changed... look into optimizing this// Any script might trigger findRayIntersectionAgainstSubMeshes (and maybe convexHullContains), so these// can occur multiple times. In addition, rendering does it's own ray picking in order to decide which// entity-scripts to call. I think it would be best to do the picking once-per-frame (in cpu, or gpu if possible)// and then the calls use the most recent such result.void Model::recalculateMeshBoxes(bool pickAgainstTriangles) { PROFILE_RANGE(__FUNCTION__); bool calculatedMeshTrianglesNeeded = pickAgainstTriangles && !_calculatedMeshTrianglesValid; if (!_calculatedMeshBoxesValid || calculatedMeshTrianglesNeeded || (!_calculatedMeshPartBoxesValid && pickAgainstTriangles) ) { const FBXGeometry& geometry = _geometry->getFBXGeometry(); int numberOfMeshes = geometry.meshes.size(); _calculatedMeshBoxes.resize(numberOfMeshes); _calculatedMeshTriangles.clear(); _calculatedMeshTriangles.resize(numberOfMeshes); _calculatedMeshPartBoxes.clear(); for (int i = 0; i < numberOfMeshes; i++) { const FBXMesh& mesh = geometry.meshes.at(i); Extents scaledMeshExtents = calculateScaledOffsetExtents(mesh.meshExtents); _calculatedMeshBoxes[i] = AABox(scaledMeshExtents); if (pickAgainstTriangles) { QVector<Triangle> thisMeshTriangles; for (int j = 0; j < mesh.parts.size(); j++) { const FBXMeshPart& part = mesh.parts.at(j); bool atLeastOnePointInBounds = false; AABox thisPartBounds; const int INDICES_PER_TRIANGLE = 3; const int INDICES_PER_QUAD = 4; if (part.quadIndices.size() > 0) { int numberOfQuads = part.quadIndices.size() / INDICES_PER_QUAD; 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++]; glm::vec3 mv0 = glm::vec3(mesh.modelTransform * glm::vec4(mesh.vertices[i0], 1.0f)); glm::vec3 mv1 = glm::vec3(mesh.modelTransform * glm::vec4(mesh.vertices[i1], 1.0f)); glm::vec3 mv2 = glm::vec3(mesh.modelTransform * glm::vec4(mesh.vertices[i2], 1.0f)); glm::vec3 mv3 = glm::vec3(mesh.modelTransform * glm::vec4(mesh.vertices[i3], 1.0f)); // track the mesh parts in model space if (!atLeastOnePointInBounds) { thisPartBounds.setBox(mv0, 0.0f); atLeastOnePointInBounds = true; } else { thisPartBounds += mv0; } thisPartBounds += mv1; thisPartBounds += mv2; thisPartBounds += mv3; glm::vec3 v0 = calculateScaledOffsetPoint(mv0); glm::vec3 v1 = calculateScaledOffsetPoint(mv1); glm::vec3 v2 = calculateScaledOffsetPoint(mv2); glm::vec3 v3 = calculateScaledOffsetPoint(mv3); // Sam's recommended triangle slices Triangle tri1 = { v0, v1, v3 }; Triangle tri2 = { v1, v2, v3 }; // NOTE: Random guy on the internet's recommended triangle slices //Triangle tri1 = { v0, v1, v2 }; //Triangle tri2 = { v2, v3, v0 }; thisMeshTriangles.push_back(tri1); thisMeshTriangles.push_back(tri2); } } if (part.triangleIndices.size() > 0) { int numberOfTris = part.triangleIndices.size() / INDICES_PER_TRIANGLE; int vIndex = 0; for (int t = 0; t < numberOfTris; t++) { int i0 = part.triangleIndices[vIndex++]; int i1 = part.triangleIndices[vIndex++]; int i2 = part.triangleIndices[vIndex++]; glm::vec3 mv0 = glm::vec3(mesh.modelTransform * glm::vec4(mesh.vertices[i0], 1.0f)); glm::vec3 mv1 = glm::vec3(mesh.modelTransform * glm::vec4(mesh.vertices[i1], 1.0f)); glm::vec3 mv2 = glm::vec3(mesh.modelTransform * glm::vec4(mesh.vertices[i2], 1.0f)); // track the mesh parts in model space if (!atLeastOnePointInBounds) { thisPartBounds.setBox(mv0, 0.0f); atLeastOnePointInBounds = true; } else { thisPartBounds += mv0; } thisPartBounds += mv1; thisPartBounds += mv2; glm::vec3 v0 = calculateScaledOffsetPoint(mv0); glm::vec3 v1 = calculateScaledOffsetPoint(mv1); glm::vec3 v2 = calculateScaledOffsetPoint(mv2); Triangle tri = { v0, v1, v2 }; thisMeshTriangles.push_back(tri); } } _calculatedMeshPartBoxes[QPair<int,int>(i, j)] = thisPartBounds; } _calculatedMeshTriangles[i] = thisMeshTriangles; _calculatedMeshPartBoxesValid = true; } } _calculatedMeshBoxesValid = true; _calculatedMeshTrianglesValid = pickAgainstTriangles; }} void Model::renderSetup(RenderArgs* args) { // set up dilated textures on first render after load/simulate const FBXGeometry& geometry = _geometry->getFBXGeometry(); if (_dilatedTextures.isEmpty()) { foreach (const FBXMesh& mesh, geometry.meshes) { QVector<QSharedPointer<Texture> > dilated; dilated.resize(mesh.parts.size()); _dilatedTextures.append(dilated); } } if (!_meshGroupsKnown && isLoaded()) { segregateMeshGroups(); }} Lines 1–500 of 1228. 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 "AbstractViewStateInterface.h" #include "AnimationHandle.h" #include "DeferredLightingEffect.h" #include "Model.h" #include "ModelRenderItem.h" #include "RenderUtilsLogging.h" using namespace std; static int modelPointerTypeId = qRegisterMetaType<QPointer<Model> >(); static int weakNetworkGeometryPointerTypeId = qRegisterMetaType<QWeakPointer<NetworkGeometry> >(); static int vec3VectorTypeId = qRegisterMetaType<QVector<glm::vec3> >(); float Model::FAKE_DIMENSION_PLACEHOLDER = -1.0f; #define HTTP_INVALID_COM "http://invalid.com" Model::Model(RigPointer rig, QObject* parent) : QObject(parent), _translation(0.0f), _rotation(), _scale(1.0f, 1.0f, 1.0f), _scaleToFit(false), _scaleToFitDimensions(0.0f), _scaledToFit(false), _snapModelToRegistrationPoint(false), _snappedToRegistrationPoint(false), _cauterizeBones(false), _pupilDilation(0.0f), _url(HTTP_INVALID_COM), _isVisible(true), _blendNumber(0), _appliedBlendNumber(0), _calculatedMeshPartBoxesValid(false), _calculatedMeshBoxesValid(false), _calculatedMeshTrianglesValid(false), _meshGroupsKnown(false), _isWireframe(false), _renderCollisionHull(false), _rig(rig) { // 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)); } Model::~Model() { deleteGeometry(); } AbstractViewStateInterface* Model::_viewState = NULL; void Model::setTranslation(const glm::vec3& translation) { _translation = translation; } void Model::setRotation(const glm::quat& rotation) { _rotation = rotation; } 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; } void Model::setScaleInternal(const glm::vec3& scale) { float scaleLength = glm::length(_scale); float relativeDeltaScale = glm::length(_scale - scale) / scaleLength; const float ONE_PERCENT = 0.01f; if (relativeDeltaScale > ONE_PERCENT || scaleLength < EPSILON) { _scale = scale; initJointTransforms(); } } 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; } QVector<JointState> Model::createJointStates(const FBXGeometry& geometry) { QVector<JointState> jointStates; for (int i = 0; i < geometry.joints.size(); ++i) { const FBXJoint& joint = geometry.joints[i]; // store a pointer to the FBXJoint in the JointState JointState state(joint); jointStates.append(state); } return jointStates; }; void Model::initJointTransforms() { if (!_geometry || !_geometry->isLoaded()) { return; } const FBXGeometry& geometry = _geometry->getFBXGeometry(); glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset; _rig->initJointTransforms(parentTransform); } void Model::init() { } void Model::reset() { if (_geometry && _geometry->isLoaded()) { const FBXGeometry& geometry = _geometry->getFBXGeometry(); _rig->reset(geometry.joints); } _meshGroupsKnown = false; _readyWhenAdded = false; // in case any of our users are using scenes invalidCalculatedMeshBoxes(); // if we have to reload, we need to assume our mesh boxes are all invalid } bool Model::updateGeometry() { PROFILE_RANGE(__FUNCTION__); bool needFullUpdate = false; bool needToRebuild = false; if (!_geometry || !_geometry->isLoaded()) { // geometry is not ready return false; } _needsReload = false; QSharedPointer<NetworkGeometry> geometry = _geometry; if (_rig->jointStatesEmpty()) { const FBXGeometry& fbxGeometry = geometry->getFBXGeometry(); if (fbxGeometry.joints.size() > 0) { initJointStates(createJointStates(fbxGeometry)); needToRebuild = true; } } if (needToRebuild) { const FBXGeometry& fbxGeometry = geometry->getFBXGeometry(); foreach (const FBXMesh& mesh, fbxGeometry.meshes) { MeshState state; state.clusterMatrices.resize(mesh.clusters.size()); state.cauterizedClusterMatrices.resize(mesh.clusters.size()); if (mesh.clusters.size() > 1) { state.clusterBuffer = std::make_shared<gpu::Buffer>(mesh.clusters.size() * sizeof(glm::mat4), nullptr); } _meshStates.append(state); 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(QVector<JointState> states) { const FBXGeometry& geometry = _geometry->getFBXGeometry(); glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset; int rootJointIndex = geometry.rootJointIndex; int leftHandJointIndex = geometry.leftHandJointIndex; int leftElbowJointIndex = leftHandJointIndex >= 0 ? geometry.joints.at(leftHandJointIndex).parentIndex : -1; int leftShoulderJointIndex = leftElbowJointIndex >= 0 ? geometry.joints.at(leftElbowJointIndex).parentIndex : -1; int rightHandJointIndex = geometry.rightHandJointIndex; int rightElbowJointIndex = rightHandJointIndex >= 0 ? geometry.joints.at(rightHandJointIndex).parentIndex : -1; int rightShoulderJointIndex = rightElbowJointIndex >= 0 ? geometry.joints.at(rightElbowJointIndex).parentIndex : -1; _rig->initJointStates(states, parentTransform, rootJointIndex, leftHandJointIndex, leftElbowJointIndex, leftShoulderJointIndex, rightHandJointIndex, rightElbowJointIndex, rightShoulderJointIndex); } bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QString& extraInfo, bool pickAgainstTriangles) { 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)) { float bestDistance = std::numeric_limits<float>::max(); float distanceToSubMesh; BoxFace subMeshFace; glm::vec3 subMeshSurfaceNormal; int subMeshIndex = 0; const FBXGeometry& geometry = _geometry->getFBXGeometry(); // If we hit the models box, then consider the submeshes... _mutex.lock(); if (!_calculatedMeshBoxesValid) { recalculateMeshBoxes(pickAgainstTriangles); } foreach (const AABox& subMeshBox, _calculatedMeshBoxes) { if (subMeshBox.findRayIntersection(origin, direction, distanceToSubMesh, subMeshFace, subMeshSurfaceNormal)) { if (distanceToSubMesh < bestDistance) { if (pickAgainstTriangles) { if (!_calculatedMeshTrianglesValid) { recalculateMeshBoxes(pickAgainstTriangles); } // check our triangles here.... const QVector<Triangle>& meshTriangles = _calculatedMeshTriangles[subMeshIndex]; int t = 0; foreach (const Triangle& triangle, meshTriangles) { t++; float thisTriangleDistance; if (findRayTriangleIntersection(origin, direction, triangle, thisTriangleDistance)) { if (thisTriangleDistance < bestDistance) { bestDistance = thisTriangleDistance; intersectedSomething = true; face = subMeshFace; surfaceNormal = triangle.getNormal(); extraInfo = geometry.getModelNameOfMesh(subMeshIndex); } } } } else { // this is the non-triangle picking case... bestDistance = distanceToSubMesh; intersectedSomething = true; face = subMeshFace; surfaceNormal = subMeshSurfaceNormal; extraInfo = geometry.getModelNameOfMesh(subMeshIndex); } } } subMeshIndex++; } _mutex.unlock(); 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)){ _mutex.lock(); if (!_calculatedMeshTrianglesValid) { recalculateMeshBoxes(true); } // If we are inside the models box, then consider the submeshes... int subMeshIndex = 0; foreach(const AABox& subMeshBox, _calculatedMeshBoxes) { if (subMeshBox.contains(point)) { bool insideMesh = true; // To be inside the sub mesh, we need to be behind every triangles' planes const QVector<Triangle>& meshTriangles = _calculatedMeshTriangles[subMeshIndex]; foreach (const Triangle& triangle, meshTriangles) { if (!isPointBehindTrianglesPlane(point, triangle.v0, triangle.v1, triangle.v2)) { // it's not behind at least one so we bail insideMesh = false; break; } } if (insideMesh) { // It's inside this mesh, return true. _mutex.unlock(); return true; } } subMeshIndex++; } _mutex.unlock(); } // It wasn't in any mesh, return false. return false; } // TODO: we seem to call this too often when things haven't actually changed... look into optimizing this // Any script might trigger findRayIntersectionAgainstSubMeshes (and maybe convexHullContains), so these // can occur multiple times. In addition, rendering does it's own ray picking in order to decide which // entity-scripts to call. I think it would be best to do the picking once-per-frame (in cpu, or gpu if possible) // and then the calls use the most recent such result. void Model::recalculateMeshBoxes(bool pickAgainstTriangles) { PROFILE_RANGE(__FUNCTION__); bool calculatedMeshTrianglesNeeded = pickAgainstTriangles && !_calculatedMeshTrianglesValid; if (!_calculatedMeshBoxesValid || calculatedMeshTrianglesNeeded || (!_calculatedMeshPartBoxesValid && pickAgainstTriangles) ) { const FBXGeometry& geometry = _geometry->getFBXGeometry(); int numberOfMeshes = geometry.meshes.size(); _calculatedMeshBoxes.resize(numberOfMeshes); _calculatedMeshTriangles.clear(); _calculatedMeshTriangles.resize(numberOfMeshes); _calculatedMeshPartBoxes.clear(); for (int i = 0; i < numberOfMeshes; i++) { const FBXMesh& mesh = geometry.meshes.at(i); Extents scaledMeshExtents = calculateScaledOffsetExtents(mesh.meshExtents); _calculatedMeshBoxes[i] = AABox(scaledMeshExtents); if (pickAgainstTriangles) { QVector<Triangle> thisMeshTriangles; for (int j = 0; j < mesh.parts.size(); j++) { const FBXMeshPart& part = mesh.parts.at(j); bool atLeastOnePointInBounds = false; AABox thisPartBounds; const int INDICES_PER_TRIANGLE = 3; const int INDICES_PER_QUAD = 4; if (part.quadIndices.size() > 0) { int numberOfQuads = part.quadIndices.size() / INDICES_PER_QUAD; 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++]; glm::vec3 mv0 = glm::vec3(mesh.modelTransform * glm::vec4(mesh.vertices[i0], 1.0f)); glm::vec3 mv1 = glm::vec3(mesh.modelTransform * glm::vec4(mesh.vertices[i1], 1.0f)); glm::vec3 mv2 = glm::vec3(mesh.modelTransform * glm::vec4(mesh.vertices[i2], 1.0f)); glm::vec3 mv3 = glm::vec3(mesh.modelTransform * glm::vec4(mesh.vertices[i3], 1.0f)); // track the mesh parts in model space if (!atLeastOnePointInBounds) { thisPartBounds.setBox(mv0, 0.0f); atLeastOnePointInBounds = true; } else { thisPartBounds += mv0; } thisPartBounds += mv1; thisPartBounds += mv2; thisPartBounds += mv3; glm::vec3 v0 = calculateScaledOffsetPoint(mv0); glm::vec3 v1 = calculateScaledOffsetPoint(mv1); glm::vec3 v2 = calculateScaledOffsetPoint(mv2); glm::vec3 v3 = calculateScaledOffsetPoint(mv3); // Sam's recommended triangle slices Triangle tri1 = { v0, v1, v3 }; Triangle tri2 = { v1, v2, v3 }; // NOTE: Random guy on the internet's recommended triangle slices //Triangle tri1 = { v0, v1, v2 }; //Triangle tri2 = { v2, v3, v0 }; thisMeshTriangles.push_back(tri1); thisMeshTriangles.push_back(tri2); } } if (part.triangleIndices.size() > 0) { int numberOfTris = part.triangleIndices.size() / INDICES_PER_TRIANGLE; int vIndex = 0; for (int t = 0; t < numberOfTris; t++) { int i0 = part.triangleIndices[vIndex++]; int i1 = part.triangleIndices[vIndex++]; int i2 = part.triangleIndices[vIndex++]; glm::vec3 mv0 = glm::vec3(mesh.modelTransform * glm::vec4(mesh.vertices[i0], 1.0f)); glm::vec3 mv1 = glm::vec3(mesh.modelTransform * glm::vec4(mesh.vertices[i1], 1.0f)); glm::vec3 mv2 = glm::vec3(mesh.modelTransform * glm::vec4(mesh.vertices[i2], 1.0f)); // track the mesh parts in model space if (!atLeastOnePointInBounds) { thisPartBounds.setBox(mv0, 0.0f); atLeastOnePointInBounds = true; } else { thisPartBounds += mv0; } thisPartBounds += mv1; thisPartBounds += mv2; glm::vec3 v0 = calculateScaledOffsetPoint(mv0); glm::vec3 v1 = calculateScaledOffsetPoint(mv1); glm::vec3 v2 = calculateScaledOffsetPoint(mv2); Triangle tri = { v0, v1, v2 }; thisMeshTriangles.push_back(tri); } } _calculatedMeshPartBoxes[QPair<int,int>(i, j)] = thisPartBounds; } _calculatedMeshTriangles[i] = thisMeshTriangles; _calculatedMeshPartBoxesValid = true; } } _calculatedMeshBoxesValid = true; _calculatedMeshTrianglesValid = pickAgainstTriangles; } } void Model::renderSetup(RenderArgs* args) { // set up dilated textures on first render after load/simulate const FBXGeometry& geometry = _geometry->getFBXGeometry(); if (_dilatedTextures.isEmpty()) { foreach (const FBXMesh& mesh, geometry.meshes) { QVector<QSharedPointer<Texture> > dilated; dilated.resize(mesh.parts.size()); _dilatedTextures.append(dilated); } } if (!_meshGroupsKnown && isLoaded()) { segregateMeshGroups(); } }