1267 lines · 50.0 KB · C++
RawDownload
////  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 "Model.h"#include "MeshPartPayload.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;    enqueueLocationChange();} void Model::setRotation(const glm::quat& rotation) {    _rotation = rotation;    enqueueLocationChange();} 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 METERS_PER_MILLIMETER = 0.01f;  void Model::setScaleInternal(const glm::vec3& scale) {    if (glm::distance(_scale, scale) > METERS_PER_MILLIMETER) {        _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;} void Model::enqueueLocationChange() {    render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene();     Transform transform;    transform.setTranslation(_translation);    transform.setRotation(_rotation);     Transform offset;    offset.setScale(_scale);    offset.postTranslate(_offset);     render::PendingChanges pendingChanges;    foreach (auto itemID, _renderItems.keys()) {        pendingChanges.updateItem<MeshPartPayload>(itemID, [transform, offset](MeshPartPayload& data) {            data.updateTransform(transform, offset);            data.notifyLocationChanged();        });    }     scene->enqueuePendingChanges(pendingChanges);} void Model::initJointTransforms() {    if (!_geometry || !_geometry->isLoaded()) {        return;    }    glm::mat4 modelOffset = glm::scale(_scale) * glm::translate(_offset);    _rig->setModelOffset(modelOffset);} void Model::init() {} void Model::reset() {    if (_geometry && _geometry->isLoaded()) {        const FBXGeometry& geometry = _geometry->getFBXGeometry();        _rig->reset(geometry);    }} bool Model::updateGeometry() {    PROFILE_RANGE(__FUNCTION__);    bool needFullUpdate = false;     if (!_geometry || !_geometry->isLoaded()) {        // geometry is not ready        return false;    }     _needsReload = false;     if (_rig->jointStatesEmpty() && _geometry->getFBXGeometry().joints.size() > 0) {        initJointStates();         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());             _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() {    const FBXGeometry& geometry = _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 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, _translation, _rotation);             _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();    }} void Model::setVisibleInScene(bool newValue, std::shared_ptr<render::Scene> scene) {    if (_isVisible != newValue) {        _isVisible = newValue;         render::PendingChanges pendingChanges;        foreach (auto item, _renderItems.keys()) {            pendingChanges.resetItem(item, _renderItems[item]);        }        scene->enqueuePendingChanges(pendingChanges);    }}  bool Model::addToScene(std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges, bool showCollisionHull) {     if ((!_meshGroupsKnown || showCollisionHull != _showCollisionHull) && isLoaded()) {        _showCollisionHull = showCollisionHull;        segregateMeshGroups();    }

Lines 1–500 of 1267.