mirror of
https://github.com/overte-org/overte.git
synced 2025-08-06 22:39:18 +02:00
Merge pull request #1982 from ey6es/master
Load FST file before FBX, use its filename and texdir properties like Faceshift does. Fixed a bug with the fallback model and added basic LOD switching based on distance.
This commit is contained in:
commit
d4c99b6320
17 changed files with 304 additions and 226 deletions
Before Width: | Height: | Size: 330 KiB After Width: | Height: | Size: 330 KiB |
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 142 KiB |
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
|
@ -1,3 +1,5 @@
|
||||||
|
filename=defaultAvatar/body.fbx
|
||||||
|
texdir=defaultAvatar
|
||||||
scale=130
|
scale=130
|
||||||
joint = jointRoot = jointRoot
|
joint = jointRoot = jointRoot
|
||||||
joint = jointLean = jointSpine
|
joint = jointLean = jointSpine
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# faceshift target mapping file
|
# faceshift target mapping file
|
||||||
name= defaultAvatar_head
|
name= defaultAvatar_head
|
||||||
filename=../../../Avatars/Jelly/jellyrob_blue.fbx
|
filename=defaultAvatar/head.fbx
|
||||||
texdir=../../../Avatars/Jelly
|
texdir=defaultAvatar
|
||||||
scale=80
|
scale=80
|
||||||
rx=0
|
rx=0
|
||||||
ry=0
|
ry=0
|
||||||
|
|
|
@ -349,13 +349,13 @@ bool Avatar::findSphereCollisionWithSkeleton(const glm::vec3& sphereCenter, floa
|
||||||
|
|
||||||
void Avatar::setFaceModelURL(const QUrl &faceModelURL) {
|
void Avatar::setFaceModelURL(const QUrl &faceModelURL) {
|
||||||
AvatarData::setFaceModelURL(faceModelURL);
|
AvatarData::setFaceModelURL(faceModelURL);
|
||||||
const QUrl DEFAULT_FACE_MODEL_URL = QUrl::fromLocalFile("resources/meshes/defaultAvatar_head.fbx");
|
const QUrl DEFAULT_FACE_MODEL_URL = QUrl::fromLocalFile("resources/meshes/defaultAvatar_head.fst");
|
||||||
_head.getFaceModel().setURL(_faceModelURL, DEFAULT_FACE_MODEL_URL);
|
_head.getFaceModel().setURL(_faceModelURL, DEFAULT_FACE_MODEL_URL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Avatar::setSkeletonModelURL(const QUrl &skeletonModelURL) {
|
void Avatar::setSkeletonModelURL(const QUrl &skeletonModelURL) {
|
||||||
AvatarData::setSkeletonModelURL(skeletonModelURL);
|
AvatarData::setSkeletonModelURL(skeletonModelURL);
|
||||||
const QUrl DEFAULT_SKELETON_MODEL_URL = QUrl::fromLocalFile("resources/meshes/defaultAvatar_body.fbx");
|
const QUrl DEFAULT_SKELETON_MODEL_URL = QUrl::fromLocalFile("resources/meshes/defaultAvatar_body.fst");
|
||||||
_skeletonModel.setURL(_skeletonModelURL, DEFAULT_SKELETON_MODEL_URL);
|
_skeletonModel.setURL(_skeletonModelURL, DEFAULT_SKELETON_MODEL_URL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ FaceModel::FaceModel(Head* owningHead) :
|
||||||
|
|
||||||
void FaceModel::simulate(float deltaTime) {
|
void FaceModel::simulate(float deltaTime) {
|
||||||
if (!isActive()) {
|
if (!isActive()) {
|
||||||
|
Model::simulate(deltaTime);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Avatar* owningAvatar = static_cast<Avatar*>(_owningHead->_owningAvatar);
|
Avatar* owningAvatar = static_cast<Avatar*>(_owningHead->_owningAvatar);
|
||||||
|
|
|
@ -21,9 +21,9 @@ SkeletonModel::SkeletonModel(Avatar* owningAvatar) :
|
||||||
|
|
||||||
void SkeletonModel::simulate(float deltaTime) {
|
void SkeletonModel::simulate(float deltaTime) {
|
||||||
if (!isActive()) {
|
if (!isActive()) {
|
||||||
|
Model::simulate(deltaTime);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setTranslation(_owningAvatar->getPosition());
|
setTranslation(_owningAvatar->getPosition());
|
||||||
setRotation(_owningAvatar->getOrientation() * glm::angleAxis(180.0f, 0.0f, 1.0f, 0.0f));
|
setRotation(_owningAvatar->getOrientation() * glm::angleAxis(180.0f, 0.0f, 1.0f, 0.0f));
|
||||||
const float MODEL_SCALE = 0.0006f;
|
const float MODEL_SCALE = 0.0006f;
|
||||||
|
|
|
@ -1577,14 +1577,16 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
||||||
return geometry;
|
return geometry;
|
||||||
}
|
}
|
||||||
|
|
||||||
FBXGeometry readFBX(const QByteArray& model, const QByteArray& mapping) {
|
QVariantHash readMapping(const QByteArray& data) {
|
||||||
QBuffer modelBuffer(const_cast<QByteArray*>(&model));
|
QBuffer buffer(const_cast<QByteArray*>(&data));
|
||||||
modelBuffer.open(QIODevice::ReadOnly);
|
buffer.open(QIODevice::ReadOnly);
|
||||||
|
return parseMapping(&buffer);
|
||||||
|
}
|
||||||
|
|
||||||
QBuffer mappingBuffer(const_cast<QByteArray*>(&mapping));
|
FBXGeometry readFBX(const QByteArray& model, const QVariantHash& mapping) {
|
||||||
mappingBuffer.open(QIODevice::ReadOnly);
|
QBuffer buffer(const_cast<QByteArray*>(&model));
|
||||||
|
buffer.open(QIODevice::ReadOnly);
|
||||||
return extractFBXGeometry(parseFBX(&modelBuffer), parseMapping(&mappingBuffer));
|
return extractFBXGeometry(parseFBX(&buffer), mapping);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool addMeshVoxelsOperation(OctreeElement* element, void* extraData) {
|
bool addMeshVoxelsOperation(OctreeElement* element, void* extraData) {
|
||||||
|
|
|
@ -164,9 +164,12 @@ public:
|
||||||
QVector<FBXAttachment> attachments;
|
QVector<FBXAttachment> attachments;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Reads an FST mapping from the supplied data.
|
||||||
|
QVariantHash readMapping(const QByteArray& data);
|
||||||
|
|
||||||
/// Reads FBX geometry from the supplied model and mapping data.
|
/// Reads FBX geometry from the supplied model and mapping data.
|
||||||
/// \exception QString if an error occurs in parsing
|
/// \exception QString if an error occurs in parsing
|
||||||
FBXGeometry readFBX(const QByteArray& model, const QByteArray& mapping);
|
FBXGeometry readFBX(const QByteArray& model, const QVariantHash& mapping);
|
||||||
|
|
||||||
/// Reads SVO geometry from the supplied model data.
|
/// Reads SVO geometry from the supplied model data.
|
||||||
FBXGeometry readSVO(const QByteArray& model);
|
FBXGeometry readSVO(const QByteArray& model);
|
||||||
|
|
|
@ -7,11 +7,7 @@
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
// include this before QOpenGLBuffer, which includes an earlier version of OpenGL
|
|
||||||
#include "InterfaceConfig.h"
|
|
||||||
|
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
#include <QOpenGLBuffer>
|
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
|
@ -298,46 +294,86 @@ QSharedPointer<NetworkGeometry> GeometryCache::getGeometry(const QUrl& url, cons
|
||||||
if (geometry.isNull()) {
|
if (geometry.isNull()) {
|
||||||
geometry = QSharedPointer<NetworkGeometry>(new NetworkGeometry(url, fallback.isValid() ?
|
geometry = QSharedPointer<NetworkGeometry>(new NetworkGeometry(url, fallback.isValid() ?
|
||||||
getGeometry(fallback) : QSharedPointer<NetworkGeometry>()));
|
getGeometry(fallback) : QSharedPointer<NetworkGeometry>()));
|
||||||
|
geometry->setLODParent(geometry);
|
||||||
_networkGeometry.insert(url, geometry);
|
_networkGeometry.insert(url, geometry);
|
||||||
}
|
}
|
||||||
return geometry;
|
return geometry;
|
||||||
}
|
}
|
||||||
|
|
||||||
NetworkGeometry::NetworkGeometry(const QUrl& url, const QSharedPointer<NetworkGeometry>& fallback) :
|
const float NetworkGeometry::NO_HYSTERESIS = -1.0f;
|
||||||
_modelRequest(url),
|
|
||||||
_modelReply(NULL),
|
NetworkGeometry::NetworkGeometry(const QUrl& url, const QSharedPointer<NetworkGeometry>& fallback,
|
||||||
_mappingReply(NULL),
|
const QVariantHash& mapping, const QUrl& textureBase) :
|
||||||
|
_request(url),
|
||||||
|
_reply(NULL),
|
||||||
|
_mapping(mapping),
|
||||||
|
_textureBase(textureBase.isValid() ? textureBase : url),
|
||||||
_fallback(fallback),
|
_fallback(fallback),
|
||||||
_attempts(0)
|
_startedLoading(false),
|
||||||
{
|
_failedToLoad(false),
|
||||||
|
_attempts(0) {
|
||||||
|
|
||||||
if (!url.isValid()) {
|
if (!url.isValid()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_modelRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
|
_request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
|
||||||
makeModelRequest();
|
|
||||||
|
|
||||||
QUrl mappingURL = url;
|
// if we already have a mapping (because we're an LOD), hold off on loading until we're requested
|
||||||
QString path = url.path();
|
if (mapping.isEmpty()) {
|
||||||
mappingURL.setPath(path.left(path.lastIndexOf('.')) + ".fst");
|
makeRequest();
|
||||||
QNetworkRequest mappingRequest(mappingURL);
|
}
|
||||||
mappingRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
|
|
||||||
_mappingReply = Application::getInstance()->getNetworkAccessManager()->get(mappingRequest);
|
|
||||||
|
|
||||||
connect(_mappingReply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(maybeReadModelWithMapping()));
|
|
||||||
connect(_mappingReply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleMappingReplyError()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NetworkGeometry::~NetworkGeometry() {
|
NetworkGeometry::~NetworkGeometry() {
|
||||||
if (_modelReply != NULL) {
|
if (_reply != NULL) {
|
||||||
delete _modelReply;
|
delete _reply;
|
||||||
}
|
}
|
||||||
if (_mappingReply != NULL) {
|
}
|
||||||
delete _mappingReply;
|
|
||||||
|
QSharedPointer<NetworkGeometry> NetworkGeometry::getLODOrFallback(float distance, float& hysteresis) const {
|
||||||
|
if (_lodParent.data() != this) {
|
||||||
|
return _lodParent.data()->getLODOrFallback(distance, hysteresis);
|
||||||
}
|
}
|
||||||
foreach (const NetworkMesh& mesh, _meshes) {
|
if (_failedToLoad && _fallback) {
|
||||||
glDeleteBuffers(1, &mesh.indexBufferID);
|
return _fallback;
|
||||||
glDeleteBuffers(1, &mesh.vertexBufferID);
|
}
|
||||||
}
|
QSharedPointer<NetworkGeometry> lod = _lodParent;
|
||||||
|
float lodDistance = 0.0f;
|
||||||
|
QMap<float, QSharedPointer<NetworkGeometry> >::const_iterator it = _lods.upperBound(distance);
|
||||||
|
if (it != _lods.constBegin()) {
|
||||||
|
it = it - 1;
|
||||||
|
lod = it.value();
|
||||||
|
lodDistance = it.key();
|
||||||
|
}
|
||||||
|
if (hysteresis != NO_HYSTERESIS && hysteresis != lodDistance) {
|
||||||
|
// if we previously selected a different distance, make sure we've moved far enough to justify switching
|
||||||
|
const float HYSTERESIS_PROPORTION = 0.1f;
|
||||||
|
if (glm::abs(distance - qMax(hysteresis, lodDistance)) / fabsf(hysteresis - lodDistance) < HYSTERESIS_PROPORTION) {
|
||||||
|
return getLODOrFallback(hysteresis, hysteresis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (lod->isLoaded()) {
|
||||||
|
hysteresis = lodDistance;
|
||||||
|
return lod;
|
||||||
|
}
|
||||||
|
// if the ideal LOD isn't loaded, we need to make sure it's started to load, and possibly return the closest loaded one
|
||||||
|
if (!lod->_startedLoading) {
|
||||||
|
lod->makeRequest();
|
||||||
|
}
|
||||||
|
float closestDistance = FLT_MAX;
|
||||||
|
if (isLoaded()) {
|
||||||
|
lod = _lodParent;
|
||||||
|
closestDistance = distance;
|
||||||
|
}
|
||||||
|
for (it = _lods.constBegin(); it != _lods.constEnd(); it++) {
|
||||||
|
float distanceToLOD = glm::abs(distance - it.key());
|
||||||
|
if (it.value()->isLoaded() && distanceToLOD < closestDistance) {
|
||||||
|
lod = it.value();
|
||||||
|
closestDistance = distanceToLOD;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hysteresis = NO_HYSTERESIS;
|
||||||
|
return lod;
|
||||||
}
|
}
|
||||||
|
|
||||||
glm::vec4 NetworkGeometry::computeAverageColor() const {
|
glm::vec4 NetworkGeometry::computeAverageColor() const {
|
||||||
|
@ -364,20 +400,167 @@ glm::vec4 NetworkGeometry::computeAverageColor() const {
|
||||||
return (totalTriangles == 0) ? glm::vec4(1.0f, 1.0f, 1.0f, 1.0f) : totalColor / totalTriangles;
|
return (totalTriangles == 0) ? glm::vec4(1.0f, 1.0f, 1.0f, 1.0f) : totalColor / totalTriangles;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkGeometry::makeModelRequest() {
|
void NetworkGeometry::makeRequest() {
|
||||||
_modelReply = Application::getInstance()->getNetworkAccessManager()->get(_modelRequest);
|
_startedLoading = true;
|
||||||
|
_reply = Application::getInstance()->getNetworkAccessManager()->get(_request);
|
||||||
|
|
||||||
connect(_modelReply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(maybeReadModelWithMapping()));
|
connect(_reply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(handleDownloadProgress(qint64,qint64)));
|
||||||
connect(_modelReply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleModelReplyError()));
|
connect(_reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleReplyError()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkGeometry::handleModelReplyError() {
|
void NetworkGeometry::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) {
|
||||||
QDebug debug = qDebug() << _modelReply->errorString();
|
if (!_reply->isFinished()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
QNetworkReply::NetworkError error = _modelReply->error();
|
QUrl url = _reply->url();
|
||||||
_modelReply->disconnect(this);
|
QByteArray data = _reply->readAll();
|
||||||
_modelReply->deleteLater();
|
_reply->disconnect(this);
|
||||||
_modelReply = NULL;
|
_reply->deleteLater();
|
||||||
|
_reply = NULL;
|
||||||
|
|
||||||
|
if (url.path().toLower().endsWith(".fst")) {
|
||||||
|
// it's a mapping file; parse it and get the mesh filename
|
||||||
|
_mapping = readMapping(data);
|
||||||
|
QString filename = _mapping.value("filename").toString();
|
||||||
|
if (filename.isNull()) {
|
||||||
|
qDebug() << "Mapping file " << url << " has no filename.";
|
||||||
|
_failedToLoad = true;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
QString texdir = _mapping.value("texdir").toString();
|
||||||
|
if (!texdir.isNull()) {
|
||||||
|
if (!texdir.endsWith('/')) {
|
||||||
|
texdir += '/';
|
||||||
|
}
|
||||||
|
_textureBase = url.resolved(texdir);
|
||||||
|
}
|
||||||
|
QVariantHash lods = _mapping.value("lod").toHash();
|
||||||
|
for (QVariantHash::const_iterator it = lods.begin(); it != lods.end(); it++) {
|
||||||
|
QSharedPointer<NetworkGeometry> geometry(new NetworkGeometry(url.resolved(it.key()),
|
||||||
|
QSharedPointer<NetworkGeometry>(), _mapping, _textureBase));
|
||||||
|
geometry->setLODParent(_lodParent);
|
||||||
|
_lods.insert(it.value().toFloat(), geometry);
|
||||||
|
}
|
||||||
|
_request.setUrl(url.resolved(filename));
|
||||||
|
|
||||||
|
// make the request immediately only if we have no LODs to switch between
|
||||||
|
_startedLoading = false;
|
||||||
|
if (_lods.isEmpty()) {
|
||||||
|
makeRequest();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
_geometry = url.path().toLower().endsWith(".svo") ? readSVO(data) : readFBX(data, _mapping);
|
||||||
|
|
||||||
|
} catch (const QString& error) {
|
||||||
|
qDebug() << "Error reading " << url << ": " << error;
|
||||||
|
_failedToLoad = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (const FBXMesh& mesh, _geometry.meshes) {
|
||||||
|
NetworkMesh networkMesh = { QOpenGLBuffer(QOpenGLBuffer::IndexBuffer), QOpenGLBuffer(QOpenGLBuffer::VertexBuffer) };
|
||||||
|
|
||||||
|
int totalIndices = 0;
|
||||||
|
foreach (const FBXMeshPart& part, mesh.parts) {
|
||||||
|
NetworkMeshPart networkPart;
|
||||||
|
if (!part.diffuseFilename.isEmpty()) {
|
||||||
|
networkPart.diffuseTexture = Application::getInstance()->getTextureCache()->getTexture(
|
||||||
|
_textureBase.resolved(QUrl(part.diffuseFilename)), false, mesh.isEye);
|
||||||
|
}
|
||||||
|
if (!part.normalFilename.isEmpty()) {
|
||||||
|
networkPart.normalTexture = Application::getInstance()->getTextureCache()->getTexture(
|
||||||
|
_textureBase.resolved(QUrl(part.normalFilename)), true);
|
||||||
|
}
|
||||||
|
networkMesh.parts.append(networkPart);
|
||||||
|
|
||||||
|
totalIndices += (part.quadIndices.size() + part.triangleIndices.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
networkMesh.indexBuffer.create();
|
||||||
|
networkMesh.indexBuffer.bind();
|
||||||
|
networkMesh.indexBuffer.setUsagePattern(QOpenGLBuffer::StaticDraw);
|
||||||
|
networkMesh.indexBuffer.allocate(totalIndices * sizeof(int));
|
||||||
|
int offset = 0;
|
||||||
|
foreach (const FBXMeshPart& part, mesh.parts) {
|
||||||
|
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, part.quadIndices.size() * sizeof(int),
|
||||||
|
part.quadIndices.constData());
|
||||||
|
offset += part.quadIndices.size() * sizeof(int);
|
||||||
|
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, part.triangleIndices.size() * sizeof(int),
|
||||||
|
part.triangleIndices.constData());
|
||||||
|
offset += part.triangleIndices.size() * sizeof(int);
|
||||||
|
}
|
||||||
|
networkMesh.indexBuffer.release();
|
||||||
|
|
||||||
|
networkMesh.vertexBuffer.create();
|
||||||
|
networkMesh.vertexBuffer.bind();
|
||||||
|
networkMesh.vertexBuffer.setUsagePattern(QOpenGLBuffer::StaticDraw);
|
||||||
|
|
||||||
|
// if we don't need to do any blending or springing, then the positions/normals can be static
|
||||||
|
if (mesh.blendshapes.isEmpty() && mesh.springiness == 0.0f) {
|
||||||
|
int normalsOffset = mesh.vertices.size() * sizeof(glm::vec3);
|
||||||
|
int tangentsOffset = normalsOffset + mesh.normals.size() * sizeof(glm::vec3);
|
||||||
|
int colorsOffset = tangentsOffset + mesh.tangents.size() * sizeof(glm::vec3);
|
||||||
|
int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
|
||||||
|
int clusterIndicesOffset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2);
|
||||||
|
int clusterWeightsOffset = clusterIndicesOffset + mesh.clusterIndices.size() * sizeof(glm::vec4);
|
||||||
|
|
||||||
|
networkMesh.vertexBuffer.allocate(clusterWeightsOffset + mesh.clusterWeights.size() * sizeof(glm::vec4));
|
||||||
|
networkMesh.vertexBuffer.write(0, mesh.vertices.constData(), mesh.vertices.size() * sizeof(glm::vec3));
|
||||||
|
networkMesh.vertexBuffer.write(normalsOffset, mesh.normals.constData(), mesh.normals.size() * sizeof(glm::vec3));
|
||||||
|
networkMesh.vertexBuffer.write(tangentsOffset, mesh.tangents.constData(),
|
||||||
|
mesh.tangents.size() * sizeof(glm::vec3));
|
||||||
|
networkMesh.vertexBuffer.write(colorsOffset, mesh.colors.constData(), mesh.colors.size() * sizeof(glm::vec3));
|
||||||
|
networkMesh.vertexBuffer.write(texCoordsOffset, mesh.texCoords.constData(),
|
||||||
|
mesh.texCoords.size() * sizeof(glm::vec2));
|
||||||
|
networkMesh.vertexBuffer.write(clusterIndicesOffset, mesh.clusterIndices.constData(),
|
||||||
|
mesh.clusterIndices.size() * sizeof(glm::vec4));
|
||||||
|
networkMesh.vertexBuffer.write(clusterWeightsOffset, mesh.clusterWeights.constData(),
|
||||||
|
mesh.clusterWeights.size() * sizeof(glm::vec4));
|
||||||
|
|
||||||
|
// if there's no springiness, then the cluster indices/weights can be static
|
||||||
|
} else if (mesh.springiness == 0.0f) {
|
||||||
|
int colorsOffset = mesh.tangents.size() * sizeof(glm::vec3);
|
||||||
|
int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
|
||||||
|
int clusterIndicesOffset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2);
|
||||||
|
int clusterWeightsOffset = clusterIndicesOffset + mesh.clusterIndices.size() * sizeof(glm::vec4);
|
||||||
|
networkMesh.vertexBuffer.allocate(clusterWeightsOffset + mesh.clusterWeights.size() * sizeof(glm::vec4));
|
||||||
|
networkMesh.vertexBuffer.write(0, mesh.tangents.constData(), mesh.tangents.size() * sizeof(glm::vec3));
|
||||||
|
networkMesh.vertexBuffer.write(colorsOffset, mesh.colors.constData(), mesh.colors.size() * sizeof(glm::vec3));
|
||||||
|
networkMesh.vertexBuffer.write(texCoordsOffset, mesh.texCoords.constData(),
|
||||||
|
mesh.texCoords.size() * sizeof(glm::vec2));
|
||||||
|
networkMesh.vertexBuffer.write(clusterIndicesOffset, mesh.clusterIndices.constData(),
|
||||||
|
mesh.clusterIndices.size() * sizeof(glm::vec4));
|
||||||
|
networkMesh.vertexBuffer.write(clusterWeightsOffset, mesh.clusterWeights.constData(),
|
||||||
|
mesh.clusterWeights.size() * sizeof(glm::vec4));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
int colorsOffset = mesh.tangents.size() * sizeof(glm::vec3);
|
||||||
|
int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
|
||||||
|
networkMesh.vertexBuffer.allocate(texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2));
|
||||||
|
networkMesh.vertexBuffer.write(0, mesh.tangents.constData(), mesh.tangents.size() * sizeof(glm::vec3));
|
||||||
|
networkMesh.vertexBuffer.write(colorsOffset, mesh.colors.constData(), mesh.colors.size() * sizeof(glm::vec3));
|
||||||
|
networkMesh.vertexBuffer.write(texCoordsOffset, mesh.texCoords.constData(),
|
||||||
|
mesh.texCoords.size() * sizeof(glm::vec2));
|
||||||
|
}
|
||||||
|
|
||||||
|
networkMesh.vertexBuffer.release();
|
||||||
|
|
||||||
|
_meshes.append(networkMesh);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkGeometry::handleReplyError() {
|
||||||
|
QDebug debug = qDebug() << _reply->errorString();
|
||||||
|
|
||||||
|
QNetworkReply::NetworkError error = _reply->error();
|
||||||
|
_reply->disconnect(this);
|
||||||
|
_reply->deleteLater();
|
||||||
|
_reply = NULL;
|
||||||
|
|
||||||
// retry for certain types of failures
|
// retry for certain types of failures
|
||||||
switch (error) {
|
switch (error) {
|
||||||
|
@ -394,164 +577,19 @@ void NetworkGeometry::handleModelReplyError() {
|
||||||
const int MAX_ATTEMPTS = 8;
|
const int MAX_ATTEMPTS = 8;
|
||||||
const int BASE_DELAY_MS = 1000;
|
const int BASE_DELAY_MS = 1000;
|
||||||
if (++_attempts < MAX_ATTEMPTS) {
|
if (++_attempts < MAX_ATTEMPTS) {
|
||||||
QTimer::singleShot(BASE_DELAY_MS * (int)pow(2.0, _attempts), this, SLOT(makeModelRequest()));
|
QTimer::singleShot(BASE_DELAY_MS * (int)pow(2.0, _attempts), this, SLOT(makeRequest()));
|
||||||
debug << " -- retrying...";
|
debug << " -- retrying...";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// fall through to final failure
|
// fall through to final failure
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
maybeLoadFallback();
|
_failedToLoad = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkGeometry::handleMappingReplyError() {
|
|
||||||
_mappingReply->disconnect(this);
|
|
||||||
_mappingReply->deleteLater();
|
|
||||||
_mappingReply = NULL;
|
|
||||||
|
|
||||||
maybeReadModelWithMapping();
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkGeometry::maybeReadModelWithMapping() {
|
|
||||||
if (_modelReply == NULL || !_modelReply->isFinished() || (_mappingReply != NULL && !_mappingReply->isFinished())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QUrl url = _modelReply->url();
|
|
||||||
QByteArray model = _modelReply->readAll();
|
|
||||||
_modelReply->disconnect(this);
|
|
||||||
_modelReply->deleteLater();
|
|
||||||
_modelReply = NULL;
|
|
||||||
|
|
||||||
QByteArray mapping;
|
|
||||||
if (_mappingReply != NULL) {
|
|
||||||
mapping = _mappingReply->readAll();
|
|
||||||
_mappingReply->disconnect(this);
|
|
||||||
_mappingReply->deleteLater();
|
|
||||||
_mappingReply = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
_geometry = url.path().toLower().endsWith(".svo") ? readSVO(model) : readFBX(model, mapping);
|
|
||||||
|
|
||||||
} catch (const QString& error) {
|
|
||||||
qDebug() << "Error reading " << url << ": " << error;
|
|
||||||
maybeLoadFallback();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (const FBXMesh& mesh, _geometry.meshes) {
|
|
||||||
NetworkMesh networkMesh;
|
|
||||||
|
|
||||||
int totalIndices = 0;
|
|
||||||
foreach (const FBXMeshPart& part, mesh.parts) {
|
|
||||||
NetworkMeshPart networkPart;
|
|
||||||
QString basePath = url.path();
|
|
||||||
basePath = basePath.left(basePath.lastIndexOf('/') + 1);
|
|
||||||
if (!part.diffuseFilename.isEmpty()) {
|
|
||||||
url.setPath(basePath + part.diffuseFilename);
|
|
||||||
networkPart.diffuseTexture = Application::getInstance()->getTextureCache()->getTexture(url, false, mesh.isEye);
|
|
||||||
}
|
|
||||||
if (!part.normalFilename.isEmpty()) {
|
|
||||||
url.setPath(basePath + part.normalFilename);
|
|
||||||
networkPart.normalTexture = Application::getInstance()->getTextureCache()->getTexture(url, true);
|
|
||||||
}
|
|
||||||
networkMesh.parts.append(networkPart);
|
|
||||||
|
|
||||||
totalIndices += (part.quadIndices.size() + part.triangleIndices.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
glGenBuffers(1, &networkMesh.indexBufferID);
|
|
||||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, networkMesh.indexBufferID);
|
|
||||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, totalIndices * sizeof(int), NULL, GL_STATIC_DRAW);
|
|
||||||
int offset = 0;
|
|
||||||
foreach (const FBXMeshPart& part, mesh.parts) {
|
|
||||||
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, part.quadIndices.size() * sizeof(int),
|
|
||||||
part.quadIndices.constData());
|
|
||||||
offset += part.quadIndices.size() * sizeof(int);
|
|
||||||
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, part.triangleIndices.size() * sizeof(int),
|
|
||||||
part.triangleIndices.constData());
|
|
||||||
offset += part.triangleIndices.size() * sizeof(int);
|
|
||||||
}
|
|
||||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
|
||||||
|
|
||||||
glGenBuffers(1, &networkMesh.vertexBufferID);
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, networkMesh.vertexBufferID);
|
|
||||||
|
|
||||||
// if we don't need to do any blending or springing, then the positions/normals can be static
|
|
||||||
if (mesh.blendshapes.isEmpty() && mesh.springiness == 0.0f) {
|
|
||||||
int normalsOffset = mesh.vertices.size() * sizeof(glm::vec3);
|
|
||||||
int tangentsOffset = normalsOffset + mesh.normals.size() * sizeof(glm::vec3);
|
|
||||||
int colorsOffset = tangentsOffset + mesh.tangents.size() * sizeof(glm::vec3);
|
|
||||||
int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
|
|
||||||
int clusterIndicesOffset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2);
|
|
||||||
int clusterWeightsOffset = clusterIndicesOffset + mesh.clusterIndices.size() * sizeof(glm::vec4);
|
|
||||||
glBufferData(GL_ARRAY_BUFFER, clusterWeightsOffset + mesh.clusterWeights.size() * sizeof(glm::vec4),
|
|
||||||
NULL, GL_STATIC_DRAW);
|
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, 0, mesh.vertices.size() * sizeof(glm::vec3), mesh.vertices.constData());
|
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, normalsOffset, mesh.normals.size() * sizeof(glm::vec3), mesh.normals.constData());
|
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, tangentsOffset, mesh.tangents.size() * sizeof(glm::vec3), mesh.tangents.constData());
|
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, colorsOffset, mesh.colors.size() * sizeof(glm::vec3), mesh.colors.constData());
|
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, texCoordsOffset, mesh.texCoords.size() * sizeof(glm::vec2),
|
|
||||||
mesh.texCoords.constData());
|
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, clusterIndicesOffset, mesh.clusterIndices.size() * sizeof(glm::vec4),
|
|
||||||
mesh.clusterIndices.constData());
|
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, clusterWeightsOffset, mesh.clusterWeights.size() * sizeof(glm::vec4),
|
|
||||||
mesh.clusterWeights.constData());
|
|
||||||
|
|
||||||
// if there's no springiness, then the cluster indices/weights can be static
|
|
||||||
} else if (mesh.springiness == 0.0f) {
|
|
||||||
int colorsOffset = mesh.tangents.size() * sizeof(glm::vec3);
|
|
||||||
int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
|
|
||||||
int clusterIndicesOffset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2);
|
|
||||||
int clusterWeightsOffset = clusterIndicesOffset + mesh.clusterIndices.size() * sizeof(glm::vec4);
|
|
||||||
glBufferData(GL_ARRAY_BUFFER, clusterWeightsOffset + mesh.clusterWeights.size() * sizeof(glm::vec4),
|
|
||||||
NULL, GL_STATIC_DRAW);
|
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, 0, mesh.tangents.size() * sizeof(glm::vec3), mesh.tangents.constData());
|
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, colorsOffset, mesh.colors.size() * sizeof(glm::vec3), mesh.colors.constData());
|
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, texCoordsOffset, mesh.texCoords.size() * sizeof(glm::vec2), mesh.texCoords.constData());
|
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, clusterIndicesOffset, mesh.clusterIndices.size() * sizeof(glm::vec4),
|
|
||||||
mesh.clusterIndices.constData());
|
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, clusterWeightsOffset, mesh.clusterWeights.size() * sizeof(glm::vec4),
|
|
||||||
mesh.clusterWeights.constData());
|
|
||||||
|
|
||||||
} else {
|
|
||||||
int colorsOffset = mesh.tangents.size() * sizeof(glm::vec3);
|
|
||||||
int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
|
|
||||||
glBufferData(GL_ARRAY_BUFFER, texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2), NULL, GL_STATIC_DRAW);
|
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, 0, mesh.tangents.size() * sizeof(glm::vec3), mesh.tangents.constData());
|
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, colorsOffset, mesh.colors.size() * sizeof(glm::vec3), mesh.colors.constData());
|
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, texCoordsOffset, mesh.texCoords.size() * sizeof(glm::vec2),
|
|
||||||
mesh.texCoords.constData());
|
|
||||||
}
|
|
||||||
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
||||||
|
|
||||||
_meshes.append(networkMesh);
|
|
||||||
}
|
|
||||||
|
|
||||||
emit loaded();
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkGeometry::loadFallback() {
|
|
||||||
_geometry = _fallback->_geometry;
|
|
||||||
_meshes = _fallback->_meshes;
|
|
||||||
emit loaded();
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkGeometry::maybeLoadFallback() {
|
|
||||||
if (_fallback) {
|
|
||||||
if (_fallback->isLoaded()) {
|
|
||||||
loadFallback();
|
|
||||||
} else {
|
|
||||||
connect(_fallback.data(), SIGNAL(loaded()), SLOT(loadFallback()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NetworkMeshPart::isTranslucent() const {
|
bool NetworkMeshPart::isTranslucent() const {
|
||||||
return diffuseTexture && diffuseTexture->isTranslucent();
|
return diffuseTexture && diffuseTexture->isTranslucent();
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,17 +9,20 @@
|
||||||
#ifndef __interface__GeometryCache__
|
#ifndef __interface__GeometryCache__
|
||||||
#define __interface__GeometryCache__
|
#define __interface__GeometryCache__
|
||||||
|
|
||||||
|
// include this before QOpenGLBuffer, which includes an earlier version of OpenGL
|
||||||
|
#include "InterfaceConfig.h"
|
||||||
|
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
|
#include <QMap>
|
||||||
#include <QNetworkRequest>
|
#include <QNetworkRequest>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QOpenGLBuffer>
|
||||||
#include <QSharedPointer>
|
#include <QSharedPointer>
|
||||||
#include <QWeakPointer>
|
#include <QWeakPointer>
|
||||||
|
|
||||||
#include "FBXReader.h"
|
#include "FBXReader.h"
|
||||||
#include "InterfaceConfig.h"
|
|
||||||
|
|
||||||
class QNetworkReply;
|
class QNetworkReply;
|
||||||
class QOpenGLBuffer;
|
|
||||||
|
|
||||||
class NetworkGeometry;
|
class NetworkGeometry;
|
||||||
class NetworkMesh;
|
class NetworkMesh;
|
||||||
|
@ -59,41 +62,52 @@ class NetworkGeometry : public QObject {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
NetworkGeometry(const QUrl& url, const QSharedPointer<NetworkGeometry>& fallback);
|
/// A hysteresis value indicating that we have no state memory.
|
||||||
|
static const float NO_HYSTERESIS;
|
||||||
|
|
||||||
|
NetworkGeometry(const QUrl& url, const QSharedPointer<NetworkGeometry>& fallback,
|
||||||
|
const QVariantHash& mapping = QVariantHash(), const QUrl& textureBase = QUrl());
|
||||||
~NetworkGeometry();
|
~NetworkGeometry();
|
||||||
|
|
||||||
|
/// Checks whether the geometry is fulled loaded.
|
||||||
bool isLoaded() const { return !_geometry.joints.isEmpty(); }
|
bool isLoaded() const { return !_geometry.joints.isEmpty(); }
|
||||||
|
|
||||||
|
/// Returns a pointer to the geometry appropriate for the specified distance.
|
||||||
|
/// \param hysteresis a hysteresis parameter that prevents rapid model switching
|
||||||
|
QSharedPointer<NetworkGeometry> getLODOrFallback(float distance, float& hysteresis) const;
|
||||||
|
|
||||||
const FBXGeometry& getFBXGeometry() const { return _geometry; }
|
const FBXGeometry& getFBXGeometry() const { return _geometry; }
|
||||||
const QVector<NetworkMesh>& getMeshes() const { return _meshes; }
|
const QVector<NetworkMesh>& getMeshes() const { return _meshes; }
|
||||||
|
|
||||||
/// Returns the average color of all meshes in the geometry.
|
/// Returns the average color of all meshes in the geometry.
|
||||||
glm::vec4 computeAverageColor() const;
|
glm::vec4 computeAverageColor() const;
|
||||||
|
|
||||||
signals:
|
|
||||||
|
|
||||||
void loaded();
|
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
|
|
||||||
void makeModelRequest();
|
void makeRequest();
|
||||||
void handleModelReplyError();
|
void handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
|
||||||
void handleMappingReplyError();
|
void handleReplyError();
|
||||||
void maybeReadModelWithMapping();
|
|
||||||
void loadFallback();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
void maybeLoadFallback();
|
friend class GeometryCache;
|
||||||
|
|
||||||
QNetworkRequest _modelRequest;
|
void setLODParent(const QWeakPointer<NetworkGeometry>& lodParent) { _lodParent = lodParent; }
|
||||||
QNetworkReply* _modelReply;
|
|
||||||
QNetworkReply* _mappingReply;
|
QNetworkRequest _request;
|
||||||
|
QNetworkReply* _reply;
|
||||||
|
QVariantHash _mapping;
|
||||||
|
QUrl _textureBase;
|
||||||
QSharedPointer<NetworkGeometry> _fallback;
|
QSharedPointer<NetworkGeometry> _fallback;
|
||||||
|
bool _startedLoading;
|
||||||
|
bool _failedToLoad;
|
||||||
|
|
||||||
int _attempts;
|
int _attempts;
|
||||||
|
QMap<float, QSharedPointer<NetworkGeometry> > _lods;
|
||||||
FBXGeometry _geometry;
|
FBXGeometry _geometry;
|
||||||
QVector<NetworkMesh> _meshes;
|
QVector<NetworkMesh> _meshes;
|
||||||
|
|
||||||
|
QWeakPointer<NetworkGeometry> _lodParent;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The state associated with a single mesh part.
|
/// The state associated with a single mesh part.
|
||||||
|
@ -110,8 +124,8 @@ public:
|
||||||
class NetworkMesh {
|
class NetworkMesh {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
GLuint indexBufferID;
|
QOpenGLBuffer indexBuffer;
|
||||||
GLuint vertexBufferID;
|
QOpenGLBuffer vertexBuffer;
|
||||||
|
|
||||||
QVector<NetworkMeshPart> parts;
|
QVector<NetworkMeshPart> parts;
|
||||||
|
|
||||||
|
|
|
@ -90,6 +90,16 @@ void Model::reset() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Model::simulate(float deltaTime) {
|
void Model::simulate(float deltaTime) {
|
||||||
|
// update our LOD
|
||||||
|
if (_geometry) {
|
||||||
|
QSharedPointer<NetworkGeometry> geometry = _geometry->getLODOrFallback(glm::distance(_translation,
|
||||||
|
Application::getInstance()->getCamera()->getPosition()), _lodHysteresis);
|
||||||
|
if (_geometry != geometry) {
|
||||||
|
deleteGeometry();
|
||||||
|
_dilatedTextures.clear();
|
||||||
|
_geometry = geometry;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!isActive()) {
|
if (!isActive()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -409,8 +419,9 @@ void Model::setURL(const QUrl& url, const QUrl& fallback) {
|
||||||
// delete our local geometry and custom textures
|
// delete our local geometry and custom textures
|
||||||
deleteGeometry();
|
deleteGeometry();
|
||||||
_dilatedTextures.clear();
|
_dilatedTextures.clear();
|
||||||
|
_lodHysteresis = NetworkGeometry::NO_HYSTERESIS;
|
||||||
|
|
||||||
_geometry = Application::getInstance()->getGeometryCache()->getGeometry(url, fallback);
|
_baseGeometry = _geometry = Application::getInstance()->getGeometryCache()->getGeometry(url, fallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
glm::vec4 Model::computeAverageColor() const {
|
glm::vec4 Model::computeAverageColor() const {
|
||||||
|
@ -791,13 +802,17 @@ void Model::renderMeshes(float alpha, bool translucent) {
|
||||||
(networkMesh.getTranslucentPartCount() == networkMesh.parts.size())) {
|
(networkMesh.getTranslucentPartCount() == networkMesh.parts.size())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, networkMesh.indexBufferID);
|
const_cast<QOpenGLBuffer&>(networkMesh.indexBuffer).bind();
|
||||||
|
|
||||||
const FBXMesh& mesh = geometry.meshes.at(i);
|
const FBXMesh& mesh = geometry.meshes.at(i);
|
||||||
int vertexCount = mesh.vertices.size();
|
int vertexCount = mesh.vertices.size();
|
||||||
|
if (vertexCount == 0) {
|
||||||
|
// sanity check
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const_cast<QOpenGLBuffer&>(networkMesh.vertexBuffer).bind();
|
||||||
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, networkMesh.vertexBufferID);
|
|
||||||
|
|
||||||
ProgramObject* program = &_program;
|
ProgramObject* program = &_program;
|
||||||
ProgramObject* skinProgram = &_skinProgram;
|
ProgramObject* skinProgram = &_skinProgram;
|
||||||
SkinLocations* skinLocations = &_skinLocations;
|
SkinLocations* skinLocations = &_skinLocations;
|
||||||
|
@ -904,11 +919,11 @@ void Model::renderMeshes(float alpha, bool translucent) {
|
||||||
qint64 offset = 0;
|
qint64 offset = 0;
|
||||||
for (int j = 0; j < networkMesh.parts.size(); j++) {
|
for (int j = 0; j < networkMesh.parts.size(); j++) {
|
||||||
const NetworkMeshPart& networkPart = networkMesh.parts.at(j);
|
const NetworkMeshPart& networkPart = networkMesh.parts.at(j);
|
||||||
|
const FBXMeshPart& part = mesh.parts.at(j);
|
||||||
if (networkPart.isTranslucent() != translucent) {
|
if (networkPart.isTranslucent() != translucent) {
|
||||||
|
offset += (part.quadIndices.size() + part.triangleIndices.size()) * sizeof(int);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const FBXMeshPart& part = mesh.parts.at(j);
|
|
||||||
|
|
||||||
// apply material properties
|
// apply material properties
|
||||||
glm::vec4 diffuse = glm::vec4(part.diffuseColor, alpha);
|
glm::vec4 diffuse = glm::vec4(part.diffuseColor, alpha);
|
||||||
glm::vec4 specular = glm::vec4(part.specularColor, alpha);
|
glm::vec4 specular = glm::vec4(part.specularColor, alpha);
|
||||||
|
|
|
@ -236,6 +236,9 @@ private:
|
||||||
void deleteGeometry();
|
void deleteGeometry();
|
||||||
void renderMeshes(float alpha, bool translucent);
|
void renderMeshes(float alpha, bool translucent);
|
||||||
|
|
||||||
|
QSharedPointer<NetworkGeometry> _baseGeometry;
|
||||||
|
float _lodHysteresis;
|
||||||
|
|
||||||
float _pupilDilation;
|
float _pupilDilation;
|
||||||
std::vector<float> _blendshapeCoefficients;
|
std::vector<float> _blendshapeCoefficients;
|
||||||
|
|
||||||
|
|
|
@ -52,8 +52,8 @@ static const float MIN_AVATAR_SCALE = .005f;
|
||||||
|
|
||||||
const float MAX_AUDIO_LOUDNESS = 1000.0; // close enough for mouth animation
|
const float MAX_AUDIO_LOUDNESS = 1000.0; // close enough for mouth animation
|
||||||
|
|
||||||
const QUrl DEFAULT_HEAD_MODEL_URL = QUrl("http://public.highfidelity.io/meshes/defaultAvatar_head.fbx");
|
const QUrl DEFAULT_HEAD_MODEL_URL = QUrl("http://public.highfidelity.io/meshes/defaultAvatar_head.fst");
|
||||||
const QUrl DEFAULT_BODY_MODEL_URL = QUrl("http://public.highfidelity.io/meshes/defaultAvatar_body.fbx");
|
const QUrl DEFAULT_BODY_MODEL_URL = QUrl("http://public.highfidelity.io/meshes/defaultAvatar_body.fst");
|
||||||
|
|
||||||
enum KeyState {
|
enum KeyState {
|
||||||
NO_KEY_DOWN = 0,
|
NO_KEY_DOWN = 0,
|
||||||
|
|
Loading…
Reference in a new issue