1544 lines · 58.6 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 "Model.h" #include <QMetaType>#include <QRunnable>#include <QThreadPool> #include <glm/gtx/transform.hpp>#include <glm/gtx/norm.hpp> #include <shared/QtHelpers.h>#include <GeometryUtil.h>#include <PathUtils.h>#include <PerfStat.h>#include <ViewFrustum.h>#include <GLMHelpers.h>#include <TBBHelpers.h> #include <model-networking/SimpleMeshProxy.h>#include <DualQuaternion.h> #include <glm/gtc/packing.hpp> #include "AbstractViewStateInterface.h"#include "MeshPartPayload.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();} void Model::setTranslation(const glm::vec3& translation) {    _translation = translation;    updateRenderItems();} void Model::setRotation(const glm::quat& rotation) {    _rotation = rotation;    updateRenderItems();} // temporary HACK: set transform while avoiding implicit calls to updateRenderItems()// TODO: make setRotation() and friends set flag to be used later to decide to updateRenderItems()void Model::setTransformNoUpdateRenderItems(const Transform& transform) {    _translation = transform.getTranslation();    _rotation = transform.getRotation();    // DO NOT call updateRenderItems() here!} 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.0000001f; 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;} bool Model::shouldInvalidatePayloadShapeKey(int meshIndex) {    if (!getGeometry()) {        return true;    }     const FBXGeometry& geometry = getFBXGeometry();    const auto& networkMeshes = getGeometry()->getMeshes();    // if our index is ever out of range for either meshes or networkMeshes, then skip it, and set our _meshGroupsKnown    // to false to rebuild out mesh groups.    if (meshIndex < 0 || meshIndex >= (int)networkMeshes.size() || meshIndex >= (int)geometry.meshes.size() || meshIndex >= (int)_meshStates.size()) {        _needsFixupInScene = true;     // trigger remove/add cycle        invalidCalculatedMeshBoxes();  // if we have to reload, we need to assume our mesh boxes are all invalid        return true;    }     return false;} void Model::updateRenderItems() {    if (!_addedToScene) {        return;    }     _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]() {         // do nothing, if the model has already been destroyed.        auto self = weakSelf.lock();        if (!self || !self->isLoaded()) {            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();         Transform modelTransform = self->getTransform();        modelTransform.setScale(glm::vec3(1.0f));         bool isWireframe = self->isWireframe();        bool isVisible = self->isVisible();        bool isLayeredInFront = self->isLayeredInFront();        bool isLayeredInHUD = self->isLayeredInHUD();         render::Transaction transaction;        for (int i = 0; i < (int) self->_modelMeshRenderItemIDs.size(); i++) {             auto itemID = self->_modelMeshRenderItemIDs[i];            auto meshIndex = self->_modelMeshRenderItemShapes[i].meshIndex;            auto clusterTransforms(self->getMeshState(meshIndex).clusterTransforms);             bool invalidatePayloadShapeKey = self->shouldInvalidatePayloadShapeKey(meshIndex);             transaction.updateItem<ModelMeshPartPayload>(itemID, [modelTransform, clusterTransforms, invalidatePayloadShapeKey,                    isWireframe, isVisible, isLayeredInFront, isLayeredInHUD](ModelMeshPartPayload& data) {                data.updateClusterBuffer(clusterTransforms);                 Transform renderTransform = modelTransform;                if (clusterTransforms.size() == 1) {#if defined(SKIN_DQ)                    Transform transform(clusterTransforms[0].getRotation(),                                        clusterTransforms[0].getScale(),                                        clusterTransforms[0].getTranslation());                    renderTransform = modelTransform.worldTransform(Transform(transform));#else                    renderTransform = modelTransform.worldTransform(Transform(clusterTransforms[0]));#endif                }                data.updateTransformForSkinnedMesh(renderTransform, modelTransform);                 data.setKey(isVisible, isLayeredInFront || isLayeredInHUD);                data.setLayer(isLayeredInFront, isLayeredInHUD);                data.setShapeKey(invalidatePayloadShapeKey, isWireframe);            });        }         Transform collisionMeshOffset;        collisionMeshOffset.setIdentity();        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::setRenderItemsNeedUpdate() {    _renderItemsNeedUpdate = true;    emit requestRenderUpdate();} void Model::reset() {    if (isLoaded()) {        const FBXGeometry& geometry = getFBXGeometry();        _rig.reset(geometry);        emit rigReset();    }} #if FBX_PACK_NORMALSstatic void packNormalAndTangent(glm::vec3 normal, glm::vec3 tangent, glm::uint32& packedNormal, glm::uint32& packedTangent) {    auto absNormal = glm::abs(normal);    auto absTangent = glm::abs(tangent);    normal /= glm::max(1e-6f, glm::max(glm::max(absNormal.x, absNormal.y), absNormal.z));    tangent /= glm::max(1e-6f, glm::max(glm::max(absTangent.x, absTangent.y), absTangent.z));    normal = glm::clamp(normal, -1.0f, 1.0f);    tangent = glm::clamp(tangent, -1.0f, 1.0f);    normal *= 511.0f;    tangent *= 511.0f;    normal = glm::round(normal);    tangent = glm::round(tangent);     glm::detail::i10i10i10i2 normalStruct;    glm::detail::i10i10i10i2 tangentStruct;    normalStruct.data.x = int(normal.x);    normalStruct.data.y = int(normal.y);    normalStruct.data.z = int(normal.z);    normalStruct.data.w = 0;    tangentStruct.data.x = int(tangent.x);    tangentStruct.data.y = int(tangent.y);    tangentStruct.data.z = int(tangent.z);    tangentStruct.data.w = 0;    packedNormal = normalStruct.pack;    packedTangent = tangentStruct.pack;}#endif 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.clusterTransforms.resize(mesh.clusters.size());            _meshStates.push_back(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()) {                std::vector<NormalType> normalsAndTangents;                normalsAndTangents.reserve(mesh.normals.size() + mesh.tangents.size());                 for (auto normalIt = mesh.normals.begin(), tangentIt = mesh.tangents.begin();                     normalIt != mesh.normals.end();                     ++normalIt, ++tangentIt) {#if FBX_PACK_NORMALS                    glm::uint32 finalNormal;                    glm::uint32 finalTangent;                    packNormalAndTangent(*normalIt, *tangentIt, finalNormal, finalTangent);#else                    const auto finalNormal = *normalIt;                    const auto finalTangent = *tangentIt;#endif                    normalsAndTangents.push_back(finalNormal);                    normalsAndTangents.push_back(finalTangent);                }                 buffer->resize(mesh.vertices.size() * (sizeof(glm::vec3) + 2 * sizeof(NormalType)));                buffer->setSubData(0, mesh.vertices.size() * sizeof(glm::vec3), (const gpu::Byte*) mesh.vertices.constData());                buffer->setSubData(mesh.vertices.size() * sizeof(glm::vec3),                                   mesh.normals.size() * 2 * sizeof(NormalType), (const gpu::Byte*) normalsAndTangents.data());            }            _blendedVertexBuffers.push_back(buffer);        }        needFullUpdate = true;        emit rigReady();    }    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;    }

Lines 1–500 of 1544.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t