mirror of
https://github.com/overte-org/overte.git
synced 2025-04-23 02:13:28 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into import-export-parents
This commit is contained in:
commit
1b3d9d7ab6
18 changed files with 778 additions and 841 deletions
interface/src/avatar
libraries
entities-renderer/src
entities/src
model-networking/src/model-networking
model/src/model
render-utils/src
|
@ -466,8 +466,8 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
|
|||
* (1.0f - ((float)(now - getHead()->getLookingAtMeStarted()))
|
||||
/ (LOOKING_AT_ME_DURATION * (float)USECS_PER_SECOND));
|
||||
if (alpha > 0.0f) {
|
||||
QSharedPointer<NetworkGeometry> geometry = _skeletonModel->getGeometry();
|
||||
if (geometry && geometry->isLoaded()) {
|
||||
if (_skeletonModel->isLoaded()) {
|
||||
const auto& geometry = _skeletonModel->getFBXGeometry();
|
||||
const float DEFAULT_EYE_DIAMETER = 0.048f; // Typical human eye
|
||||
const float RADIUS_INCREMENT = 0.005f;
|
||||
batch.setModelTransform(Transform());
|
||||
|
@ -475,7 +475,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
|
|||
glm::vec3 position = getHead()->getLeftEyePosition();
|
||||
Transform transform;
|
||||
transform.setTranslation(position);
|
||||
float eyeDiameter = geometry->getFBXGeometry().leftEyeSize;
|
||||
float eyeDiameter = geometry.leftEyeSize;
|
||||
if (eyeDiameter == 0.0f) {
|
||||
eyeDiameter = DEFAULT_EYE_DIAMETER;
|
||||
}
|
||||
|
@ -486,7 +486,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
|
|||
|
||||
position = getHead()->getRightEyePosition();
|
||||
transform.setTranslation(position);
|
||||
eyeDiameter = geometry->getFBXGeometry().rightEyeSize;
|
||||
eyeDiameter = geometry.rightEyeSize;
|
||||
if (eyeDiameter == 0.0f) {
|
||||
eyeDiameter = DEFAULT_EYE_DIAMETER;
|
||||
}
|
||||
|
@ -815,7 +815,7 @@ int Avatar::getJointIndex(const QString& name) const {
|
|||
Q_RETURN_ARG(int, result), Q_ARG(const QString&, name));
|
||||
return result;
|
||||
}
|
||||
return _skeletonModel->isActive() ? _skeletonModel->getGeometry()->getFBXGeometry().getJointIndex(name) : -1;
|
||||
return _skeletonModel->isActive() ? _skeletonModel->getFBXGeometry().getJointIndex(name) : -1;
|
||||
}
|
||||
|
||||
QStringList Avatar::getJointNames() const {
|
||||
|
@ -825,7 +825,7 @@ QStringList Avatar::getJointNames() const {
|
|||
Q_RETURN_ARG(QStringList, result));
|
||||
return result;
|
||||
}
|
||||
return _skeletonModel->isActive() ? _skeletonModel->getGeometry()->getFBXGeometry().getJointNames() : QStringList();
|
||||
return _skeletonModel->isActive() ? _skeletonModel->getFBXGeometry().getJointNames() : QStringList();
|
||||
}
|
||||
|
||||
glm::vec3 Avatar::getJointPosition(int index) const {
|
||||
|
|
|
@ -1272,8 +1272,8 @@ void MyAvatar::setVisibleInSceneIfReady(Model* model, render::ScenePointer scene
|
|||
|
||||
void MyAvatar::initHeadBones() {
|
||||
int neckJointIndex = -1;
|
||||
if (_skeletonModel->getGeometry()) {
|
||||
neckJointIndex = _skeletonModel->getGeometry()->getFBXGeometry().neckJointIndex;
|
||||
if (_skeletonModel->isLoaded()) {
|
||||
neckJointIndex = _skeletonModel->getFBXGeometry().neckJointIndex;
|
||||
}
|
||||
if (neckJointIndex == -1) {
|
||||
return;
|
||||
|
|
|
@ -39,12 +39,12 @@ SkeletonModel::~SkeletonModel() {
|
|||
}
|
||||
|
||||
void SkeletonModel::initJointStates() {
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
const FBXGeometry& geometry = getFBXGeometry();
|
||||
glm::mat4 modelOffset = glm::scale(_scale) * glm::translate(_offset);
|
||||
_rig->initJointStates(geometry, modelOffset);
|
||||
|
||||
// Determine the default eye position for avatar scale = 1.0
|
||||
int headJointIndex = _geometry->getFBXGeometry().headJointIndex;
|
||||
int headJointIndex = geometry.headJointIndex;
|
||||
if (0 > headJointIndex || headJointIndex >= _rig->getJointStateCount()) {
|
||||
qCWarning(interfaceapp) << "Bad head joint! Got:" << headJointIndex << "jointCount:" << _rig->getJointStateCount();
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ void SkeletonModel::initJointStates() {
|
|||
getEyeModelPositions(leftEyePosition, rightEyePosition);
|
||||
glm::vec3 midEyePosition = (leftEyePosition + rightEyePosition) / 2.0f;
|
||||
|
||||
int rootJointIndex = _geometry->getFBXGeometry().rootJointIndex;
|
||||
int rootJointIndex = geometry.rootJointIndex;
|
||||
glm::vec3 rootModelPosition;
|
||||
getJointPosition(rootJointIndex, rootModelPosition);
|
||||
|
||||
|
@ -87,10 +87,12 @@ Rig::CharacterControllerState convertCharacterControllerState(CharacterControlle
|
|||
|
||||
// Called within Model::simulate call, below.
|
||||
void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
||||
const FBXGeometry& geometry = getFBXGeometry();
|
||||
|
||||
Head* head = _owningAvatar->getHead();
|
||||
|
||||
if (_owningAvatar->isMyAvatar()) {
|
||||
MyAvatar* myAvatar = static_cast<MyAvatar*>(_owningAvatar);
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
|
||||
Rig::HeadParameters headParams;
|
||||
headParams.enableLean = qApp->isHMDMode();
|
||||
|
@ -183,7 +185,6 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
|||
// Thus this should really only be ... else if (_owningAvatar->getHead()->isLookingAtMe()) {...
|
||||
// However, in the !isLookingAtMe case, the eyes aren't rotating the way they should right now.
|
||||
// We will revisit that as priorities allow, and particularly after the new rig/animation/joints.
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
|
||||
// If the head is not positioned, updateEyeJoints won't get the math right
|
||||
glm::quat headOrientation;
|
||||
|
@ -329,22 +330,23 @@ float SkeletonModel::getRightArmLength() const {
|
|||
}
|
||||
|
||||
bool SkeletonModel::getHeadPosition(glm::vec3& headPosition) const {
|
||||
return isActive() && getJointPositionInWorldFrame(_geometry->getFBXGeometry().headJointIndex, headPosition);
|
||||
return isActive() && getJointPositionInWorldFrame(getFBXGeometry().headJointIndex, headPosition);
|
||||
}
|
||||
|
||||
bool SkeletonModel::getNeckPosition(glm::vec3& neckPosition) const {
|
||||
return isActive() && getJointPositionInWorldFrame(_geometry->getFBXGeometry().neckJointIndex, neckPosition);
|
||||
return isActive() && getJointPositionInWorldFrame(getFBXGeometry().neckJointIndex, neckPosition);
|
||||
}
|
||||
|
||||
bool SkeletonModel::getLocalNeckPosition(glm::vec3& neckPosition) const {
|
||||
return isActive() && getJointPosition(_geometry->getFBXGeometry().neckJointIndex, neckPosition);
|
||||
return isActive() && getJointPosition(getFBXGeometry().neckJointIndex, neckPosition);
|
||||
}
|
||||
|
||||
bool SkeletonModel::getEyeModelPositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const {
|
||||
if (!isActive()) {
|
||||
return false;
|
||||
}
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
const FBXGeometry& geometry = getFBXGeometry();
|
||||
|
||||
if (getJointPosition(geometry.leftEyeJointIndex, firstEyePosition) &&
|
||||
getJointPosition(geometry.rightEyeJointIndex, secondEyePosition)) {
|
||||
return true;
|
||||
|
@ -386,11 +388,11 @@ float VERY_BIG_MASS = 1.0e6f;
|
|||
|
||||
// virtual
|
||||
void SkeletonModel::computeBoundingShape() {
|
||||
if (_geometry == NULL || _rig->jointStatesEmpty()) {
|
||||
if (!isLoaded() || _rig->jointStatesEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
const FBXGeometry& geometry = getFBXGeometry();
|
||||
if (geometry.joints.isEmpty() || geometry.rootJointIndex == -1) {
|
||||
// rootJointIndex == -1 if the avatar model has no skeleton
|
||||
return;
|
||||
|
@ -429,7 +431,7 @@ void SkeletonModel::renderBoundingCollisionShapes(gpu::Batch& batch, float scale
|
|||
}
|
||||
|
||||
bool SkeletonModel::hasSkeleton() {
|
||||
return isActive() ? _geometry->getFBXGeometry().rootJointIndex != -1 : false;
|
||||
return isActive() ? getFBXGeometry().rootJointIndex != -1 : false;
|
||||
}
|
||||
|
||||
void SkeletonModel::onInvalidate() {
|
||||
|
|
|
@ -38,10 +38,10 @@ public:
|
|||
void updateAttitude();
|
||||
|
||||
/// Returns the index of the left hand joint, or -1 if not found.
|
||||
int getLeftHandJointIndex() const { return isActive() ? _geometry->getFBXGeometry().leftHandJointIndex : -1; }
|
||||
int getLeftHandJointIndex() const { return isActive() ? getFBXGeometry().leftHandJointIndex : -1; }
|
||||
|
||||
/// Returns the index of the right hand joint, or -1 if not found.
|
||||
int getRightHandJointIndex() const { return isActive() ? _geometry->getFBXGeometry().rightHandJointIndex : -1; }
|
||||
int getRightHandJointIndex() const { return isActive() ? getFBXGeometry().rightHandJointIndex : -1; }
|
||||
|
||||
bool getLeftGrabPosition(glm::vec3& position) const;
|
||||
bool getRightGrabPosition(glm::vec3& position) const;
|
||||
|
|
|
@ -43,7 +43,7 @@ void SoftAttachmentModel::updateClusterMatrices(glm::vec3 modelPosition, glm::qu
|
|||
}
|
||||
_needsUpdateClusterMatrices = false;
|
||||
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
const FBXGeometry& geometry = getFBXGeometry();
|
||||
|
||||
glm::mat4 modelToWorld = glm::mat4_cast(modelOrientation);
|
||||
for (int i = 0; i < _meshStates.size(); i++) {
|
||||
|
|
|
@ -421,8 +421,8 @@ const FBXGeometry* EntityTreeRenderer::getGeometryForEntity(EntityItemPointer en
|
|||
std::dynamic_pointer_cast<RenderableModelEntityItem>(entityItem);
|
||||
assert(modelEntityItem); // we need this!!!
|
||||
ModelPointer model = modelEntityItem->getModel(this);
|
||||
if (model) {
|
||||
result = &model->getGeometry()->getFBXGeometry();
|
||||
if (model && model->isLoaded()) {
|
||||
result = &model->getFBXGeometry();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
@ -446,11 +446,8 @@ const FBXGeometry* EntityTreeRenderer::getCollisionGeometryForEntity(EntityItemP
|
|||
std::dynamic_pointer_cast<RenderableModelEntityItem>(entityItem);
|
||||
if (modelEntityItem->hasCompoundShapeURL()) {
|
||||
ModelPointer model = modelEntityItem->getModel(this);
|
||||
if (model) {
|
||||
const QSharedPointer<NetworkGeometry> collisionNetworkGeometry = model->getCollisionGeometry();
|
||||
if (collisionNetworkGeometry && collisionNetworkGeometry->isLoaded()) {
|
||||
result = &collisionNetworkGeometry->getFBXGeometry();
|
||||
}
|
||||
if (model && model->isCollisionLoaded()) {
|
||||
result = &model->getCollisionFBXGeometry();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -110,7 +110,7 @@ int RenderableModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned c
|
|||
QVariantMap RenderableModelEntityItem::parseTexturesToMap(QString textures) {
|
||||
// If textures are unset, revert to original textures
|
||||
if (textures == "") {
|
||||
return _originalTexturesMap;
|
||||
return _originalTextures;
|
||||
}
|
||||
|
||||
// Legacy: a ,\n-delimited list of filename:"texturepath"
|
||||
|
@ -122,7 +122,7 @@ QVariantMap RenderableModelEntityItem::parseTexturesToMap(QString textures) {
|
|||
QJsonDocument texturesJson = QJsonDocument::fromJson(textures.toUtf8(), &error);
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
qCWarning(entitiesrenderer) << "Could not evaluate textures property value:" << _textures;
|
||||
return _originalTexturesMap;
|
||||
return _originalTextures;
|
||||
}
|
||||
return texturesJson.object().toVariantMap();
|
||||
}
|
||||
|
@ -135,44 +135,23 @@ void RenderableModelEntityItem::remapTextures() {
|
|||
if (!_model->isLoaded()) {
|
||||
return; // nothing to do if the model has not yet loaded
|
||||
}
|
||||
|
||||
|
||||
auto& geometry = _model->getGeometry()->getGeometry();
|
||||
|
||||
if (!_originalTexturesRead) {
|
||||
const QSharedPointer<NetworkGeometry>& networkGeometry = _model->getGeometry();
|
||||
if (networkGeometry) {
|
||||
_originalTextures = networkGeometry->getTextureNames();
|
||||
_originalTexturesMap = parseTexturesToMap(_originalTextures.join(",\n"));
|
||||
_originalTexturesRead = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (_currentTextures == _textures) {
|
||||
return; // nothing to do if our recently mapped textures match our desired textures
|
||||
}
|
||||
|
||||
// since we're changing here, we need to run through our current texture map
|
||||
// and any textures in the recently mapped texture, that is not in our desired
|
||||
// textures, we need to "unset"
|
||||
QVariantMap currentTextureMap = parseTexturesToMap(_currentTextures);
|
||||
QVariantMap textureMap = parseTexturesToMap(_textures);
|
||||
_originalTextures = geometry->getTextures();
|
||||
_originalTexturesRead = true;
|
||||
|
||||
foreach(const QString& key, currentTextureMap.keys()) {
|
||||
// if the desired texture map (what we're setting the textures to) doesn't
|
||||
// contain this texture, then remove it by setting the URL to null
|
||||
if (!textureMap.contains(key)) {
|
||||
QUrl noURL;
|
||||
qCDebug(entitiesrenderer) << "Removing texture named" << key << "by replacing it with no URL";
|
||||
_model->setTextureWithNameToURL(key, noURL);
|
||||
}
|
||||
// Default to _originalTextures to avoid remapping immediately and lagging on load
|
||||
_currentTextures = _originalTextures;
|
||||
}
|
||||
|
||||
// here's where we remap any textures if needed...
|
||||
foreach(const QString& key, textureMap.keys()) {
|
||||
QUrl newTextureURL = textureMap[key].toUrl();
|
||||
qCDebug(entitiesrenderer) << "Updating texture named" << key << "to texture at URL" << newTextureURL;
|
||||
_model->setTextureWithNameToURL(key, newTextureURL);
|
||||
auto textures = parseTexturesToMap(_textures);
|
||||
|
||||
if (textures != _currentTextures) {
|
||||
geometry->setTextures(textures);
|
||||
_currentTextures = textures;
|
||||
}
|
||||
|
||||
_currentTextures = _textures;
|
||||
}
|
||||
|
||||
// TODO: we need a solution for changes to the postion/rotation/etc of a model...
|
||||
|
@ -526,8 +505,7 @@ bool RenderableModelEntityItem::needsToCallUpdate() const {
|
|||
|
||||
void RenderableModelEntityItem::update(const quint64& now) {
|
||||
if (!_dimensionsInitialized && _model && _model->isActive()) {
|
||||
const QSharedPointer<NetworkGeometry> renderNetworkGeometry = _model->getGeometry();
|
||||
if (renderNetworkGeometry && renderNetworkGeometry->isLoaded()) {
|
||||
if (_model->isLoaded()) {
|
||||
EntityItemProperties properties;
|
||||
auto extents = _model->getMeshExtents();
|
||||
properties.setDimensions(extents.maximum - extents.minimum);
|
||||
|
@ -593,13 +571,8 @@ bool RenderableModelEntityItem::isReadyToComputeShape() {
|
|||
return false;
|
||||
}
|
||||
|
||||
const QSharedPointer<NetworkGeometry> collisionNetworkGeometry = _model->getCollisionGeometry();
|
||||
const QSharedPointer<NetworkGeometry> renderNetworkGeometry = _model->getGeometry();
|
||||
|
||||
if ((collisionNetworkGeometry && collisionNetworkGeometry->isLoaded()) &&
|
||||
(renderNetworkGeometry && renderNetworkGeometry->isLoaded())) {
|
||||
if (_model->isLoaded() && _model->isCollisionLoaded()) {
|
||||
// we have both URLs AND both geometries AND they are both fully loaded.
|
||||
|
||||
if (_needsInitialSimulation) {
|
||||
// the _model's offset will be wrong until _needsInitialSimulation is false
|
||||
PerformanceTimer perfTimer("_model->simulate");
|
||||
|
@ -624,15 +597,12 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
|
|||
adjustShapeInfoByRegistration(info);
|
||||
} else {
|
||||
updateModelBounds();
|
||||
const QSharedPointer<NetworkGeometry> collisionNetworkGeometry = _model->getCollisionGeometry();
|
||||
|
||||
// should never fall in here when collision model not fully loaded
|
||||
// hence we assert collisionNetworkGeometry is not NULL
|
||||
assert(collisionNetworkGeometry);
|
||||
|
||||
const FBXGeometry& collisionGeometry = collisionNetworkGeometry->getFBXGeometry();
|
||||
const QSharedPointer<NetworkGeometry> renderNetworkGeometry = _model->getGeometry();
|
||||
const FBXGeometry& renderGeometry = renderNetworkGeometry->getFBXGeometry();
|
||||
// hence we assert that all geometries exist and are loaded
|
||||
assert(_model->isLoaded() && _model->isCollisionLoaded());
|
||||
const FBXGeometry& renderGeometry = _model->getFBXGeometry();
|
||||
const FBXGeometry& collisionGeometry = _model->getCollisionFBXGeometry();
|
||||
|
||||
_points.clear();
|
||||
unsigned int i = 0;
|
||||
|
@ -734,10 +704,8 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
|
|||
}
|
||||
|
||||
bool RenderableModelEntityItem::contains(const glm::vec3& point) const {
|
||||
if (EntityItem::contains(point) && _model && _model->getCollisionGeometry()) {
|
||||
const QSharedPointer<NetworkGeometry> collisionNetworkGeometry = _model->getCollisionGeometry();
|
||||
const FBXGeometry& collisionGeometry = collisionNetworkGeometry->getFBXGeometry();
|
||||
return collisionGeometry.convexHullContains(worldToEntity(point));
|
||||
if (EntityItem::contains(point) && _model && _model->isCollisionLoaded()) {
|
||||
return _model->getCollisionFBXGeometry().convexHullContains(worldToEntity(point));
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
|
@ -90,9 +90,8 @@ private:
|
|||
bool _needsInitialSimulation = true;
|
||||
bool _needsModelReload = true;
|
||||
EntityTreeRenderer* _myRenderer = nullptr;
|
||||
QString _currentTextures;
|
||||
QStringList _originalTextures;
|
||||
QVariantMap _originalTexturesMap;
|
||||
QVariantMap _currentTextures;
|
||||
QVariantMap _originalTextures;
|
||||
bool _originalTexturesRead = false;
|
||||
QVector<QVector<glm::vec3>> _points;
|
||||
bool _dimensionsInitialized = true;
|
||||
|
|
|
@ -529,9 +529,9 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
|
|||
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_NO_SKIP(boundingBox, boundingBox); // gettable, but not settable
|
||||
}
|
||||
|
||||
QString textureNamesList = _textureNames.join(",\n");
|
||||
QString textureNamesStr = QJsonDocument::fromVariant(_textureNames).toJson();
|
||||
if (!skipDefaults) {
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_NO_SKIP(originalTextures, textureNamesList); // gettable, but not settable
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_NO_SKIP(originalTextures, textureNamesStr); // gettable, but not settable
|
||||
}
|
||||
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_PARENT_ID, parentID);
|
||||
|
|
|
@ -245,8 +245,8 @@ public:
|
|||
const glm::vec3& getNaturalPosition() const { return _naturalPosition; }
|
||||
void calculateNaturalPosition(const glm::vec3& min, const glm::vec3& max);
|
||||
|
||||
const QStringList& getTextureNames() const { return _textureNames; }
|
||||
void setTextureNames(const QStringList& value) { _textureNames = value; }
|
||||
const QVariantMap& getTextureNames() const { return _textureNames; }
|
||||
void setTextureNames(const QVariantMap& value) { _textureNames = value; }
|
||||
|
||||
QString getSimulatorIDAsString() const { return _simulationOwner.getID().toString().mid(1,36).toUpper(); }
|
||||
|
||||
|
@ -297,7 +297,7 @@ private:
|
|||
// NOTE: The following are pseudo client only properties. They are only used in clients which can access
|
||||
// properties of model geometry. But these properties are not serialized like other properties.
|
||||
QVector<SittingPoint> _sittingPoints;
|
||||
QStringList _textureNames;
|
||||
QVariantMap _textureNames;
|
||||
glm::vec3 _naturalDimensions;
|
||||
glm::vec3 _naturalPosition;
|
||||
|
||||
|
|
|
@ -1,53 +1,109 @@
|
|||
//
|
||||
// ModelCache.cpp
|
||||
// interface/src/renderer
|
||||
// libraries/model-networking
|
||||
//
|
||||
// Created by Andrzej Kapolka on 6/21/13.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
// Created by Zach Pomerantz on 3/15/16.
|
||||
// Copyright 2016 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 "ModelCache.h"
|
||||
#include <FSTReader.h>
|
||||
#include "FBXReader.h"
|
||||
#include "OBJReader.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <gpu/Batch.h>
|
||||
#include <gpu/Stream.h>
|
||||
|
||||
#include <QNetworkReply>
|
||||
#include <QThreadPool>
|
||||
|
||||
#include <FSTReader.h>
|
||||
#include <NumericalConstants.h>
|
||||
|
||||
#include "TextureCache.h"
|
||||
#include "ModelNetworkingLogging.h"
|
||||
|
||||
#include "model/TextureMap.h"
|
||||
class GeometryReader;
|
||||
|
||||
//#define WANT_DEBUG
|
||||
class GeometryExtra {
|
||||
public:
|
||||
const QVariantHash& mapping;
|
||||
const QUrl& textureBaseUrl;
|
||||
};
|
||||
|
||||
ModelCache::ModelCache()
|
||||
{
|
||||
const qint64 GEOMETRY_DEFAULT_UNUSED_MAX_SIZE = DEFAULT_UNUSED_MAX_SIZE;
|
||||
setUnusedResourceCacheSize(GEOMETRY_DEFAULT_UNUSED_MAX_SIZE);
|
||||
class GeometryMappingResource : public GeometryResource {
|
||||
Q_OBJECT
|
||||
public:
|
||||
GeometryMappingResource(const QUrl& url) : GeometryResource(url) {};
|
||||
|
||||
virtual void downloadFinished(const QByteArray& data) override;
|
||||
|
||||
private slots:
|
||||
void onGeometryMappingLoaded(bool success);
|
||||
|
||||
private:
|
||||
GeometryResource::Pointer _geometryResource;
|
||||
};
|
||||
|
||||
void GeometryMappingResource::downloadFinished(const QByteArray& data) {
|
||||
auto mapping = FSTReader::readMapping(data);
|
||||
|
||||
QString filename = mapping.value("filename").toString();
|
||||
if (filename.isNull()) {
|
||||
qCDebug(modelnetworking) << "Mapping file" << _url << "has no \"filename\" field";
|
||||
finishedLoading(false);
|
||||
} else {
|
||||
QUrl url = _url.resolved(filename);
|
||||
QUrl textureBaseUrl;
|
||||
|
||||
QString texdir = mapping.value("texdir").toString();
|
||||
if (!texdir.isNull()) {
|
||||
if (!texdir.endsWith('/')) {
|
||||
texdir += '/';
|
||||
}
|
||||
textureBaseUrl = _url.resolved(texdir);
|
||||
}
|
||||
|
||||
auto modelCache = DependencyManager::get<ModelCache>();
|
||||
GeometryExtra extra{ mapping, textureBaseUrl };
|
||||
|
||||
// Get the raw GeometryResource, not the wrapped NetworkGeometry
|
||||
_geometryResource = modelCache->getResource(url, QUrl(), true, &extra).staticCast<GeometryResource>();
|
||||
|
||||
if (_geometryResource->isLoaded()) {
|
||||
onGeometryMappingLoaded(!_geometryResource->getURL().isEmpty());
|
||||
} else {
|
||||
connect(_geometryResource.data(), &Resource::finished, this, &GeometryMappingResource::onGeometryMappingLoaded);
|
||||
}
|
||||
|
||||
// Avoid caching nested resources - their references will be held by the parent
|
||||
_geometryResource->_isCacheable = false;
|
||||
}
|
||||
}
|
||||
|
||||
ModelCache::~ModelCache() {
|
||||
void GeometryMappingResource::onGeometryMappingLoaded(bool success) {
|
||||
if (success) {
|
||||
_geometry = _geometryResource->_geometry;
|
||||
_shapes = _geometryResource->_shapes;
|
||||
_meshes = _geometryResource->_meshes;
|
||||
_materials = _geometryResource->_materials;
|
||||
}
|
||||
finishedLoading(success);
|
||||
}
|
||||
|
||||
QSharedPointer<Resource> ModelCache::createResource(const QUrl& url, const QSharedPointer<Resource>& fallback,
|
||||
bool delayLoad, const void* extra) {
|
||||
// NetworkGeometry is no longer a subclass of Resource, but requires this method because, it is pure virtual.
|
||||
assert(false);
|
||||
return QSharedPointer<Resource>();
|
||||
}
|
||||
class GeometryReader : public QRunnable {
|
||||
public:
|
||||
GeometryReader(QWeakPointer<Resource>& resource, const QUrl& url, const QVariantHash& mapping,
|
||||
const QByteArray& data) :
|
||||
_resource(resource), _url(url), _mapping(mapping), _data(data) {}
|
||||
virtual ~GeometryReader() = default;
|
||||
|
||||
virtual void run() override;
|
||||
|
||||
GeometryReader::GeometryReader(const QUrl& url, const QByteArray& data, const QVariantHash& mapping) :
|
||||
_url(url),
|
||||
_data(data),
|
||||
_mapping(mapping) {
|
||||
}
|
||||
private:
|
||||
QWeakPointer<Resource> _resource;
|
||||
QUrl _url;
|
||||
QVariantHash _mapping;
|
||||
QByteArray _data;
|
||||
};
|
||||
|
||||
void GeometryReader::run() {
|
||||
auto originalPriority = QThread::currentThread()->priority();
|
||||
|
@ -55,458 +111,389 @@ void GeometryReader::run() {
|
|||
originalPriority = QThread::NormalPriority;
|
||||
}
|
||||
QThread::currentThread()->setPriority(QThread::LowPriority);
|
||||
|
||||
// Ensure the resource is still being requested
|
||||
auto resource = _resource.toStrongRef();
|
||||
if (!resource) {
|
||||
qCWarning(modelnetworking) << "Abandoning load of" << _url << "; could not get strong ref";
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (_data.isEmpty()) {
|
||||
throw QString("Reply is NULL ?!");
|
||||
throw QString("reply is NULL");
|
||||
}
|
||||
QString urlname = _url.path().toLower();
|
||||
bool urlValid = true;
|
||||
urlValid &= !urlname.isEmpty();
|
||||
urlValid &= !_url.path().isEmpty();
|
||||
urlValid &= _url.path().toLower().endsWith(".fbx") || _url.path().toLower().endsWith(".obj");
|
||||
|
||||
if (urlValid) {
|
||||
// Let's read the binaries from the network
|
||||
FBXGeometry* fbxgeo = nullptr;
|
||||
QString urlname = _url.path().toLower();
|
||||
if (!urlname.isEmpty() && !_url.path().isEmpty() &&
|
||||
(_url.path().toLower().endsWith(".fbx") || _url.path().toLower().endsWith(".obj"))) {
|
||||
FBXGeometry* fbxGeometry = nullptr;
|
||||
|
||||
if (_url.path().toLower().endsWith(".fbx")) {
|
||||
const bool grabLightmaps = true;
|
||||
const float lightmapLevel = 1.0f;
|
||||
fbxgeo = readFBX(_data, _mapping, _url.path(), grabLightmaps, lightmapLevel);
|
||||
if (fbxgeo->meshes.size() == 0 && fbxgeo->joints.size() == 0) {
|
||||
// empty fbx geometry, indicates error
|
||||
fbxGeometry = readFBX(_data, _mapping, _url.path());
|
||||
if (fbxGeometry->meshes.size() == 0 && fbxGeometry->joints.size() == 0) {
|
||||
throw QString("empty geometry, possibly due to an unsupported FBX version");
|
||||
}
|
||||
} else if (_url.path().toLower().endsWith(".obj")) {
|
||||
fbxgeo = OBJReader().readOBJ(_data, _mapping, _url);
|
||||
fbxGeometry = OBJReader().readOBJ(_data, _mapping, _url);
|
||||
} else {
|
||||
QString errorStr("unsupported format");
|
||||
throw errorStr;
|
||||
throw QString("unsupported format");
|
||||
}
|
||||
emit onSuccess(fbxgeo);
|
||||
|
||||
QMetaObject::invokeMethod(resource.data(), "setGeometryDefinition",
|
||||
Q_ARG(void*, fbxGeometry));
|
||||
} else {
|
||||
throw QString("url is invalid");
|
||||
}
|
||||
|
||||
} catch (const QString& error) {
|
||||
qCDebug(modelnetworking) << "Error reading " << _url << ": " << error;
|
||||
emit onError(NetworkGeometry::ModelParseError, error);
|
||||
QMetaObject::invokeMethod(resource.data(), "finishedLoading", Q_ARG(bool, false));
|
||||
}
|
||||
|
||||
QThread::currentThread()->setPriority(originalPriority);
|
||||
}
|
||||
|
||||
NetworkGeometry::NetworkGeometry(const QUrl& url, bool delayLoad, const QVariantHash& mapping, const QUrl& textureBaseUrl) :
|
||||
_url(url),
|
||||
_mapping(mapping),
|
||||
_textureBaseUrl(textureBaseUrl.isValid() ? textureBaseUrl : url) {
|
||||
class GeometryDefinitionResource : public GeometryResource {
|
||||
Q_OBJECT
|
||||
public:
|
||||
GeometryDefinitionResource(const QUrl& url, const QVariantHash& mapping, const QUrl& textureBaseUrl) :
|
||||
GeometryResource(url), _mapping(mapping), _textureBaseUrl(textureBaseUrl.isValid() ? textureBaseUrl : url) {}
|
||||
|
||||
if (delayLoad) {
|
||||
_state = DelayState;
|
||||
} else {
|
||||
attemptRequestInternal();
|
||||
}
|
||||
virtual void downloadFinished(const QByteArray& data) override;
|
||||
|
||||
protected:
|
||||
Q_INVOKABLE void setGeometryDefinition(void* fbxGeometry);
|
||||
|
||||
private:
|
||||
QVariantHash _mapping;
|
||||
QUrl _textureBaseUrl;
|
||||
};
|
||||
|
||||
void GeometryDefinitionResource::downloadFinished(const QByteArray& data) {
|
||||
QThreadPool::globalInstance()->start(new GeometryReader(_self, _url, _mapping, data));
|
||||
}
|
||||
|
||||
NetworkGeometry::~NetworkGeometry() {
|
||||
if (_resource) {
|
||||
_resource->deleteLater();
|
||||
}
|
||||
}
|
||||
void GeometryDefinitionResource::setGeometryDefinition(void* fbxGeometry) {
|
||||
// Assume ownership of the geometry pointer
|
||||
_geometry.reset(static_cast<FBXGeometry*>(fbxGeometry));
|
||||
|
||||
void NetworkGeometry::attemptRequest() {
|
||||
if (_state == DelayState) {
|
||||
attemptRequestInternal();
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkGeometry::attemptRequestInternal() {
|
||||
if (_url.path().toLower().endsWith(".fst")) {
|
||||
_mappingUrl = _url;
|
||||
requestMapping(_url);
|
||||
} else {
|
||||
_modelUrl = _url;
|
||||
requestModel(_url);
|
||||
}
|
||||
}
|
||||
|
||||
bool NetworkGeometry::isLoaded() const {
|
||||
return _state == SuccessState;
|
||||
}
|
||||
|
||||
bool NetworkGeometry::isLoadedWithTextures() const {
|
||||
if (!isLoaded()) {
|
||||
return false;
|
||||
// Copy materials
|
||||
QHash<QString, size_t> materialIDAtlas;
|
||||
for (const FBXMaterial& material : _geometry->materials) {
|
||||
materialIDAtlas[material.materialID] = _materials.size();
|
||||
_materials.push_back(std::make_shared<NetworkMaterial>(material, _textureBaseUrl));
|
||||
}
|
||||
|
||||
if (!_isLoadedWithTextures) {
|
||||
_hasTransparentTextures = false;
|
||||
|
||||
for (auto&& material : _materials) {
|
||||
if ((material->albedoTexture && !material->albedoTexture->isLoaded()) ||
|
||||
(material->normalTexture && !material->normalTexture->isLoaded()) ||
|
||||
(material->roughnessTexture && !material->roughnessTexture->isLoaded()) ||
|
||||
(material->metallicTexture && !material->metallicTexture->isLoaded()) ||
|
||||
(material->occlusionTexture && !material->occlusionTexture->isLoaded()) ||
|
||||
(material->emissiveTexture && !material->emissiveTexture->isLoaded()) ||
|
||||
(material->lightmapTexture && !material->lightmapTexture->isLoaded())) {
|
||||
return false;
|
||||
}
|
||||
if (material->albedoTexture && material->albedoTexture->getGPUTexture()) {
|
||||
// Reassign the texture to make sure that itsalbedo alpha channel material key is detected correctly
|
||||
material->_material->setTextureMap(model::MaterialKey::ALBEDO_MAP, material->_material->getTextureMap(model::MaterialKey::ALBEDO_MAP));
|
||||
const auto& usage = material->albedoTexture->getGPUTexture()->getUsage();
|
||||
bool isTransparentTexture = usage.isAlpha() && !usage.isAlphaMask();
|
||||
_hasTransparentTextures |= isTransparentTexture;
|
||||
}
|
||||
}
|
||||
|
||||
_isLoadedWithTextures = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void NetworkGeometry::setTextureWithNameToURL(const QString& name, const QUrl& url) {
|
||||
if (_meshes.size() > 0) {
|
||||
auto textureCache = DependencyManager::get<TextureCache>();
|
||||
for (auto&& material : _materials) {
|
||||
auto networkMaterial = material->_material;
|
||||
auto oldTextureMaps = networkMaterial->getTextureMaps();
|
||||
if (material->albedoTextureName == name) {
|
||||
material->albedoTexture = textureCache->getTexture(url, DEFAULT_TEXTURE);
|
||||
|
||||
auto albedoMap = model::TextureMapPointer(new model::TextureMap());
|
||||
albedoMap->setTextureSource(material->albedoTexture->_textureSource);
|
||||
albedoMap->setTextureTransform(oldTextureMaps[model::MaterialKey::ALBEDO_MAP]->getTextureTransform());
|
||||
// when reassigning the albedo texture we also check for the alpha channel used as opacity
|
||||
albedoMap->setUseAlphaChannel(true);
|
||||
networkMaterial->setTextureMap(model::MaterialKey::ALBEDO_MAP, albedoMap);
|
||||
} else if (material->normalTextureName == name) {
|
||||
material->normalTexture = textureCache->getTexture(url);
|
||||
|
||||
auto normalMap = model::TextureMapPointer(new model::TextureMap());
|
||||
normalMap->setTextureSource(material->normalTexture->_textureSource);
|
||||
|
||||
networkMaterial->setTextureMap(model::MaterialKey::NORMAL_MAP, normalMap);
|
||||
} else if (material->roughnessTextureName == name) {
|
||||
// FIXME: If passing a gloss map instead of a roughmap how to say that ? looking for gloss in the name ?
|
||||
material->roughnessTexture = textureCache->getTexture(url, ROUGHNESS_TEXTURE);
|
||||
|
||||
auto roughnessMap = model::TextureMapPointer(new model::TextureMap());
|
||||
roughnessMap->setTextureSource(material->roughnessTexture->_textureSource);
|
||||
|
||||
networkMaterial->setTextureMap(model::MaterialKey::ROUGHNESS_MAP, roughnessMap);
|
||||
} else if (material->metallicTextureName == name) {
|
||||
// FIXME: If passing a specular map instead of a metallic how to say that ? looking for wtf in the name ?
|
||||
material->metallicTexture = textureCache->getTexture(url, METALLIC_TEXTURE);
|
||||
|
||||
auto glossMap = model::TextureMapPointer(new model::TextureMap());
|
||||
glossMap->setTextureSource(material->metallicTexture->_textureSource);
|
||||
|
||||
networkMaterial->setTextureMap(model::MaterialKey::METALLIC_MAP, glossMap);
|
||||
} else if (material->emissiveTextureName == name) {
|
||||
material->emissiveTexture = textureCache->getTexture(url, EMISSIVE_TEXTURE);
|
||||
|
||||
auto emissiveMap = model::TextureMapPointer(new model::TextureMap());
|
||||
emissiveMap->setTextureSource(material->emissiveTexture->_textureSource);
|
||||
|
||||
networkMaterial->setTextureMap(model::MaterialKey::EMISSIVE_MAP, emissiveMap);
|
||||
} else if (material->lightmapTextureName == name) {
|
||||
material->lightmapTexture = textureCache->getTexture(url, LIGHTMAP_TEXTURE);
|
||||
|
||||
auto lightmapMap = model::TextureMapPointer(new model::TextureMap());
|
||||
lightmapMap->setTextureSource(material->lightmapTexture->_textureSource);
|
||||
lightmapMap->setTextureTransform(
|
||||
oldTextureMaps[model::MaterialKey::LIGHTMAP_MAP]->getTextureTransform());
|
||||
glm::vec2 oldOffsetScale =
|
||||
oldTextureMaps[model::MaterialKey::LIGHTMAP_MAP]->getLightmapOffsetScale();
|
||||
lightmapMap->setLightmapOffsetScale(oldOffsetScale.x, oldOffsetScale.y);
|
||||
|
||||
networkMaterial->setTextureMap(model::MaterialKey::LIGHTMAP_MAP, lightmapMap);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
qCWarning(modelnetworking) << "Ignoring setTextureWithNameToURL() geometry not ready." << name << url;
|
||||
}
|
||||
_isLoadedWithTextures = false;
|
||||
}
|
||||
|
||||
QStringList NetworkGeometry::getTextureNames() const {
|
||||
QStringList result;
|
||||
for (auto&& material : _materials) {
|
||||
if (!material->emissiveTextureName.isEmpty() && material->emissiveTexture) {
|
||||
QString textureURL = material->emissiveTexture->getURL().toString();
|
||||
result << material->emissiveTextureName + ":\"" + textureURL + "\"";
|
||||
}
|
||||
|
||||
if (!material->albedoTextureName.isEmpty() && material->albedoTexture) {
|
||||
QString textureURL = material->albedoTexture->getURL().toString();
|
||||
result << material->albedoTextureName + ":\"" + textureURL + "\"";
|
||||
}
|
||||
|
||||
if (!material->normalTextureName.isEmpty() && material->normalTexture) {
|
||||
QString textureURL = material->normalTexture->getURL().toString();
|
||||
result << material->normalTextureName + ":\"" + textureURL + "\"";
|
||||
}
|
||||
|
||||
if (!material->roughnessTextureName.isEmpty() && material->roughnessTexture) {
|
||||
QString textureURL = material->roughnessTexture->getURL().toString();
|
||||
result << material->roughnessTextureName + ":\"" + textureURL + "\"";
|
||||
}
|
||||
|
||||
if (!material->metallicTextureName.isEmpty() && material->metallicTexture) {
|
||||
QString textureURL = material->metallicTexture->getURL().toString();
|
||||
result << material->metallicTextureName + ":\"" + textureURL + "\"";
|
||||
}
|
||||
|
||||
if (!material->occlusionTextureName.isEmpty() && material->occlusionTexture) {
|
||||
QString textureURL = material->occlusionTexture->getURL().toString();
|
||||
result << material->occlusionTextureName + ":\"" + textureURL + "\"";
|
||||
}
|
||||
|
||||
if (!material->lightmapTextureName.isEmpty() && material->lightmapTexture) {
|
||||
QString textureURL = material->lightmapTexture->getURL().toString();
|
||||
result << material->lightmapTextureName + ":\"" + textureURL + "\"";
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void NetworkGeometry::requestMapping(const QUrl& url) {
|
||||
_state = RequestMappingState;
|
||||
if (_resource) {
|
||||
_resource->deleteLater();
|
||||
}
|
||||
_resource = new Resource(url, false);
|
||||
connect(_resource, &Resource::loaded, this, &NetworkGeometry::mappingRequestDone);
|
||||
connect(_resource, &Resource::failed, this, &NetworkGeometry::mappingRequestError);
|
||||
}
|
||||
|
||||
void NetworkGeometry::requestModel(const QUrl& url) {
|
||||
_state = RequestModelState;
|
||||
if (_resource) {
|
||||
_resource->deleteLater();
|
||||
}
|
||||
_modelUrl = url;
|
||||
_resource = new Resource(url, false);
|
||||
connect(_resource, &Resource::loaded, this, &NetworkGeometry::modelRequestDone);
|
||||
connect(_resource, &Resource::failed, this, &NetworkGeometry::modelRequestError);
|
||||
}
|
||||
|
||||
void NetworkGeometry::mappingRequestDone(const QByteArray data) {
|
||||
assert(_state == RequestMappingState);
|
||||
|
||||
// parse the mapping file
|
||||
_mapping = FSTReader::readMapping(data);
|
||||
|
||||
QUrl replyUrl = _mappingUrl;
|
||||
QString modelUrlStr = _mapping.value("filename").toString();
|
||||
if (modelUrlStr.isNull()) {
|
||||
qCDebug(modelnetworking) << "Mapping file " << _url << "has no \"filename\" entry";
|
||||
emit onFailure(*this, MissingFilenameInMapping);
|
||||
} else {
|
||||
// read _textureBase from mapping file, if present
|
||||
QString texdir = _mapping.value("texdir").toString();
|
||||
if (!texdir.isNull()) {
|
||||
if (!texdir.endsWith('/')) {
|
||||
texdir += '/';
|
||||
}
|
||||
_textureBaseUrl = replyUrl.resolved(texdir);
|
||||
}
|
||||
|
||||
_modelUrl = replyUrl.resolved(modelUrlStr);
|
||||
requestModel(_modelUrl);
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkGeometry::mappingRequestError(QNetworkReply::NetworkError error) {
|
||||
assert(_state == RequestMappingState);
|
||||
_state = ErrorState;
|
||||
emit onFailure(*this, MappingRequestError);
|
||||
}
|
||||
|
||||
void NetworkGeometry::modelRequestDone(const QByteArray data) {
|
||||
assert(_state == RequestModelState);
|
||||
|
||||
_state = ParsingModelState;
|
||||
|
||||
// asynchronously parse the model file.
|
||||
GeometryReader* geometryReader = new GeometryReader(_modelUrl, data, _mapping);
|
||||
connect(geometryReader, SIGNAL(onSuccess(FBXGeometry*)), SLOT(modelParseSuccess(FBXGeometry*)));
|
||||
connect(geometryReader, SIGNAL(onError(int, QString)), SLOT(modelParseError(int, QString)));
|
||||
|
||||
QThreadPool::globalInstance()->start(geometryReader);
|
||||
}
|
||||
|
||||
void NetworkGeometry::modelRequestError(QNetworkReply::NetworkError error) {
|
||||
assert(_state == RequestModelState);
|
||||
_state = ErrorState;
|
||||
emit onFailure(*this, ModelRequestError);
|
||||
}
|
||||
|
||||
static NetworkMesh* buildNetworkMesh(const FBXMesh& mesh, const QUrl& textureBaseUrl) {
|
||||
NetworkMesh* networkMesh = new NetworkMesh();
|
||||
|
||||
networkMesh->_mesh = mesh._mesh;
|
||||
|
||||
return networkMesh;
|
||||
}
|
||||
|
||||
|
||||
static model::TextureMapPointer setupNetworkTextureMap(NetworkGeometry* geometry, const QUrl& textureBaseUrl,
|
||||
const FBXTexture& texture, TextureType type,
|
||||
NetworkTexturePointer& networkTexture, QString& networkTextureName) {
|
||||
auto textureCache = DependencyManager::get<TextureCache>();
|
||||
|
||||
// If content is inline, cache it under the fbx file, not its base url
|
||||
const auto baseUrl = texture.content.isEmpty() ? textureBaseUrl : QUrl(textureBaseUrl.url() + "/");
|
||||
const auto filename = baseUrl.resolved(QUrl(texture.filename));
|
||||
|
||||
networkTexture = textureCache->getTexture(filename, type, texture.content);
|
||||
QObject::connect(networkTexture.data(), &NetworkTexture::networkTextureCreated, geometry, &NetworkGeometry::textureLoaded);
|
||||
networkTextureName = texture.name;
|
||||
|
||||
auto map = std::make_shared<model::TextureMap>();
|
||||
map->setTextureSource(networkTexture->_textureSource);
|
||||
return map;
|
||||
}
|
||||
|
||||
static NetworkMaterial* buildNetworkMaterial(NetworkGeometry* geometry, const FBXMaterial& material, const QUrl& textureBaseUrl) {
|
||||
NetworkMaterial* networkMaterial = new NetworkMaterial();
|
||||
networkMaterial->_material = material._material;
|
||||
|
||||
if (!material.albedoTexture.filename.isEmpty()) {
|
||||
auto albedoMap = setupNetworkTextureMap(geometry, textureBaseUrl, material.albedoTexture, DEFAULT_TEXTURE,
|
||||
networkMaterial->albedoTexture, networkMaterial->albedoTextureName);
|
||||
albedoMap->setTextureTransform(material.albedoTexture.transform);
|
||||
|
||||
if (!material.opacityTexture.filename.isEmpty()) {
|
||||
if (material.albedoTexture.filename == material.opacityTexture.filename) {
|
||||
// Best case scenario, just indicating that the albedo map contains transparency
|
||||
albedoMap->setUseAlphaChannel(true);
|
||||
} else {
|
||||
// Opacity Map is different from the Abledo map, not supported
|
||||
}
|
||||
}
|
||||
|
||||
material._material->setTextureMap(model::MaterialKey::ALBEDO_MAP, albedoMap);
|
||||
}
|
||||
|
||||
|
||||
if (!material.normalTexture.filename.isEmpty()) {
|
||||
auto normalMap = setupNetworkTextureMap(geometry, textureBaseUrl, material.normalTexture,
|
||||
(material.normalTexture.isBumpmap ? BUMP_TEXTURE : NORMAL_TEXTURE),
|
||||
networkMaterial->normalTexture, networkMaterial->normalTextureName);
|
||||
networkMaterial->_material->setTextureMap(model::MaterialKey::NORMAL_MAP, normalMap);
|
||||
}
|
||||
|
||||
// Roughness first or gloss maybe
|
||||
if (!material.roughnessTexture.filename.isEmpty()) {
|
||||
auto roughnessMap = setupNetworkTextureMap(geometry, textureBaseUrl, material.roughnessTexture, ROUGHNESS_TEXTURE,
|
||||
networkMaterial->roughnessTexture, networkMaterial->roughnessTextureName);
|
||||
material._material->setTextureMap(model::MaterialKey::ROUGHNESS_MAP, roughnessMap);
|
||||
} else if (!material.glossTexture.filename.isEmpty()) {
|
||||
auto roughnessMap = setupNetworkTextureMap(geometry, textureBaseUrl, material.glossTexture, GLOSS_TEXTURE,
|
||||
networkMaterial->roughnessTexture, networkMaterial->roughnessTextureName);
|
||||
material._material->setTextureMap(model::MaterialKey::ROUGHNESS_MAP, roughnessMap);
|
||||
}
|
||||
|
||||
// Metallic first or specular maybe
|
||||
|
||||
if (!material.metallicTexture.filename.isEmpty()) {
|
||||
auto metallicMap = setupNetworkTextureMap(geometry, textureBaseUrl, material.metallicTexture, METALLIC_TEXTURE,
|
||||
networkMaterial->metallicTexture, networkMaterial->metallicTextureName);
|
||||
material._material->setTextureMap(model::MaterialKey::METALLIC_MAP, metallicMap);
|
||||
} else if (!material.specularTexture.filename.isEmpty()) {
|
||||
|
||||
auto metallicMap = setupNetworkTextureMap(geometry, textureBaseUrl, material.specularTexture, SPECULAR_TEXTURE,
|
||||
networkMaterial->metallicTexture, networkMaterial->metallicTextureName);
|
||||
material._material->setTextureMap(model::MaterialKey::METALLIC_MAP, metallicMap);
|
||||
}
|
||||
|
||||
if (!material.occlusionTexture.filename.isEmpty()) {
|
||||
auto occlusionMap = setupNetworkTextureMap(geometry, textureBaseUrl, material.occlusionTexture, OCCLUSION_TEXTURE,
|
||||
networkMaterial->occlusionTexture, networkMaterial->occlusionTextureName);
|
||||
material._material->setTextureMap(model::MaterialKey::OCCLUSION_MAP, occlusionMap);
|
||||
}
|
||||
|
||||
if (!material.emissiveTexture.filename.isEmpty()) {
|
||||
auto emissiveMap = setupNetworkTextureMap(geometry, textureBaseUrl, material.emissiveTexture, EMISSIVE_TEXTURE,
|
||||
networkMaterial->emissiveTexture, networkMaterial->emissiveTextureName);
|
||||
material._material->setTextureMap(model::MaterialKey::EMISSIVE_MAP, emissiveMap);
|
||||
}
|
||||
|
||||
if (!material.lightmapTexture.filename.isEmpty()) {
|
||||
auto lightmapMap = setupNetworkTextureMap(geometry, textureBaseUrl, material.lightmapTexture, LIGHTMAP_TEXTURE,
|
||||
networkMaterial->lightmapTexture, networkMaterial->lightmapTextureName);
|
||||
lightmapMap->setTextureTransform(material.lightmapTexture.transform);
|
||||
lightmapMap->setLightmapOffsetScale(material.lightmapParams.x, material.lightmapParams.y);
|
||||
material._material->setTextureMap(model::MaterialKey::LIGHTMAP_MAP, lightmapMap);
|
||||
}
|
||||
|
||||
return networkMaterial;
|
||||
}
|
||||
|
||||
|
||||
void NetworkGeometry::modelParseSuccess(FBXGeometry* geometry) {
|
||||
// assume owner ship of geometry pointer
|
||||
_geometry.reset(geometry);
|
||||
|
||||
|
||||
|
||||
foreach(const FBXMesh& mesh, _geometry->meshes) {
|
||||
_meshes.emplace_back(buildNetworkMesh(mesh, _textureBaseUrl));
|
||||
}
|
||||
|
||||
QHash<QString, size_t> fbxMatIDToMatID;
|
||||
foreach(const FBXMaterial& material, _geometry->materials) {
|
||||
fbxMatIDToMatID[material.materialID] = _materials.size();
|
||||
_materials.emplace_back(buildNetworkMaterial(this, material, _textureBaseUrl));
|
||||
}
|
||||
|
||||
|
||||
std::shared_ptr<NetworkMeshes> meshes = std::make_shared<NetworkMeshes>();
|
||||
std::shared_ptr<NetworkShapes> shapes = std::make_shared<NetworkShapes>();
|
||||
int meshID = 0;
|
||||
foreach(const FBXMesh& mesh, _geometry->meshes) {
|
||||
for (const FBXMesh& mesh : _geometry->meshes) {
|
||||
// Copy mesh pointers
|
||||
meshes->emplace_back(mesh._mesh);
|
||||
int partID = 0;
|
||||
foreach (const FBXMeshPart& part, mesh.parts) {
|
||||
NetworkShape* networkShape = new NetworkShape();
|
||||
networkShape->_meshID = meshID;
|
||||
networkShape->_partID = partID;
|
||||
networkShape->_materialID = (int)fbxMatIDToMatID[part.materialID];
|
||||
_shapes.emplace_back(networkShape);
|
||||
for (const FBXMeshPart& part : mesh.parts) {
|
||||
// Construct local shapes
|
||||
shapes->push_back(std::make_shared<NetworkShape>(meshID, partID, (int)materialIDAtlas[part.materialID]));
|
||||
partID++;
|
||||
}
|
||||
meshID++;
|
||||
}
|
||||
_meshes = meshes;
|
||||
_shapes = shapes;
|
||||
|
||||
_state = SuccessState;
|
||||
emit onSuccess(*this, *_geometry.get());
|
||||
|
||||
delete _resource;
|
||||
_resource = nullptr;
|
||||
finishedLoading(true);
|
||||
}
|
||||
|
||||
void NetworkGeometry::modelParseError(int error, QString str) {
|
||||
_state = ErrorState;
|
||||
emit onFailure(*this, (NetworkGeometry::Error)error);
|
||||
|
||||
delete _resource;
|
||||
_resource = nullptr;
|
||||
ModelCache::ModelCache() {
|
||||
const qint64 GEOMETRY_DEFAULT_UNUSED_MAX_SIZE = DEFAULT_UNUSED_MAX_SIZE;
|
||||
setUnusedResourceCacheSize(GEOMETRY_DEFAULT_UNUSED_MAX_SIZE);
|
||||
}
|
||||
|
||||
const NetworkMaterial* NetworkGeometry::getShapeMaterial(int shapeID) {
|
||||
if ((shapeID >= 0) && (shapeID < (int)_shapes.size())) {
|
||||
int materialID = _shapes[shapeID]->_materialID;
|
||||
if ((materialID >= 0) && ((unsigned int)materialID < _materials.size())) {
|
||||
return _materials[materialID].get();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
QSharedPointer<Resource> ModelCache::createResource(const QUrl& url, const QSharedPointer<Resource>& fallback,
|
||||
bool delayLoad, const void* extra) {
|
||||
const GeometryExtra* geometryExtra = static_cast<const GeometryExtra*>(extra);
|
||||
|
||||
Resource* resource = nullptr;
|
||||
if (url.path().toLower().endsWith(".fst")) {
|
||||
resource = new GeometryMappingResource(url);
|
||||
} else {
|
||||
return 0;
|
||||
resource = new GeometryDefinitionResource(url, geometryExtra->mapping, geometryExtra->textureBaseUrl);
|
||||
}
|
||||
|
||||
return QSharedPointer<Resource>(resource, &Resource::allReferencesCleared);
|
||||
}
|
||||
|
||||
std::shared_ptr<NetworkGeometry> ModelCache::getGeometry(const QUrl& url, const QVariantHash& mapping, const QUrl& textureBaseUrl) {
|
||||
GeometryExtra geometryExtra = { mapping, textureBaseUrl };
|
||||
GeometryResource::Pointer resource = getResource(url, QUrl(), true, &geometryExtra).staticCast<GeometryResource>();
|
||||
return std::make_shared<NetworkGeometry>(resource);
|
||||
}
|
||||
|
||||
const QVariantMap Geometry::getTextures() const {
|
||||
QVariantMap textures;
|
||||
for (const auto& material : _materials) {
|
||||
for (const auto& texture : material->_textures) {
|
||||
if (texture.texture) {
|
||||
textures[texture.name] = texture.texture->getURL();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return textures;
|
||||
}
|
||||
|
||||
// FIXME: The materials should only be copied when modified, but the Model currently caches the original
|
||||
Geometry::Geometry(const Geometry& geometry) {
|
||||
_geometry = geometry._geometry;
|
||||
_meshes = geometry._meshes;
|
||||
_shapes = geometry._shapes;
|
||||
|
||||
_materials.reserve(geometry._materials.size());
|
||||
for (const auto& material : geometry._materials) {
|
||||
_materials.push_back(std::make_shared<NetworkMaterial>(*material));
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkGeometry::textureLoaded(const QWeakPointer<NetworkTexture>& networkTexture) {
|
||||
numTextureLoaded++;
|
||||
void Geometry::setTextures(const QVariantMap& textureMap) {
|
||||
if (_meshes->size() > 0) {
|
||||
for (auto& material : _materials) {
|
||||
// Check if any material textures actually changed
|
||||
if (std::any_of(material->_textures.cbegin(), material->_textures.cend(),
|
||||
[&textureMap](const NetworkMaterial::Textures::value_type& it) { return it.texture && textureMap.contains(it.name); })) {
|
||||
|
||||
// FIXME: The Model currently caches the materials (waste of space!)
|
||||
// so they must be copied in the Geometry copy-ctor
|
||||
// if (material->isOriginal()) {
|
||||
// // Copy the material to avoid mutating the cached version
|
||||
// material = std::make_shared<NetworkMaterial>(*material);
|
||||
//}
|
||||
|
||||
material->setTextures(textureMap);
|
||||
_areTexturesLoaded = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
qCWarning(modelnetworking) << "Ignoring setTextures(); geometry not ready";
|
||||
}
|
||||
}
|
||||
|
||||
bool Geometry::areTexturesLoaded() const {
|
||||
if (!_areTexturesLoaded) {
|
||||
_hasTransparentTextures = false;
|
||||
|
||||
for (auto& material : _materials) {
|
||||
// Check if material textures are loaded
|
||||
if (std::any_of(material->_textures.cbegin(), material->_textures.cend(),
|
||||
[](const NetworkMaterial::Textures::value_type& it) { return it.texture && !it.texture->isLoaded(); })) {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// If material textures are loaded, check the material translucency
|
||||
const auto albedoTexture = material->_textures[NetworkMaterial::MapChannel::ALBEDO_MAP];
|
||||
if (albedoTexture.texture && albedoTexture.texture->getGPUTexture()) {
|
||||
material->resetOpacityMap();
|
||||
|
||||
_hasTransparentTextures |= material->getKey().isTranslucent();
|
||||
}
|
||||
}
|
||||
|
||||
_areTexturesLoaded = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::shared_ptr<const NetworkMaterial> Geometry::getShapeMaterial(int shapeID) const {
|
||||
if ((shapeID >= 0) && (shapeID < (int)_shapes->size())) {
|
||||
int materialID = _shapes->at(shapeID)->materialID;
|
||||
if ((materialID >= 0) && (materialID < (int)_materials.size())) {
|
||||
return _materials[materialID];
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
NetworkGeometry::NetworkGeometry(const GeometryResource::Pointer& networkGeometry) : _resource(networkGeometry) {
|
||||
connect(_resource.data(), &Resource::finished, this, &NetworkGeometry::resourceFinished);
|
||||
connect(_resource.data(), &Resource::onRefresh, this, &NetworkGeometry::resourceRefreshed);
|
||||
if (_resource->isLoaded()) {
|
||||
resourceFinished(!_resource->getURL().isEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkGeometry::resourceFinished(bool success) {
|
||||
// FIXME: Model is not set up to handle a refresh
|
||||
if (_instance) {
|
||||
return;
|
||||
}
|
||||
if (success) {
|
||||
_instance = std::make_shared<Geometry>(*_resource);
|
||||
}
|
||||
emit finished(success);
|
||||
}
|
||||
|
||||
void NetworkGeometry::resourceRefreshed() {
|
||||
// FIXME: Model is not set up to handle a refresh
|
||||
// _instance.reset();
|
||||
}
|
||||
|
||||
const QString NetworkMaterial::NO_TEXTURE = QString();
|
||||
|
||||
const QString& NetworkMaterial::getTextureName(MapChannel channel) {
|
||||
if (_textures[channel].texture) {
|
||||
return _textures[channel].name;
|
||||
}
|
||||
return NO_TEXTURE;
|
||||
}
|
||||
|
||||
QUrl NetworkMaterial::getTextureUrl(const QUrl& url, const FBXTexture& texture) {
|
||||
// If content is inline, cache it under the fbx file, not its url
|
||||
const auto baseUrl = texture.content.isEmpty() ? url : QUrl(url.url() + "/");
|
||||
return baseUrl.resolved(QUrl(texture.filename));
|
||||
}
|
||||
|
||||
model::TextureMapPointer NetworkMaterial::fetchTextureMap(const QUrl& baseUrl, const FBXTexture& fbxTexture,
|
||||
TextureType type, MapChannel channel) {
|
||||
const auto url = getTextureUrl(baseUrl, fbxTexture);
|
||||
const auto texture = DependencyManager::get<TextureCache>()->getTexture(url, type, fbxTexture.content);
|
||||
_textures[channel] = Texture { fbxTexture.name, texture };
|
||||
|
||||
auto map = std::make_shared<model::TextureMap>();
|
||||
map->setTextureSource(texture->_textureSource);
|
||||
return map;
|
||||
}
|
||||
|
||||
model::TextureMapPointer NetworkMaterial::fetchTextureMap(const QUrl& url, TextureType type, MapChannel channel) {
|
||||
const auto texture = DependencyManager::get<TextureCache>()->getTexture(url, type);
|
||||
_textures[channel].texture = texture;
|
||||
|
||||
auto map = std::make_shared<model::TextureMap>();
|
||||
map->setTextureSource(texture->_textureSource);
|
||||
return map;
|
||||
}
|
||||
|
||||
NetworkMaterial::NetworkMaterial(const FBXMaterial& material, const QUrl& textureBaseUrl) :
|
||||
model::Material(*material._material)
|
||||
{
|
||||
_textures = Textures(MapChannel::NUM_MAP_CHANNELS);
|
||||
if (!material.albedoTexture.filename.isEmpty()) {
|
||||
auto map = fetchTextureMap(textureBaseUrl, material.albedoTexture, DEFAULT_TEXTURE, MapChannel::ALBEDO_MAP);
|
||||
_albedoTransform = material.albedoTexture.transform;
|
||||
map->setTextureTransform(_albedoTransform);
|
||||
|
||||
if (!material.opacityTexture.filename.isEmpty()) {
|
||||
if (material.albedoTexture.filename == material.opacityTexture.filename) {
|
||||
// Best case scenario, just indicating that the albedo map contains transparency
|
||||
// TODO: Different albedo/opacity maps are not currently supported
|
||||
map->setUseAlphaChannel(true);
|
||||
}
|
||||
}
|
||||
|
||||
setTextureMap(MapChannel::ALBEDO_MAP, map);
|
||||
}
|
||||
|
||||
|
||||
if (!material.normalTexture.filename.isEmpty()) {
|
||||
auto type = (material.normalTexture.isBumpmap ? BUMP_TEXTURE : NORMAL_TEXTURE);
|
||||
auto map = fetchTextureMap(textureBaseUrl, material.normalTexture, type, MapChannel::NORMAL_MAP);
|
||||
setTextureMap(MapChannel::NORMAL_MAP, map);
|
||||
}
|
||||
|
||||
if (!material.roughnessTexture.filename.isEmpty()) {
|
||||
auto map = fetchTextureMap(textureBaseUrl, material.roughnessTexture, ROUGHNESS_TEXTURE, MapChannel::ROUGHNESS_MAP);
|
||||
setTextureMap(MapChannel::ROUGHNESS_MAP, map);
|
||||
} else if (!material.glossTexture.filename.isEmpty()) {
|
||||
auto map = fetchTextureMap(textureBaseUrl, material.glossTexture, GLOSS_TEXTURE, MapChannel::ROUGHNESS_MAP);
|
||||
setTextureMap(MapChannel::ROUGHNESS_MAP, map);
|
||||
}
|
||||
|
||||
if (!material.metallicTexture.filename.isEmpty()) {
|
||||
auto map = fetchTextureMap(textureBaseUrl, material.metallicTexture, METALLIC_TEXTURE, MapChannel::METALLIC_MAP);
|
||||
setTextureMap(MapChannel::METALLIC_MAP, map);
|
||||
} else if (!material.specularTexture.filename.isEmpty()) {
|
||||
auto map = fetchTextureMap(textureBaseUrl, material.specularTexture, SPECULAR_TEXTURE, MapChannel::METALLIC_MAP);
|
||||
setTextureMap(MapChannel::METALLIC_MAP, map);
|
||||
}
|
||||
|
||||
if (!material.occlusionTexture.filename.isEmpty()) {
|
||||
auto map = fetchTextureMap(textureBaseUrl, material.occlusionTexture, OCCLUSION_TEXTURE, MapChannel::OCCLUSION_MAP);
|
||||
setTextureMap(MapChannel::OCCLUSION_MAP, map);
|
||||
}
|
||||
|
||||
if (!material.emissiveTexture.filename.isEmpty()) {
|
||||
auto map = fetchTextureMap(textureBaseUrl, material.emissiveTexture, EMISSIVE_TEXTURE, MapChannel::EMISSIVE_MAP);
|
||||
setTextureMap(MapChannel::EMISSIVE_MAP, map);
|
||||
}
|
||||
|
||||
if (!material.lightmapTexture.filename.isEmpty()) {
|
||||
auto map = fetchTextureMap(textureBaseUrl, material.lightmapTexture, LIGHTMAP_TEXTURE, MapChannel::LIGHTMAP_MAP);
|
||||
_lightmapTransform = material.lightmapTexture.transform;
|
||||
_lightmapParams = material.lightmapParams;
|
||||
map->setTextureTransform(_lightmapTransform);
|
||||
map->setLightmapOffsetScale(_lightmapParams.x, _lightmapParams.y);
|
||||
setTextureMap(MapChannel::LIGHTMAP_MAP, map);
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkMaterial::setTextures(const QVariantMap& textureMap) {
|
||||
_isOriginal = false;
|
||||
|
||||
const auto& albedoName = getTextureName(MapChannel::ALBEDO_MAP);
|
||||
const auto& normalName = getTextureName(MapChannel::NORMAL_MAP);
|
||||
const auto& roughnessName = getTextureName(MapChannel::ROUGHNESS_MAP);
|
||||
const auto& metallicName = getTextureName(MapChannel::METALLIC_MAP);
|
||||
const auto& occlusionName = getTextureName(MapChannel::OCCLUSION_MAP);
|
||||
const auto& emissiveName = getTextureName(MapChannel::EMISSIVE_MAP);
|
||||
const auto& lightmapName = getTextureName(MapChannel::LIGHTMAP_MAP);
|
||||
|
||||
if (!albedoName.isEmpty()) {
|
||||
auto url = textureMap.contains(albedoName) ? textureMap[albedoName].toUrl() : QUrl();
|
||||
auto map = fetchTextureMap(url, DEFAULT_TEXTURE, MapChannel::ALBEDO_MAP);
|
||||
map->setTextureTransform(_albedoTransform);
|
||||
// when reassigning the albedo texture we also check for the alpha channel used as opacity
|
||||
map->setUseAlphaChannel(true);
|
||||
setTextureMap(MapChannel::ALBEDO_MAP, map);
|
||||
}
|
||||
|
||||
if (!normalName.isEmpty()) {
|
||||
auto url = textureMap.contains(normalName) ? textureMap[normalName].toUrl() : QUrl();
|
||||
auto map = fetchTextureMap(url, DEFAULT_TEXTURE, MapChannel::NORMAL_MAP);
|
||||
setTextureMap(MapChannel::NORMAL_MAP, map);
|
||||
}
|
||||
|
||||
if (!roughnessName.isEmpty()) {
|
||||
auto url = textureMap.contains(roughnessName) ? textureMap[roughnessName].toUrl() : QUrl();
|
||||
// FIXME: If passing a gloss map instead of a roughmap how do we know?
|
||||
auto map = fetchTextureMap(url, ROUGHNESS_TEXTURE, MapChannel::ROUGHNESS_MAP);
|
||||
setTextureMap(MapChannel::ROUGHNESS_MAP, map);
|
||||
}
|
||||
|
||||
if (!metallicName.isEmpty()) {
|
||||
auto url = textureMap.contains(metallicName) ? textureMap[metallicName].toUrl() : QUrl();
|
||||
// FIXME: If passing a specular map instead of a metallic how do we know?
|
||||
auto map = fetchTextureMap(url, METALLIC_TEXTURE, MapChannel::METALLIC_MAP);
|
||||
setTextureMap(MapChannel::METALLIC_MAP, map);
|
||||
}
|
||||
|
||||
if (!occlusionName.isEmpty()) {
|
||||
auto url = textureMap.contains(occlusionName) ? textureMap[occlusionName].toUrl() : QUrl();
|
||||
auto map = fetchTextureMap(url, OCCLUSION_TEXTURE, MapChannel::OCCLUSION_MAP);
|
||||
setTextureMap(MapChannel::OCCLUSION_MAP, map);
|
||||
}
|
||||
|
||||
if (!emissiveName.isEmpty()) {
|
||||
auto url = textureMap.contains(emissiveName) ? textureMap[emissiveName].toUrl() : QUrl();
|
||||
auto map = fetchTextureMap(url, EMISSIVE_TEXTURE, MapChannel::EMISSIVE_MAP);
|
||||
setTextureMap(MapChannel::EMISSIVE_MAP, map);
|
||||
}
|
||||
|
||||
if (!lightmapName.isEmpty()) {
|
||||
auto url = textureMap.contains(lightmapName) ? textureMap[lightmapName].toUrl() : QUrl();
|
||||
auto map = fetchTextureMap(url, LIGHTMAP_TEXTURE, MapChannel::LIGHTMAP_MAP);
|
||||
map->setTextureTransform(_lightmapTransform);
|
||||
map->setLightmapOffsetScale(_lightmapParams.x, _lightmapParams.y);
|
||||
setTextureMap(MapChannel::LIGHTMAP_MAP, map);
|
||||
}
|
||||
}
|
||||
|
||||
#include "ModelCache.moc"
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
//
|
||||
// ModelCache.h
|
||||
// libraries/model-networking/src/model-networking
|
||||
// libraries/model-networking
|
||||
//
|
||||
// Created by Sam Gateau on 9/21/15.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
// Created by Zach Pomerantz on 3/15/16.
|
||||
// Copyright 2016 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
|
||||
|
@ -12,200 +12,176 @@
|
|||
#ifndef hifi_ModelCache_h
|
||||
#define hifi_ModelCache_h
|
||||
|
||||
#include <QMap>
|
||||
#include <QRunnable>
|
||||
|
||||
#include <DependencyManager.h>
|
||||
#include <ResourceCache.h>
|
||||
|
||||
#include "FBXReader.h"
|
||||
#include "OBJReader.h"
|
||||
|
||||
#include <gpu/Batch.h>
|
||||
#include <gpu/Stream.h>
|
||||
|
||||
|
||||
#include <model/Material.h>
|
||||
#include <model/Asset.h>
|
||||
|
||||
class NetworkGeometry;
|
||||
class NetworkMesh;
|
||||
#include "FBXReader.h"
|
||||
#include "TextureCache.h"
|
||||
|
||||
// Alias instead of derive to avoid copying
|
||||
using NetworkMesh = model::Mesh;
|
||||
|
||||
class NetworkTexture;
|
||||
class NetworkMaterial;
|
||||
class NetworkShape;
|
||||
class NetworkGeometry;
|
||||
|
||||
/// Stores cached geometry.
|
||||
class GeometryMappingResource;
|
||||
|
||||
/// Stores cached model geometries.
|
||||
class ModelCache : public ResourceCache, public Dependency {
|
||||
Q_OBJECT
|
||||
SINGLETON_DEPENDENCY
|
||||
|
||||
public:
|
||||
virtual QSharedPointer<Resource> createResource(const QUrl& url, const QSharedPointer<Resource>& fallback,
|
||||
bool delayLoad, const void* extra);
|
||||
/// Loads a model geometry from the specified URL.
|
||||
std::shared_ptr<NetworkGeometry> getGeometry(const QUrl& url,
|
||||
const QVariantHash& mapping = QVariantHash(), const QUrl& textureBaseUrl = QUrl());
|
||||
|
||||
/// Loads geometry from the specified URL.
|
||||
/// \param fallback a fallback URL to load if the desired one is unavailable
|
||||
/// \param delayLoad if true, don't load the geometry immediately; wait until load is first requested
|
||||
QSharedPointer<NetworkGeometry> getGeometry(const QUrl& url, const QUrl& fallback = QUrl(), bool delayLoad = false);
|
||||
protected:
|
||||
friend class GeometryMappingResource;
|
||||
|
||||
virtual QSharedPointer<Resource> createResource(const QUrl& url,
|
||||
const QSharedPointer<Resource>& fallback, bool delayLoad, const void* extra);
|
||||
|
||||
private:
|
||||
ModelCache();
|
||||
virtual ~ModelCache();
|
||||
virtual ~ModelCache() = default;
|
||||
};
|
||||
|
||||
QHash<QUrl, QWeakPointer<NetworkGeometry> > _networkGeometry;
|
||||
class Geometry {
|
||||
public:
|
||||
using Pointer = std::shared_ptr<Geometry>;
|
||||
|
||||
Geometry() = default;
|
||||
Geometry(const Geometry& geometry);
|
||||
|
||||
// Immutable over lifetime
|
||||
using NetworkMeshes = std::vector<std::shared_ptr<const NetworkMesh>>;
|
||||
using NetworkShapes = std::vector<std::shared_ptr<const NetworkShape>>;
|
||||
|
||||
// Mutable, but must retain structure of vector
|
||||
using NetworkMaterials = std::vector<std::shared_ptr<NetworkMaterial>>;
|
||||
|
||||
const FBXGeometry& getGeometry() const { return *_geometry; }
|
||||
const NetworkMeshes& getMeshes() const { return *_meshes; }
|
||||
const std::shared_ptr<const NetworkMaterial> getShapeMaterial(int shapeID) const;
|
||||
|
||||
const QVariantMap getTextures() const;
|
||||
void setTextures(const QVariantMap& textureMap);
|
||||
|
||||
virtual bool areTexturesLoaded() const;
|
||||
// Returns true if any albedo texture has a non-masking alpha channel.
|
||||
// This can only be known after areTexturesLoaded().
|
||||
bool hasTransparentTextures() const { return _hasTransparentTextures; }
|
||||
|
||||
protected:
|
||||
friend class GeometryMappingResource;
|
||||
|
||||
// Shared across all geometries, constant throughout lifetime
|
||||
std::shared_ptr<const FBXGeometry> _geometry;
|
||||
std::shared_ptr<const NetworkMeshes> _meshes;
|
||||
std::shared_ptr<const NetworkShapes> _shapes;
|
||||
|
||||
// Copied to each geometry, mutable throughout lifetime via setTextures
|
||||
NetworkMaterials _materials;
|
||||
|
||||
private:
|
||||
mutable bool _areTexturesLoaded { false };
|
||||
mutable bool _hasTransparentTextures { false };
|
||||
};
|
||||
|
||||
/// A geometry loaded from the network.
|
||||
class GeometryResource : public Resource, public Geometry {
|
||||
public:
|
||||
using Pointer = QSharedPointer<GeometryResource>;
|
||||
|
||||
GeometryResource(const QUrl& url) : Resource(url) {}
|
||||
|
||||
virtual bool areTexturesLoaded() const { return isLoaded() && Geometry::areTexturesLoaded(); }
|
||||
|
||||
protected:
|
||||
friend class GeometryMappingResource;
|
||||
|
||||
virtual bool isCacheable() const override { return _loaded && _isCacheable; }
|
||||
|
||||
bool _isCacheable { true };
|
||||
};
|
||||
|
||||
class NetworkGeometry : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
// mapping is only used if url is a .fbx or .obj file, it is essentially the content of an fst file.
|
||||
// if delayLoad is true, the url will not be immediately downloaded.
|
||||
// use the attemptRequest method to initiate the download.
|
||||
NetworkGeometry(const QUrl& url, bool delayLoad, const QVariantHash& mapping, const QUrl& textureBaseUrl = QUrl());
|
||||
~NetworkGeometry();
|
||||
using Pointer = std::shared_ptr<NetworkGeometry>;
|
||||
|
||||
const QUrl& getURL() const { return _url; }
|
||||
NetworkGeometry() = delete;
|
||||
NetworkGeometry(const GeometryResource::Pointer& networkGeometry);
|
||||
|
||||
void attemptRequest();
|
||||
const QUrl& getURL() { return _resource->getURL(); }
|
||||
|
||||
// true when the geometry is loaded (but maybe not it's associated textures)
|
||||
bool isLoaded() const;
|
||||
|
||||
// true when the requested geometry and its textures are loaded.
|
||||
bool isLoadedWithTextures() const;
|
||||
|
||||
// true if the albedo texture has a non-masking alpha channel.
|
||||
// This can only be known after isLoadedWithTextures().
|
||||
bool hasTransparentTextures() const { return _hasTransparentTextures; }
|
||||
|
||||
// WARNING: only valid when isLoaded returns true.
|
||||
const FBXGeometry& getFBXGeometry() const { return *_geometry; }
|
||||
const std::vector<std::unique_ptr<NetworkMesh>>& getMeshes() const { return _meshes; }
|
||||
// const model::AssetPointer getAsset() const { return _asset; }
|
||||
|
||||
// model::MeshPointer getShapeMesh(int shapeID);
|
||||
// int getShapePart(int shapeID);
|
||||
|
||||
// This would be the final verison
|
||||
// model::MaterialPointer getShapeMaterial(int shapeID);
|
||||
const NetworkMaterial* getShapeMaterial(int shapeID);
|
||||
|
||||
|
||||
void setTextureWithNameToURL(const QString& name, const QUrl& url);
|
||||
QStringList getTextureNames() const;
|
||||
|
||||
enum Error {
|
||||
MissingFilenameInMapping = 0,
|
||||
MappingRequestError,
|
||||
ModelRequestError,
|
||||
ModelParseError
|
||||
};
|
||||
/// Returns the geometry, if it is loaded (must be checked!)
|
||||
const Geometry::Pointer& getGeometry() { return _instance; }
|
||||
|
||||
signals:
|
||||
// Fired when everything has downloaded and parsed successfully.
|
||||
void onSuccess(NetworkGeometry& networkGeometry, FBXGeometry& fbxGeometry);
|
||||
/// Emitted when the NetworkGeometry loads (or fails to)
|
||||
void finished(bool success);
|
||||
|
||||
// Fired when something went wrong.
|
||||
void onFailure(NetworkGeometry& networkGeometry, Error error);
|
||||
private slots:
|
||||
void resourceFinished(bool success);
|
||||
void resourceRefreshed();
|
||||
|
||||
public slots:
|
||||
void textureLoaded(const QWeakPointer<NetworkTexture>& networkTexture);
|
||||
private:
|
||||
GeometryResource::Pointer _resource;
|
||||
Geometry::Pointer _instance { nullptr };
|
||||
};
|
||||
|
||||
protected slots:
|
||||
void mappingRequestDone(const QByteArray data);
|
||||
void mappingRequestError(QNetworkReply::NetworkError error);
|
||||
|
||||
void modelRequestDone(const QByteArray data);
|
||||
void modelRequestError(QNetworkReply::NetworkError error);
|
||||
|
||||
void modelParseSuccess(FBXGeometry* geometry);
|
||||
void modelParseError(int error, QString str);
|
||||
class NetworkMaterial : public model::Material {
|
||||
public:
|
||||
using MapChannel = model::Material::MapChannel;
|
||||
|
||||
NetworkMaterial(const FBXMaterial& material, const QUrl& textureBaseUrl);
|
||||
|
||||
protected:
|
||||
void attemptRequestInternal();
|
||||
void requestMapping(const QUrl& url);
|
||||
void requestModel(const QUrl& url);
|
||||
friend class Geometry;
|
||||
|
||||
enum State { DelayState,
|
||||
RequestMappingState,
|
||||
RequestModelState,
|
||||
ParsingModelState,
|
||||
SuccessState,
|
||||
ErrorState };
|
||||
State _state;
|
||||
class Texture {
|
||||
public:
|
||||
QString name;
|
||||
QSharedPointer<NetworkTexture> texture;
|
||||
};
|
||||
using Textures = std::vector<Texture>;
|
||||
|
||||
QUrl _url;
|
||||
QUrl _mappingUrl;
|
||||
QUrl _modelUrl;
|
||||
QVariantHash _mapping;
|
||||
QUrl _textureBaseUrl;
|
||||
int numTextureLoaded = 0;
|
||||
Textures _textures;
|
||||
|
||||
Resource* _resource = nullptr;
|
||||
std::unique_ptr<FBXGeometry> _geometry; // This should go away evenutally once we can put everything we need in the model::AssetPointer
|
||||
std::vector<std::unique_ptr<NetworkMesh>> _meshes;
|
||||
std::vector<std::unique_ptr<NetworkMaterial>> _materials;
|
||||
std::vector<std::unique_ptr<NetworkShape>> _shapes;
|
||||
static const QString NO_TEXTURE;
|
||||
const QString& getTextureName(MapChannel channel);
|
||||
|
||||
void setTextures(const QVariantMap& textureMap);
|
||||
|
||||
// The model asset created from this NetworkGeometry
|
||||
// model::AssetPointer _asset;
|
||||
const bool& isOriginal() const { return _isOriginal; }
|
||||
|
||||
// cache for isLoadedWithTextures()
|
||||
mutable bool _isLoadedWithTextures = false;
|
||||
mutable bool _hasTransparentTextures = false;
|
||||
};
|
||||
|
||||
/// Reads geometry in a worker thread.
|
||||
class GeometryReader : public QObject, public QRunnable {
|
||||
Q_OBJECT
|
||||
public:
|
||||
GeometryReader(const QUrl& url, const QByteArray& data, const QVariantHash& mapping);
|
||||
virtual void run();
|
||||
signals:
|
||||
void onSuccess(FBXGeometry* geometry);
|
||||
void onError(int error, QString str);
|
||||
private:
|
||||
QUrl _url;
|
||||
QByteArray _data;
|
||||
QVariantHash _mapping;
|
||||
};
|
||||
// Helpers for the ctors
|
||||
QUrl getTextureUrl(const QUrl& baseUrl, const FBXTexture& fbxTexture);
|
||||
model::TextureMapPointer fetchTextureMap(const QUrl& baseUrl, const FBXTexture& fbxTexture,
|
||||
TextureType type, MapChannel channel);
|
||||
model::TextureMapPointer fetchTextureMap(const QUrl& url, TextureType type, MapChannel channel);
|
||||
|
||||
Transform _albedoTransform;
|
||||
Transform _lightmapTransform;
|
||||
vec2 _lightmapParams;
|
||||
|
||||
bool _isOriginal { true };
|
||||
};
|
||||
|
||||
class NetworkShape {
|
||||
public:
|
||||
int _meshID{ -1 };
|
||||
int _partID{ -1 };
|
||||
int _materialID{ -1 };
|
||||
NetworkShape(int mesh, int part, int material) : meshID { mesh }, partID { part }, materialID { material } {}
|
||||
int meshID { -1 };
|
||||
int partID { -1 };
|
||||
int materialID { -1 };
|
||||
};
|
||||
|
||||
class NetworkMaterial {
|
||||
public:
|
||||
|
||||
model::MaterialPointer _material;
|
||||
QString emissiveTextureName;
|
||||
QSharedPointer<NetworkTexture> emissiveTexture;
|
||||
QString albedoTextureName;
|
||||
QSharedPointer<NetworkTexture> albedoTexture;
|
||||
QString normalTextureName;
|
||||
QSharedPointer<NetworkTexture> normalTexture;
|
||||
QString roughnessTextureName;
|
||||
QSharedPointer<NetworkTexture> roughnessTexture;
|
||||
QString metallicTextureName;
|
||||
QSharedPointer<NetworkTexture> metallicTexture;
|
||||
QString occlusionTextureName;
|
||||
QSharedPointer<NetworkTexture> occlusionTexture;
|
||||
QString lightmapTextureName;
|
||||
QSharedPointer<NetworkTexture> lightmapTexture;
|
||||
};
|
||||
|
||||
|
||||
/// The state associated with a single mesh.
|
||||
class NetworkMesh {
|
||||
public:
|
||||
model::MeshPointer _mesh;
|
||||
};
|
||||
|
||||
#endif // hifi_GeometryCache_h
|
||||
#endif // hifi_ModelCache_h
|
||||
|
|
|
@ -18,26 +18,34 @@ using namespace gpu;
|
|||
Material::Material() :
|
||||
_key(0),
|
||||
_schemaBuffer(),
|
||||
_textureMaps() {
|
||||
|
||||
// only if created from nothing shall we create the Buffer to store the properties
|
||||
Schema schema;
|
||||
_schemaBuffer = gpu::BufferView(std::make_shared<gpu::Buffer>(sizeof(Schema), (const gpu::Byte*) &schema));
|
||||
_textureMaps()
|
||||
{
|
||||
// created from nothing: create the Buffer to store the properties
|
||||
Schema schema;
|
||||
_schemaBuffer = gpu::BufferView(std::make_shared<gpu::Buffer>(sizeof(Schema), (const gpu::Byte*) &schema));
|
||||
|
||||
|
||||
}
|
||||
|
||||
Material::Material(const Material& material) :
|
||||
_key(material._key),
|
||||
_schemaBuffer(material._schemaBuffer),
|
||||
_textureMaps(material._textureMaps) {
|
||||
_textureMaps(material._textureMaps)
|
||||
{
|
||||
// copied: create the Buffer to store the properties, avoid holding a ref to the old Buffer
|
||||
Schema schema;
|
||||
_schemaBuffer = gpu::BufferView(std::make_shared<gpu::Buffer>(sizeof(Schema), (const gpu::Byte*) &schema));
|
||||
_schemaBuffer.edit<Schema>() = material._schemaBuffer.get<Schema>();
|
||||
}
|
||||
|
||||
Material& Material::operator= (const Material& material) {
|
||||
_key = (material._key);
|
||||
_schemaBuffer = (material._schemaBuffer);
|
||||
_textureMaps = (material._textureMaps);
|
||||
|
||||
// copied: create the Buffer to store the properties, avoid holding a ref to the old Buffer
|
||||
Schema schema;
|
||||
_schemaBuffer = gpu::BufferView(std::make_shared<gpu::Buffer>(sizeof(Schema), (const gpu::Byte*) &schema));
|
||||
_schemaBuffer.edit<Schema>() = material._schemaBuffer.get<Schema>();
|
||||
|
||||
return (*this);
|
||||
}
|
||||
|
||||
|
@ -83,41 +91,48 @@ void Material::setMetallic(float metallic) {
|
|||
void Material::setTextureMap(MapChannel channel, const TextureMapPointer& textureMap) {
|
||||
if (textureMap) {
|
||||
_key.setMapChannel(channel, (true));
|
||||
|
||||
if (channel == MaterialKey::ALBEDO_MAP) {
|
||||
// clear the previous flags whatever they were:
|
||||
_key.setOpacityMaskMap(false);
|
||||
_key.setTranslucentMap(false);
|
||||
|
||||
if (textureMap->useAlphaChannel() && textureMap->isDefined() && textureMap->getTextureView().isValid()) {
|
||||
auto usage = textureMap->getTextureView()._texture->getUsage();
|
||||
if (usage.isAlpha()) {
|
||||
// Texture has alpha, is not just a mask or a true transparent channel
|
||||
if (usage.isAlphaMask()) {
|
||||
_key.setOpacityMaskMap(true);
|
||||
_key.setTranslucentMap(false);
|
||||
} else {
|
||||
_key.setOpacityMaskMap(false);
|
||||
_key.setTranslucentMap(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_textureMaps[channel] = textureMap;
|
||||
} else {
|
||||
_key.setMapChannel(channel, (false));
|
||||
|
||||
if (channel == MaterialKey::ALBEDO_MAP) {
|
||||
_key.setOpacityMaskMap(false);
|
||||
_key.setTranslucentMap(false);
|
||||
}
|
||||
|
||||
_textureMaps.erase(channel);
|
||||
}
|
||||
|
||||
_schemaBuffer.edit<Schema>()._key = (uint32)_key._flags.to_ulong();
|
||||
|
||||
if (channel == MaterialKey::ALBEDO_MAP) {
|
||||
resetOpacityMap();
|
||||
}
|
||||
|
||||
_schemaBuffer.edit<Schema>()._key = (uint32)_key._flags.to_ulong();
|
||||
|
||||
}
|
||||
|
||||
void Material::resetOpacityMap() const {
|
||||
// Clear the previous flags
|
||||
_key.setOpacityMaskMap(false);
|
||||
_key.setTranslucentMap(false);
|
||||
|
||||
const auto& textureMap = getTextureMap(MaterialKey::ALBEDO_MAP);
|
||||
if (textureMap &&
|
||||
textureMap->useAlphaChannel() &&
|
||||
textureMap->isDefined() &&
|
||||
textureMap->getTextureView().isValid()) {
|
||||
|
||||
auto usage = textureMap->getTextureView()._texture->getUsage();
|
||||
if (usage.isAlpha()) {
|
||||
if (usage.isAlphaMask()) {
|
||||
// Texture has alpha, but it is just a mask
|
||||
_key.setOpacityMaskMap(true);
|
||||
_key.setTranslucentMap(false);
|
||||
} else {
|
||||
// Texture has alpha, it is a true translucency channel
|
||||
_key.setOpacityMaskMap(false);
|
||||
_key.setTranslucentMap(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_schemaBuffer.edit<Schema>()._key = (uint32)_key._flags.to_ulong();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -291,15 +291,17 @@ public:
|
|||
const TextureMaps& getTextureMaps() const { return _textureMaps; }
|
||||
const TextureMapPointer getTextureMap(MapChannel channel) const;
|
||||
|
||||
// Albedo maps cannot have opacity detected until they are loaded
|
||||
// This method allows const changing of the key/schemaBuffer without touching the map
|
||||
void resetOpacityMap() const;
|
||||
|
||||
// conversion from legacy material properties to PBR equivalent
|
||||
static float shininessToRoughness(float shininess) { return 1.0f - shininess / 100.0f; }
|
||||
|
||||
protected:
|
||||
|
||||
MaterialKey _key;
|
||||
UniformBufferView _schemaBuffer;
|
||||
private:
|
||||
mutable MaterialKey _key;
|
||||
mutable UniformBufferView _schemaBuffer;
|
||||
TextureMaps _textureMaps;
|
||||
|
||||
};
|
||||
typedef std::shared_ptr< Material > MaterialPointer;
|
||||
|
||||
|
|
|
@ -45,14 +45,14 @@ template <> void payloadRender(const MeshPartPayload::Pointer& payload, RenderAr
|
|||
}
|
||||
}
|
||||
|
||||
MeshPartPayload::MeshPartPayload(model::MeshPointer mesh, int partIndex, model::MaterialPointer material, const Transform& transform, const Transform& offsetTransform) {
|
||||
MeshPartPayload::MeshPartPayload(const std::shared_ptr<const model::Mesh>& mesh, int partIndex, model::MaterialPointer material, const Transform& transform, const Transform& offsetTransform) {
|
||||
|
||||
updateMeshPart(mesh, partIndex);
|
||||
updateMaterial(material);
|
||||
updateTransform(transform, offsetTransform);
|
||||
}
|
||||
|
||||
void MeshPartPayload::updateMeshPart(model::MeshPointer drawMesh, int partIndex) {
|
||||
void MeshPartPayload::updateMeshPart(const std::shared_ptr<const model::Mesh>& drawMesh, int partIndex) {
|
||||
_drawMesh = drawMesh;
|
||||
if (_drawMesh) {
|
||||
auto vertexFormat = _drawMesh->getVertexFormat();
|
||||
|
@ -320,7 +320,9 @@ ModelMeshPartPayload::ModelMeshPartPayload(Model* model, int _meshIndex, int par
|
|||
_model(model),
|
||||
_meshIndex(_meshIndex),
|
||||
_shapeID(shapeIndex) {
|
||||
auto& modelMesh = _model->_geometry->getMeshes().at(_meshIndex)->_mesh;
|
||||
|
||||
assert(_model && _model->isLoaded());
|
||||
auto& modelMesh = _model->getGeometry()->getGeometry()->getMeshes().at(_meshIndex);
|
||||
updateMeshPart(modelMesh, partIndex);
|
||||
|
||||
updateTransform(transform, offsetTransform);
|
||||
|
@ -328,20 +330,22 @@ ModelMeshPartPayload::ModelMeshPartPayload(Model* model, int _meshIndex, int par
|
|||
}
|
||||
|
||||
void ModelMeshPartPayload::initCache() {
|
||||
assert(_model->isLoaded());
|
||||
|
||||
if (_drawMesh) {
|
||||
auto vertexFormat = _drawMesh->getVertexFormat();
|
||||
_hasColorAttrib = vertexFormat->hasAttribute(gpu::Stream::COLOR);
|
||||
_isSkinned = vertexFormat->hasAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT) && vertexFormat->hasAttribute(gpu::Stream::SKIN_CLUSTER_INDEX);
|
||||
|
||||
|
||||
const FBXGeometry& geometry = _model->_geometry->getFBXGeometry();
|
||||
const FBXGeometry& geometry = _model->getFBXGeometry();
|
||||
const FBXMesh& mesh = geometry.meshes.at(_meshIndex);
|
||||
|
||||
_isBlendShaped = !mesh.blendshapes.isEmpty();
|
||||
}
|
||||
|
||||
auto networkMaterial = _model->_geometry->getShapeMaterial(_shapeID);
|
||||
auto networkMaterial = _model->getGeometry()->getGeometry()->getShapeMaterial(_shapeID);
|
||||
if (networkMaterial) {
|
||||
_drawMaterial = networkMaterial->_material;
|
||||
_drawMaterial = networkMaterial;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -380,8 +384,9 @@ Item::Bound ModelMeshPartPayload::getBound() const {
|
|||
}
|
||||
|
||||
ShapeKey ModelMeshPartPayload::getShapeKey() const {
|
||||
const FBXGeometry& geometry = _model->_geometry->getFBXGeometry();
|
||||
const std::vector<std::unique_ptr<NetworkMesh>>& networkMeshes = _model->_geometry->getMeshes();
|
||||
assert(_model->isLoaded());
|
||||
const FBXGeometry& geometry = _model->getFBXGeometry();
|
||||
const auto& networkMeshes = _model->getGeometry()->getGeometry()->getMeshes();
|
||||
|
||||
// guard against partially loaded meshes
|
||||
if (_meshIndex >= (int)networkMeshes.size() || _meshIndex >= (int)geometry.meshes.size() || _meshIndex >= (int)_model->_meshStates.size()) {
|
||||
|
|
|
@ -24,12 +24,12 @@ class Model;
|
|||
class MeshPartPayload {
|
||||
public:
|
||||
MeshPartPayload() {}
|
||||
MeshPartPayload(model::MeshPointer mesh, int partIndex, model::MaterialPointer material, const Transform& transform, const Transform& offsetTransform);
|
||||
MeshPartPayload(const std::shared_ptr<const model::Mesh>& mesh, int partIndex, model::MaterialPointer material, const Transform& transform, const Transform& offsetTransform);
|
||||
|
||||
typedef render::Payload<MeshPartPayload> Payload;
|
||||
typedef Payload::DataPointer Pointer;
|
||||
|
||||
virtual void updateMeshPart(model::MeshPointer drawMesh, int partIndex);
|
||||
virtual void updateMeshPart(const std::shared_ptr<const model::Mesh>& drawMesh, int partIndex);
|
||||
|
||||
virtual void notifyLocationChanged() {}
|
||||
virtual void updateTransform(const Transform& transform, const Transform& offsetTransform);
|
||||
|
@ -49,11 +49,11 @@ public:
|
|||
virtual void bindTransform(gpu::Batch& batch, const render::ShapePipeline::LocationsPointer locations, bool canCauterize = true) const;
|
||||
|
||||
// Payload resource cached values
|
||||
model::MeshPointer _drawMesh;
|
||||
std::shared_ptr<const model::Mesh> _drawMesh;
|
||||
int _partIndex = 0;
|
||||
model::Mesh::Part _drawPart;
|
||||
|
||||
model::MaterialPointer _drawMaterial;
|
||||
std::shared_ptr<const model::Material> _drawMaterial;
|
||||
|
||||
model::Box _localBound;
|
||||
Transform _drawTransform;
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
using namespace std;
|
||||
|
||||
static int nakedModelPointerTypeId = qRegisterMetaType<ModelPointer>();
|
||||
static int weakNetworkGeometryPointerTypeId = qRegisterMetaType<QWeakPointer<NetworkGeometry> >();
|
||||
static int weakNetworkGeometryPointerTypeId = qRegisterMetaType<std::weak_ptr<NetworkGeometry> >();
|
||||
static int vec3VectorTypeId = qRegisterMetaType<QVector<glm::vec3> >();
|
||||
float Model::FAKE_DIMENSION_PLACEHOLDER = -1.0f;
|
||||
#define HTTP_INVALID_COM "http://invalid.com"
|
||||
|
@ -74,12 +74,14 @@ Model::~Model() {
|
|||
|
||||
AbstractViewStateInterface* Model::_viewState = NULL;
|
||||
|
||||
bool Model::needsFixupInScene() {
|
||||
bool Model::needsFixupInScene() const {
|
||||
if (readyToAddToScene()) {
|
||||
// Once textures are loaded, fixup if they are now transparent
|
||||
if (!_needsReload && _needsUpdateTransparentTextures && _geometry->isLoadedWithTextures()) {
|
||||
if (_needsUpdateTransparentTextures && _geometry->getGeometry()->areTexturesLoaded()) {
|
||||
_needsUpdateTransparentTextures = false;
|
||||
if (_hasTransparentTextures != _geometry->hasTransparentTextures()) {
|
||||
bool hasTransparentTextures = _geometry->getGeometry()->hasTransparentTextures();
|
||||
if (_hasTransparentTextures != hasTransparentTextures) {
|
||||
_hasTransparentTextures = hasTransparentTextures;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -150,19 +152,18 @@ void Model::enqueueLocationChange() {
|
|||
}
|
||||
|
||||
void Model::initJointTransforms() {
|
||||
if (!_geometry || !_geometry->isLoaded()) {
|
||||
return;
|
||||
if (isLoaded()) {
|
||||
glm::mat4 modelOffset = glm::scale(_scale) * glm::translate(_offset);
|
||||
_rig->setModelOffset(modelOffset);
|
||||
}
|
||||
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();
|
||||
if (isLoaded()) {
|
||||
const FBXGeometry& geometry = getFBXGeometry();
|
||||
_rig->reset(geometry);
|
||||
}
|
||||
}
|
||||
|
@ -171,17 +172,16 @@ bool Model::updateGeometry() {
|
|||
PROFILE_RANGE(__FUNCTION__);
|
||||
bool needFullUpdate = false;
|
||||
|
||||
if (!_geometry || !_geometry->isLoaded()) {
|
||||
// geometry is not ready
|
||||
if (!isLoaded()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_needsReload = false;
|
||||
|
||||
if (_rig->jointStatesEmpty() && _geometry->getFBXGeometry().joints.size() > 0) {
|
||||
if (_rig->jointStatesEmpty() && getFBXGeometry().joints.size() > 0) {
|
||||
initJointStates();
|
||||
|
||||
const FBXGeometry& fbxGeometry = _geometry->getFBXGeometry();
|
||||
const FBXGeometry& fbxGeometry = getFBXGeometry();
|
||||
foreach (const FBXMesh& mesh, fbxGeometry.meshes) {
|
||||
MeshState state;
|
||||
state.clusterMatrices.resize(mesh.clusters.size());
|
||||
|
@ -205,7 +205,7 @@ bool Model::updateGeometry() {
|
|||
|
||||
// virtual
|
||||
void Model::initJointStates() {
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
const FBXGeometry& geometry = getFBXGeometry();
|
||||
glm::mat4 modelOffset = glm::scale(_scale) * glm::translate(_offset);
|
||||
|
||||
_rig->initJointStates(geometry, modelOffset);
|
||||
|
@ -248,7 +248,7 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g
|
|||
glm::vec3 subMeshSurfaceNormal;
|
||||
int subMeshIndex = 0;
|
||||
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
const FBXGeometry& geometry = getFBXGeometry();
|
||||
|
||||
// If we hit the models box, then consider the submeshes...
|
||||
_mutex.lock();
|
||||
|
@ -367,7 +367,7 @@ void Model::recalculateMeshBoxes(bool pickAgainstTriangles) {
|
|||
bool calculatedMeshTrianglesNeeded = pickAgainstTriangles && !_calculatedMeshTrianglesValid;
|
||||
|
||||
if (!_calculatedMeshBoxesValid || calculatedMeshTrianglesNeeded || (!_calculatedMeshPartBoxesValid && pickAgainstTriangles) ) {
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
const FBXGeometry& geometry = getFBXGeometry();
|
||||
int numberOfMeshes = geometry.meshes.size();
|
||||
_calculatedMeshBoxes.resize(numberOfMeshes);
|
||||
_calculatedMeshTriangles.clear();
|
||||
|
@ -478,7 +478,7 @@ void Model::recalculateMeshBoxes(bool pickAgainstTriangles) {
|
|||
|
||||
void Model::renderSetup(RenderArgs* args) {
|
||||
// set up dilated textures on first render after load/simulate
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
const FBXGeometry& geometry = getFBXGeometry();
|
||||
if (_dilatedTextures.isEmpty()) {
|
||||
foreach (const FBXMesh& mesh, geometry.meshes) {
|
||||
QVector<QSharedPointer<Texture> > dilated;
|
||||
|
@ -627,7 +627,7 @@ Extents Model::getBindExtents() const {
|
|||
if (!isActive()) {
|
||||
return Extents();
|
||||
}
|
||||
const Extents& bindExtents = _geometry->getFBXGeometry().bindExtents;
|
||||
const Extents& bindExtents = getFBXGeometry().bindExtents;
|
||||
Extents scaledExtents = { bindExtents.minimum * _scale, bindExtents.maximum * _scale };
|
||||
return scaledExtents;
|
||||
}
|
||||
|
@ -636,12 +636,12 @@ Extents Model::getMeshExtents() const {
|
|||
if (!isActive()) {
|
||||
return Extents();
|
||||
}
|
||||
const Extents& extents = _geometry->getFBXGeometry().meshExtents;
|
||||
const Extents& extents = getFBXGeometry().meshExtents;
|
||||
|
||||
// even though our caller asked for "unscaled" we need to include any fst scaling, translation, and rotation, which
|
||||
// is captured in the offset matrix
|
||||
glm::vec3 minimum = glm::vec3(_geometry->getFBXGeometry().offset * glm::vec4(extents.minimum, 1.0f));
|
||||
glm::vec3 maximum = glm::vec3(_geometry->getFBXGeometry().offset * glm::vec4(extents.maximum, 1.0f));
|
||||
glm::vec3 minimum = glm::vec3(getFBXGeometry().offset * glm::vec4(extents.minimum, 1.0f));
|
||||
glm::vec3 maximum = glm::vec3(getFBXGeometry().offset * glm::vec4(extents.maximum, 1.0f));
|
||||
Extents scaledExtents = { minimum * _scale, maximum * _scale };
|
||||
return scaledExtents;
|
||||
}
|
||||
|
@ -651,12 +651,12 @@ Extents Model::getUnscaledMeshExtents() const {
|
|||
return Extents();
|
||||
}
|
||||
|
||||
const Extents& extents = _geometry->getFBXGeometry().meshExtents;
|
||||
const Extents& extents = getFBXGeometry().meshExtents;
|
||||
|
||||
// even though our caller asked for "unscaled" we need to include any fst scaling, translation, and rotation, which
|
||||
// is captured in the offset matrix
|
||||
glm::vec3 minimum = glm::vec3(_geometry->getFBXGeometry().offset * glm::vec4(extents.minimum, 1.0f));
|
||||
glm::vec3 maximum = glm::vec3(_geometry->getFBXGeometry().offset * glm::vec4(extents.maximum, 1.0f));
|
||||
glm::vec3 minimum = glm::vec3(getFBXGeometry().offset * glm::vec4(extents.minimum, 1.0f));
|
||||
glm::vec3 maximum = glm::vec3(getFBXGeometry().offset * glm::vec4(extents.maximum, 1.0f));
|
||||
Extents scaledExtents = { minimum, maximum };
|
||||
|
||||
return scaledExtents;
|
||||
|
@ -665,8 +665,8 @@ Extents Model::getUnscaledMeshExtents() const {
|
|||
Extents Model::calculateScaledOffsetExtents(const Extents& extents,
|
||||
glm::vec3 modelPosition, glm::quat modelOrientation) const {
|
||||
// we need to include any fst scaling, translation, and rotation, which is captured in the offset matrix
|
||||
glm::vec3 minimum = glm::vec3(_geometry->getFBXGeometry().offset * glm::vec4(extents.minimum, 1.0f));
|
||||
glm::vec3 maximum = glm::vec3(_geometry->getFBXGeometry().offset * glm::vec4(extents.maximum, 1.0f));
|
||||
glm::vec3 minimum = glm::vec3(getFBXGeometry().offset * glm::vec4(extents.minimum, 1.0f));
|
||||
glm::vec3 maximum = glm::vec3(getFBXGeometry().offset * glm::vec4(extents.maximum, 1.0f));
|
||||
|
||||
Extents scaledOffsetExtents = { ((minimum + _offset) * _scale),
|
||||
((maximum + _offset) * _scale) };
|
||||
|
@ -686,7 +686,7 @@ AABox Model::calculateScaledOffsetAABox(const AABox& box, glm::vec3 modelPositio
|
|||
|
||||
glm::vec3 Model::calculateScaledOffsetPoint(const glm::vec3& point) const {
|
||||
// we need to include any fst scaling, translation, and rotation, which is captured in the offset matrix
|
||||
glm::vec3 offsetPoint = glm::vec3(_geometry->getFBXGeometry().offset * glm::vec4(point, 1.0f));
|
||||
glm::vec3 offsetPoint = glm::vec3(getFBXGeometry().offset * glm::vec4(point, 1.0f));
|
||||
glm::vec3 scaledPoint = ((offsetPoint + _offset) * _scale);
|
||||
glm::vec3 rotatedPoint = _rotation * scaledPoint;
|
||||
glm::vec3 translatedPoint = rotatedPoint + _translation;
|
||||
|
@ -714,11 +714,11 @@ void Model::setJointTranslation(int index, bool valid, const glm::vec3& translat
|
|||
}
|
||||
|
||||
int Model::getParentJointIndex(int jointIndex) const {
|
||||
return (isActive() && jointIndex != -1) ? _geometry->getFBXGeometry().joints.at(jointIndex).parentIndex : -1;
|
||||
return (isActive() && jointIndex != -1) ? getFBXGeometry().joints.at(jointIndex).parentIndex : -1;
|
||||
}
|
||||
|
||||
int Model::getLastFreeJointIndex(int jointIndex) const {
|
||||
return (isActive() && jointIndex != -1) ? _geometry->getFBXGeometry().joints.at(jointIndex).freeLineage.last() : -1;
|
||||
return (isActive() && jointIndex != -1) ? getFBXGeometry().joints.at(jointIndex).freeLineage.last() : -1;
|
||||
}
|
||||
|
||||
void Model::setURL(const QUrl& url) {
|
||||
|
@ -743,29 +743,16 @@ void Model::setURL(const QUrl& url) {
|
|||
invalidCalculatedMeshBoxes();
|
||||
deleteGeometry();
|
||||
|
||||
_geometry.reset(new NetworkGeometry(url, false, QVariantHash()));
|
||||
_geometry = DependencyManager::get<ModelCache>()->getGeometry(url);
|
||||
onInvalidate();
|
||||
}
|
||||
|
||||
const QSharedPointer<NetworkGeometry> Model::getCollisionGeometry(bool delayLoad)
|
||||
{
|
||||
if (_collisionGeometry.isNull() && !_collisionUrl.isEmpty()) {
|
||||
_collisionGeometry.reset(new NetworkGeometry(_collisionUrl, delayLoad, QVariantHash()));
|
||||
}
|
||||
|
||||
if (_collisionGeometry && _collisionGeometry->isLoaded()) {
|
||||
return _collisionGeometry;
|
||||
}
|
||||
|
||||
return QSharedPointer<NetworkGeometry>();
|
||||
}
|
||||
|
||||
void Model::setCollisionModelURL(const QUrl& url) {
|
||||
if (_collisionUrl == url) {
|
||||
return;
|
||||
}
|
||||
_collisionUrl = url;
|
||||
_collisionGeometry.reset(new NetworkGeometry(url, false, QVariantHash()));
|
||||
_collisionGeometry = DependencyManager::get<ModelCache>()->getGeometry(url);
|
||||
}
|
||||
|
||||
bool Model::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position) const {
|
||||
|
@ -815,13 +802,13 @@ QStringList Model::getJointNames() const {
|
|||
Q_RETURN_ARG(QStringList, result));
|
||||
return result;
|
||||
}
|
||||
return isActive() ? _geometry->getFBXGeometry().getJointNames() : QStringList();
|
||||
return isActive() ? getFBXGeometry().getJointNames() : QStringList();
|
||||
}
|
||||
|
||||
class Blender : public QRunnable {
|
||||
public:
|
||||
|
||||
Blender(ModelPointer model, int blendNumber, const QWeakPointer<NetworkGeometry>& geometry,
|
||||
Blender(ModelPointer model, int blendNumber, const std::weak_ptr<NetworkGeometry>& geometry,
|
||||
const QVector<FBXMesh>& meshes, const QVector<float>& blendshapeCoefficients);
|
||||
|
||||
virtual void run();
|
||||
|
@ -830,12 +817,12 @@ private:
|
|||
|
||||
ModelPointer _model;
|
||||
int _blendNumber;
|
||||
QWeakPointer<NetworkGeometry> _geometry;
|
||||
std::weak_ptr<NetworkGeometry> _geometry;
|
||||
QVector<FBXMesh> _meshes;
|
||||
QVector<float> _blendshapeCoefficients;
|
||||
};
|
||||
|
||||
Blender::Blender(ModelPointer model, int blendNumber, const QWeakPointer<NetworkGeometry>& geometry,
|
||||
Blender::Blender(ModelPointer model, int blendNumber, const std::weak_ptr<NetworkGeometry>& geometry,
|
||||
const QVector<FBXMesh>& meshes, const QVector<float>& blendshapeCoefficients) :
|
||||
_model(model),
|
||||
_blendNumber(blendNumber),
|
||||
|
@ -878,7 +865,7 @@ void Blender::run() {
|
|||
// post the result to the geometry cache, which will dispatch to the model if still alive
|
||||
QMetaObject::invokeMethod(DependencyManager::get<ModelBlender>().data(), "setBlendedVertices",
|
||||
Q_ARG(ModelPointer, _model), Q_ARG(int, _blendNumber),
|
||||
Q_ARG(const QWeakPointer<NetworkGeometry>&, _geometry), Q_ARG(const QVector<glm::vec3>&, vertices),
|
||||
Q_ARG(const std::weak_ptr<NetworkGeometry>&, _geometry), Q_ARG(const QVector<glm::vec3>&, vertices),
|
||||
Q_ARG(const QVector<glm::vec3>&, normals));
|
||||
}
|
||||
|
||||
|
@ -1010,7 +997,7 @@ void Model::updateClusterMatrices(glm::vec3 modelPosition, glm::quat modelOrient
|
|||
return;
|
||||
}
|
||||
_needsUpdateClusterMatrices = false;
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
const FBXGeometry& geometry = getFBXGeometry();
|
||||
glm::mat4 zeroScale(glm::vec4(0.0f, 0.0f, 0.0f, 0.0f),
|
||||
glm::vec4(0.0f, 0.0f, 0.0f, 0.0f),
|
||||
glm::vec4(0.0f, 0.0f, 0.0f, 0.0f),
|
||||
|
@ -1067,41 +1054,44 @@ void Model::updateClusterMatrices(glm::vec3 modelPosition, glm::quat modelOrient
|
|||
}
|
||||
|
||||
void Model::inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm::quat& targetRotation, float priority) {
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
const FBXGeometry& geometry = getFBXGeometry();
|
||||
const QVector<int>& freeLineage = geometry.joints.at(endIndex).freeLineage;
|
||||
glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset);
|
||||
_rig->inverseKinematics(endIndex, targetPosition, targetRotation, priority, freeLineage, parentTransform);
|
||||
}
|
||||
|
||||
bool Model::restoreJointPosition(int jointIndex, float fraction, float priority) {
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
const FBXGeometry& geometry = getFBXGeometry();
|
||||
const QVector<int>& freeLineage = geometry.joints.at(jointIndex).freeLineage;
|
||||
return _rig->restoreJointPosition(jointIndex, fraction, priority, freeLineage);
|
||||
}
|
||||
|
||||
float Model::getLimbLength(int jointIndex) const {
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
const FBXGeometry& geometry = getFBXGeometry();
|
||||
const QVector<int>& freeLineage = geometry.joints.at(jointIndex).freeLineage;
|
||||
return _rig->getLimbLength(jointIndex, freeLineage, _scale, geometry.joints);
|
||||
}
|
||||
|
||||
bool Model::maybeStartBlender() {
|
||||
const FBXGeometry& fbxGeometry = _geometry->getFBXGeometry();
|
||||
if (fbxGeometry.hasBlendedMeshes()) {
|
||||
QThreadPool::globalInstance()->start(new Blender(getThisPointer(), ++_blendNumber, _geometry,
|
||||
fbxGeometry.meshes, _blendshapeCoefficients));
|
||||
return true;
|
||||
if (isLoaded()) {
|
||||
const FBXGeometry& fbxGeometry = getFBXGeometry();
|
||||
if (fbxGeometry.hasBlendedMeshes()) {
|
||||
QThreadPool::globalInstance()->start(new Blender(getThisPointer(), ++_blendNumber, _geometry,
|
||||
fbxGeometry.meshes, _blendshapeCoefficients));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Model::setBlendedVertices(int blendNumber, const QWeakPointer<NetworkGeometry>& geometry,
|
||||
void Model::setBlendedVertices(int blendNumber, const std::weak_ptr<NetworkGeometry>& geometry,
|
||||
const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& normals) {
|
||||
if (_geometry != geometry || _blendedVertexBuffers.empty() || blendNumber < _appliedBlendNumber) {
|
||||
auto geometryRef = geometry.lock();
|
||||
if (!geometryRef || _geometry != geometryRef || _blendedVertexBuffers.empty() || blendNumber < _appliedBlendNumber) {
|
||||
return;
|
||||
}
|
||||
_appliedBlendNumber = blendNumber;
|
||||
const FBXGeometry& fbxGeometry = _geometry->getFBXGeometry();
|
||||
const FBXGeometry& fbxGeometry = getFBXGeometry();
|
||||
int index = 0;
|
||||
for (int i = 0; i < fbxGeometry.meshes.size(); i++) {
|
||||
const FBXMesh& mesh = fbxGeometry.meshes.at(i);
|
||||
|
@ -1118,13 +1108,6 @@ void Model::setBlendedVertices(int blendNumber, const QWeakPointer<NetworkGeomet
|
|||
}
|
||||
}
|
||||
|
||||
void Model::setGeometry(const QSharedPointer<NetworkGeometry>& newGeometry) {
|
||||
if (_geometry == newGeometry) {
|
||||
return;
|
||||
}
|
||||
_geometry = newGeometry;
|
||||
}
|
||||
|
||||
void Model::deleteGeometry() {
|
||||
_blendedVertexBuffers.clear();
|
||||
_meshStates.clear();
|
||||
|
@ -1134,7 +1117,7 @@ void Model::deleteGeometry() {
|
|||
|
||||
AABox Model::getPartBounds(int meshIndex, int partIndex, glm::vec3 modelPosition, glm::quat modelOrientation) const {
|
||||
|
||||
if (!_geometry || !_geometry->isLoaded()) {
|
||||
if (!isLoaded()) {
|
||||
return AABox();
|
||||
}
|
||||
|
||||
|
@ -1143,10 +1126,10 @@ AABox Model::getPartBounds(int meshIndex, int partIndex, glm::vec3 modelPosition
|
|||
bool isSkinned = state.clusterMatrices.size() > 1;
|
||||
if (isSkinned) {
|
||||
// if we're skinned return the entire mesh extents because we can't know for sure our clusters don't move us
|
||||
return calculateScaledOffsetAABox(_geometry->getFBXGeometry().meshExtents, modelPosition, modelOrientation);
|
||||
return calculateScaledOffsetAABox(getFBXGeometry().meshExtents, modelPosition, modelOrientation);
|
||||
}
|
||||
}
|
||||
if (_geometry->getFBXGeometry().meshes.size() > meshIndex) {
|
||||
if (getFBXGeometry().meshes.size() > meshIndex) {
|
||||
|
||||
// FIX ME! - This is currently a hack because for some mesh parts our efforts to calculate the bounding
|
||||
// box of the mesh part fails. It seems to create boxes that are not consistent with where the
|
||||
|
@ -1160,27 +1143,28 @@ AABox Model::getPartBounds(int meshIndex, int partIndex, glm::vec3 modelPosition
|
|||
// return _calculatedMeshBoxes[meshIndex];
|
||||
//
|
||||
// If we not skinned use the bounds of the subMesh for all it's parts
|
||||
const FBXMesh& mesh = _geometry->getFBXGeometry().meshes.at(meshIndex);
|
||||
const FBXMesh& mesh = getFBXGeometry().meshes.at(meshIndex);
|
||||
return calculateScaledOffsetExtents(mesh.meshExtents, modelPosition, modelOrientation);
|
||||
}
|
||||
return AABox();
|
||||
}
|
||||
|
||||
void Model::segregateMeshGroups() {
|
||||
QSharedPointer<NetworkGeometry> networkGeometry;
|
||||
NetworkGeometry::Pointer networkGeometry;
|
||||
bool showingCollisionHull = false;
|
||||
if (_showCollisionHull && _collisionGeometry) {
|
||||
if (_collisionGeometry->isLoaded()) {
|
||||
if (isCollisionLoaded()) {
|
||||
networkGeometry = _collisionGeometry;
|
||||
showingCollisionHull = true;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
assert(isLoaded());
|
||||
networkGeometry = _geometry;
|
||||
}
|
||||
const FBXGeometry& geometry = networkGeometry->getFBXGeometry();
|
||||
const std::vector<std::unique_ptr<NetworkMesh>>& networkMeshes = networkGeometry->getMeshes();
|
||||
const FBXGeometry& geometry = networkGeometry->getGeometry()->getGeometry();
|
||||
const auto& networkMeshes = networkGeometry->getGeometry()->getMeshes();
|
||||
|
||||
// all of our mesh vectors must match in size
|
||||
auto geoMeshesSize = geometry.meshes.size();
|
||||
|
@ -1208,7 +1192,7 @@ void Model::segregateMeshGroups() {
|
|||
int shapeID = 0;
|
||||
for (int i = 0; i < (int)networkMeshes.size(); i++) {
|
||||
const FBXMesh& mesh = geometry.meshes.at(i);
|
||||
const NetworkMesh& networkMesh = *(networkMeshes.at(i).get());
|
||||
const auto& networkMesh = networkMeshes.at(i);
|
||||
|
||||
// Create the render payloads
|
||||
int totalParts = mesh.parts.size();
|
||||
|
@ -1220,7 +1204,7 @@ void Model::segregateMeshGroups() {
|
|||
_collisionHullMaterial->setMetallic(0.02f);
|
||||
_collisionHullMaterial->setRoughness(0.5f);
|
||||
}
|
||||
_renderItemsSet << std::make_shared<MeshPartPayload>(networkMesh._mesh, partIndex, _collisionHullMaterial, transform, offset);
|
||||
_renderItemsSet << std::make_shared<MeshPartPayload>(networkMesh, partIndex, _collisionHullMaterial, transform, offset);
|
||||
} else {
|
||||
_renderItemsSet << std::make_shared<ModelMeshPartPayload>(this, i, partIndex, shapeID, transform, offset);
|
||||
}
|
||||
|
@ -1285,7 +1269,7 @@ void ModelBlender::noteRequiresBlend(ModelPointer model) {
|
|||
}
|
||||
|
||||
void ModelBlender::setBlendedVertices(ModelPointer model, int blendNumber,
|
||||
const QWeakPointer<NetworkGeometry>& geometry, const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& normals) {
|
||||
const std::weak_ptr<NetworkGeometry>& geometry, const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& normals) {
|
||||
if (model) {
|
||||
model->setBlendedVertices(blendNumber, geometry, vertices, normals);
|
||||
}
|
||||
|
|
|
@ -78,9 +78,9 @@ public:
|
|||
|
||||
// new Scene/Engine rendering support
|
||||
void setVisibleInScene(bool newValue, std::shared_ptr<render::Scene> scene);
|
||||
bool needsFixupInScene();
|
||||
bool readyToAddToScene(RenderArgs* renderArgs = nullptr) {
|
||||
return !_needsReload && isRenderable() && isActive() && isLoaded();
|
||||
bool needsFixupInScene() const;
|
||||
bool readyToAddToScene(RenderArgs* renderArgs = nullptr) const {
|
||||
return !_needsReload && isRenderable() && isActive();
|
||||
}
|
||||
bool initWhenReady(render::ScenePointer scene);
|
||||
bool addToScene(std::shared_ptr<render::Scene> scene,
|
||||
|
@ -92,7 +92,7 @@ public:
|
|||
bool showCollisionHull = false);
|
||||
void removeFromScene(std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges);
|
||||
void renderSetup(RenderArgs* args);
|
||||
bool isRenderable() const { return !_meshStates.isEmpty() || (isActive() && _geometry->getMeshes().empty()); }
|
||||
bool isRenderable() const { return !_meshStates.isEmpty() || (isActive() && getGeometry()->getGeometry()->getMeshes().empty()); }
|
||||
|
||||
bool isVisible() const { return _isVisible; }
|
||||
|
||||
|
@ -102,11 +102,11 @@ public:
|
|||
bool maybeStartBlender();
|
||||
|
||||
/// Sets blended vertices computed in a separate thread.
|
||||
void setBlendedVertices(int blendNumber, const QWeakPointer<NetworkGeometry>& geometry,
|
||||
void setBlendedVertices(int blendNumber, const std::weak_ptr<NetworkGeometry>& geometry,
|
||||
const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& normals);
|
||||
|
||||
bool isLoaded() const { return _geometry && _geometry->isLoaded(); }
|
||||
bool isLoadedWithTextures() const { return _geometry && _geometry->isLoadedWithTextures(); }
|
||||
bool isLoaded() const { return _geometry && _geometry->getGeometry(); }
|
||||
bool isCollisionLoaded() const { return _collisionGeometry && _collisionGeometry->getGeometry(); }
|
||||
|
||||
void setIsWireframe(bool isWireframe) { _isWireframe = isWireframe; }
|
||||
bool isWireframe() const { return _isWireframe; }
|
||||
|
@ -123,12 +123,22 @@ public:
|
|||
virtual void updateClusterMatrices(glm::vec3 modelPosition, glm::quat modelOrientation);
|
||||
|
||||
/// Returns a reference to the shared geometry.
|
||||
const QSharedPointer<NetworkGeometry>& getGeometry() const { return _geometry; }
|
||||
const NetworkGeometry::Pointer& getGeometry() const { return _geometry; }
|
||||
/// Returns a reference to the shared collision geometry.
|
||||
const NetworkGeometry::Pointer& getCollisionGeometry() const { return _collisionGeometry; }
|
||||
|
||||
bool isActive() const { return _geometry && _geometry->isLoaded(); }
|
||||
/// Provided as a convenience, will crash if !isLoaded()
|
||||
// And so that getGeometry() isn't chained everywhere
|
||||
const FBXGeometry& getFBXGeometry() const { assert(isLoaded()); return getGeometry()->getGeometry()->getGeometry(); }
|
||||
/// Provided as a convenience, will crash if !isCollisionLoaded()
|
||||
const FBXGeometry& getCollisionFBXGeometry() const { assert(isCollisionLoaded()); return getCollisionGeometry()->getGeometry()->getGeometry(); }
|
||||
|
||||
Q_INVOKABLE void setTextureWithNameToURL(const QString& name, const QUrl& url)
|
||||
{ _geometry->setTextureWithNameToURL(name, url); }
|
||||
// Set the model to use for collisions
|
||||
Q_INVOKABLE void setCollisionModelURL(const QUrl& url);
|
||||
const QUrl& getCollisionURL() const { return _collisionUrl; }
|
||||
|
||||
|
||||
bool isActive() const { return isLoaded(); }
|
||||
|
||||
bool convexHullContains(glm::vec3 point);
|
||||
|
||||
|
@ -143,13 +153,6 @@ public:
|
|||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
QString& extraInfo, bool pickAgainstTriangles = false);
|
||||
|
||||
// Set the model to use for collisions
|
||||
Q_INVOKABLE void setCollisionModelURL(const QUrl& url);
|
||||
const QUrl& getCollisionURL() const { return _collisionUrl; }
|
||||
|
||||
/// Returns a reference to the shared collision geometry.
|
||||
const QSharedPointer<NetworkGeometry> getCollisionGeometry(bool delayLoad = false);
|
||||
|
||||
void setOffset(const glm::vec3& offset);
|
||||
const glm::vec3& getOffset() const { return _offset; }
|
||||
|
||||
|
@ -257,8 +260,7 @@ protected:
|
|||
/// \return true if joint exists
|
||||
bool getJointPosition(int jointIndex, glm::vec3& position) const;
|
||||
|
||||
QSharedPointer<NetworkGeometry> _geometry;
|
||||
void setGeometry(const QSharedPointer<NetworkGeometry>& newGeometry);
|
||||
NetworkGeometry::Pointer _geometry;
|
||||
|
||||
glm::vec3 _translation;
|
||||
glm::quat _rotation;
|
||||
|
@ -325,7 +327,7 @@ protected:
|
|||
void deleteGeometry();
|
||||
void initJointTransforms();
|
||||
|
||||
QSharedPointer<NetworkGeometry> _collisionGeometry;
|
||||
NetworkGeometry::Pointer _collisionGeometry;
|
||||
|
||||
float _pupilDilation;
|
||||
QVector<float> _blendshapeCoefficients;
|
||||
|
@ -376,8 +378,8 @@ protected:
|
|||
bool _readyWhenAdded { false };
|
||||
bool _needsReload { true };
|
||||
bool _needsUpdateClusterMatrices { true };
|
||||
bool _needsUpdateTransparentTextures { true };
|
||||
bool _hasTransparentTextures { false };
|
||||
mutable bool _needsUpdateTransparentTextures { true };
|
||||
mutable bool _hasTransparentTextures { false };
|
||||
bool _showCollisionHull { false };
|
||||
|
||||
friend class ModelMeshPartPayload;
|
||||
|
@ -385,7 +387,7 @@ protected:
|
|||
};
|
||||
|
||||
Q_DECLARE_METATYPE(ModelPointer)
|
||||
Q_DECLARE_METATYPE(QWeakPointer<NetworkGeometry>)
|
||||
Q_DECLARE_METATYPE(std::weak_ptr<NetworkGeometry>)
|
||||
|
||||
/// Handle management of pending models that need blending
|
||||
class ModelBlender : public QObject, public Dependency {
|
||||
|
@ -398,7 +400,7 @@ public:
|
|||
void noteRequiresBlend(ModelPointer model);
|
||||
|
||||
public slots:
|
||||
void setBlendedVertices(ModelPointer model, int blendNumber, const QWeakPointer<NetworkGeometry>& geometry,
|
||||
void setBlendedVertices(ModelPointer model, int blendNumber, const std::weak_ptr<NetworkGeometry>& geometry,
|
||||
const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& normals);
|
||||
|
||||
private:
|
||||
|
|
Loading…
Reference in a new issue