1495 lines · 58.2 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 <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(RigPointer rig, QObject* parent) :    QObject(parent),    _renderGeometry(),    _collisionGeometry(),    _renderWatcher(_renderGeometry),    _translation(0.0f),    _rotation(),    _scale(1.0f, 1.0f, 1.0f),    _scaleToFit(false),    _scaleToFitDimensions(1.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),    _isWireframe(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));     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();} 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() && !_modelMeshRenderItems.isEmpty()) {        size_t textureSize = 0;        int textureCount = 0;        bool allTexturesLoaded = true;        foreach(auto renderItem, _modelMeshRenderItemsSet) {            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;        }         render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene();         Transform modelTransform;        modelTransform.setScale(scale);        modelTransform.setTranslation(self->_translation);        modelTransform.setRotation(self->_rotation);         Transform modelMeshOffset;        if (self->isLoaded()) {            // includes model offset and unitScale.            modelMeshOffset = Transform(self->_rig->getGeometryToRigTransform());        } else {            modelMeshOffset.postTranslate(self->_offset);        }         uint32_t deleteGeometryCounter = self->_deleteGeometryCounter;         render::PendingChanges pendingChanges;        foreach (auto itemID, self->_modelMeshRenderItems.keys()) {            pendingChanges.updateItem<ModelMeshPartPayload>(itemID, [modelTransform, modelMeshOffset, deleteGeometryCounter](ModelMeshPartPayload& data) {                if (!data.hasStartedFade() && data._model && data._model->isLoaded() && data._model->getGeometry()->areTexturesLoaded()) {                    data.startFade();                }                // Ensure the model geometry was not reset between frames                if (data._model && data._model->isLoaded() && deleteGeometryCounter == data._model->_deleteGeometryCounter) {                    // lazy update of cluster matrices used for rendering.  We need to update them here, so we can correctly update the bounding box.                    data._model->updateClusterMatrices(modelTransform.getTranslation(), modelTransform.getRotation());                     // update the model transform and bounding box for this render item.                    const Model::MeshState& state = data._model->_meshStates.at(data._meshIndex);                    data.updateTransformForSkinnedMesh(modelTransform, modelMeshOffset, state.clusterMatrices);                }            });        }         // collision mesh does not share the same unit scale as the FBX file's mesh: only apply offset        Transform collisionMeshOffset;        collisionMeshOffset.setIdentity();        foreach (auto itemID, self->_collisionRenderItems.keys()) {            pendingChanges.updateItem<MeshPartPayload>(itemID, [modelTransform, collisionMeshOffset](MeshPartPayload& data) {                // update the model transform for this render item.                data.updateTransform(modelTransform, collisionMeshOffset);            });        }         scene->enqueuePendingChanges(pendingChanges);    });} 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() {    PROFILE_RANGE(render, __FUNCTION__);    PerformanceTimer perfTimer("Model::updateGeometry");    bool needFullUpdate = false;     if (!isLoaded()) {        return false;    }     _needsReload = false;     if (_rig->jointStatesEmpty() && getFBXGeometry().joints.size() > 0) {        initJointStates();         const FBXGeometry& fbxGeometry = 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 = 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 = getFBXGeometry();         // If we hit the models box, then consider the submeshes...        _mutex.lock();        if (!_calculatedMeshBoxesValid || (pickAgainstTriangles && !_calculatedMeshTrianglesValid)) {            recalculateMeshBoxes(pickAgainstTriangles);        }         for (const auto& subMeshBox : _calculatedMeshBoxes) {             if (subMeshBox.findRayIntersection(origin, direction, distanceToSubMesh, subMeshFace, subMeshSurfaceNormal)) {                if (distanceToSubMesh < bestDistance) {                    if (pickAgainstTriangles) {                        // check our triangles here....                        const QVector<Triangle>& meshTriangles = _calculatedMeshTriangles[subMeshIndex];                        for(const auto& triangle : meshTriangles) {                            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(render, __FUNCTION__);    bool calculatedMeshTrianglesNeeded = pickAgainstTriangles && !_calculatedMeshTrianglesValid;     if (!_calculatedMeshBoxesValid || calculatedMeshTrianglesNeeded || (!_calculatedMeshPartBoxesValid && pickAgainstTriangles) ) {        const FBXGeometry& 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;

Lines 1–500 of 1495.