1228 lines · 48.8 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 "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.