1389 lines · 52.3 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(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.