mirror of
https://github.com/overte-org/overte.git
synced 2025-04-25 15:14:19 +02:00
Merge branch 'master' of https://github.com/worklist/hifi into voxel_render_pipeline_improvement
This commit is contained in:
commit
2450d64016
4 changed files with 173 additions and 88 deletions
|
@ -19,12 +19,6 @@
|
|||
|
||||
using namespace std;
|
||||
|
||||
FBXNode parseFBX(const QByteArray& data) {
|
||||
QBuffer buffer(const_cast<QByteArray*>(&data));
|
||||
buffer.open(QIODevice::ReadOnly);
|
||||
return parseFBX(&buffer);
|
||||
}
|
||||
|
||||
template<class T> QVariant readArray(QDataStream& in) {
|
||||
quint32 arrayLength;
|
||||
quint32 encoding;
|
||||
|
@ -189,6 +183,41 @@ FBXNode parseFBX(QIODevice* device) {
|
|||
return top;
|
||||
}
|
||||
|
||||
QVariantHash parseMapping(QIODevice* device) {
|
||||
QVariantHash properties;
|
||||
|
||||
QByteArray line;
|
||||
while (!(line = device->readLine()).isEmpty()) {
|
||||
if ((line = line.trimmed()).startsWith('#')) {
|
||||
continue; // comment
|
||||
}
|
||||
QList<QByteArray> sections = line.split('=');
|
||||
if (sections.size() < 2) {
|
||||
continue;
|
||||
}
|
||||
QByteArray name = sections.at(0).trimmed();
|
||||
if (sections.size() == 2) {
|
||||
properties.insert(name, sections.at(1).trimmed());
|
||||
|
||||
} else if (sections.size() == 3) {
|
||||
QVariantHash heading = properties.value(name).toHash();
|
||||
heading.insert(sections.at(1).trimmed(), sections.at(2).trimmed());
|
||||
properties.insert(name, heading);
|
||||
|
||||
} else if (sections.size() >= 4) {
|
||||
QVariantHash heading = properties.value(name).toHash();
|
||||
QVariantList contents;
|
||||
for (int i = 2; i < sections.size(); i++) {
|
||||
contents.append(sections.at(i).trimmed());
|
||||
}
|
||||
heading.insertMulti(sections.at(1).trimmed(), contents);
|
||||
properties.insert(name, heading);
|
||||
}
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
QVector<glm::vec3> createVec3Vector(const QVector<double>& doubleVector) {
|
||||
QVector<glm::vec3> values;
|
||||
for (const double* it = doubleVector.constData(), *end = it + doubleVector.size(); it != end; ) {
|
||||
|
@ -269,19 +298,6 @@ const char* FACESHIFT_BLENDSHAPES[] = {
|
|||
""
|
||||
};
|
||||
|
||||
QHash<QByteArray, int> createBlendshapeMap() {
|
||||
QHash<QByteArray, int> map;
|
||||
for (int i = 0;; i++) {
|
||||
QByteArray name = FACESHIFT_BLENDSHAPES[i];
|
||||
if (name != "") {
|
||||
map.insert(name, i);
|
||||
|
||||
} else {
|
||||
return map;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Transform {
|
||||
public:
|
||||
bool inheritScale;
|
||||
|
@ -315,11 +331,10 @@ glm::mat4 getGlobalTransform(const QMultiHash<qint64, qint64>& parentMap, const
|
|||
class ExtractedBlendshape {
|
||||
public:
|
||||
qint64 id;
|
||||
int index;
|
||||
FBXBlendshape blendshape;
|
||||
};
|
||||
|
||||
FBXGeometry extractFBXGeometry(const FBXNode& node) {
|
||||
FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) {
|
||||
QHash<qint64, FBXMesh> meshes;
|
||||
QVector<ExtractedBlendshape> blendshapes;
|
||||
QMultiHash<qint64, qint64> parentMap;
|
||||
|
@ -329,10 +344,35 @@ FBXGeometry extractFBXGeometry(const FBXNode& node) {
|
|||
QHash<qint64, QByteArray> textureFilenames;
|
||||
QHash<qint64, qint64> diffuseTextures;
|
||||
QHash<qint64, qint64> bumpTextures;
|
||||
|
||||
QVariantHash joints = mapping.value("joint").toHash();
|
||||
QByteArray jointEyeLeftName = joints.value("jointEyeLeft", "jointEyeLeft").toByteArray();
|
||||
QByteArray jointEyeRightName = joints.value("jointEyeRight", "jointEyeRight").toByteArray();
|
||||
QByteArray jointNeckName = joints.value("jointNeck", "jointNeck").toByteArray();
|
||||
qint64 jointEyeLeftID = 0;
|
||||
qint64 jointEyeRightID = 0;
|
||||
qint64 jointNeckID = 0;
|
||||
|
||||
QVariantHash blendshapeMappings = mapping.value("bs").toHash();
|
||||
QHash<QByteArray, QPair<int, float> > blendshapeIndices;
|
||||
for (int i = 0;; i++) {
|
||||
QByteArray blendshapeName = FACESHIFT_BLENDSHAPES[i];
|
||||
if (blendshapeName.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
QList<QVariant> mappings = blendshapeMappings.values(blendshapeName);
|
||||
if (mappings.isEmpty()) {
|
||||
blendshapeIndices.insert("ExpressionBlendshapes." + blendshapeName, QPair<int, float>(i, 1.0f));
|
||||
} else {
|
||||
foreach (const QVariant& mapping, mappings) {
|
||||
QVariantList blendshapeMapping = mapping.toList();
|
||||
blendshapeIndices.insert(blendshapeMapping.at(0).toByteArray(),
|
||||
QPair<int, float>(i, blendshapeMapping.at(1).toFloat()));
|
||||
}
|
||||
}
|
||||
}
|
||||
QHash<qint64, QPair<int, float> > blendshapeChannelIndices;
|
||||
|
||||
foreach (const FBXNode& child, node.children) {
|
||||
if (child.name == "Objects") {
|
||||
foreach (const FBXNode& object, child.children) {
|
||||
|
@ -387,14 +427,16 @@ FBXGeometry extractFBXGeometry(const FBXNode& node) {
|
|||
}
|
||||
|
||||
// same with the tex coords
|
||||
mesh.texCoords.resize(mesh.vertices.size());
|
||||
for (int i = 0, n = polygonIndices.size(); i < n; i++) {
|
||||
int index = polygonIndices.at(i);
|
||||
int texCoordIndex = texCoordIndices.at(i);
|
||||
if (texCoordIndex >= 0) {
|
||||
mesh.texCoords[index < 0 ? (-index - 1) : index] = texCoords.at(texCoordIndex);
|
||||
}
|
||||
}
|
||||
if (!texCoordIndices.isEmpty()) {
|
||||
mesh.texCoords.resize(mesh.vertices.size());
|
||||
for (int i = 0, n = polygonIndices.size(); i < n; i++) {
|
||||
int index = polygonIndices.at(i);
|
||||
int texCoordIndex = texCoordIndices.at(i);
|
||||
if (texCoordIndex >= 0) {
|
||||
mesh.texCoords[index < 0 ? (-index - 1) : index] = texCoords.at(texCoordIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// convert the polygons to quads and triangles
|
||||
for (const int* beginIndex = polygonIndices.constData(), *end = beginIndex + polygonIndices.size();
|
||||
|
@ -441,22 +483,18 @@ FBXGeometry extractFBXGeometry(const FBXNode& node) {
|
|||
}
|
||||
}
|
||||
|
||||
// the name is followed by a null and some type info
|
||||
QByteArray name = object.properties.at(1).toByteArray();
|
||||
static QHash<QByteArray, int> blendshapeMap = createBlendshapeMap();
|
||||
extracted.index = blendshapeMap.value(name.left(name.indexOf('\0')));
|
||||
|
||||
blendshapes.append(extracted);
|
||||
}
|
||||
} else if (object.name == "Model") {
|
||||
QByteArray name = object.properties.at(1).toByteArray();
|
||||
if (name.startsWith("jointEyeLeft") || name.startsWith("EyeL") || name.startsWith("joint_Leye")) {
|
||||
name = name.left(name.indexOf('\0'));
|
||||
if (name == jointEyeLeftName || name == "EyeL" || name == "joint_Leye") {
|
||||
jointEyeLeftID = object.properties.at(0).value<qint64>();
|
||||
|
||||
} else if (name.startsWith("jointEyeRight") || name.startsWith("EyeR") || name.startsWith("joint_Reye")) {
|
||||
} else if (name == jointEyeRightName || name == "EyeR" || name == "joint_Reye") {
|
||||
jointEyeRightID = object.properties.at(0).value<qint64>();
|
||||
|
||||
} else if (name.startsWith("jointNeck") || name.startsWith("NeckRot") || name.startsWith("joint_neck")) {
|
||||
} else if (name == jointNeckName || name == "NeckRot" || name == "joint_neck") {
|
||||
jointNeckID = object.properties.at(0).value<qint64>();
|
||||
}
|
||||
glm::vec3 translation;
|
||||
|
@ -526,12 +564,18 @@ FBXGeometry extractFBXGeometry(const FBXNode& node) {
|
|||
subobject.properties.at(0).toByteArray());
|
||||
}
|
||||
}
|
||||
} else if (object.name == "Deformer" && object.properties.at(2) == "Cluster") {
|
||||
foreach (const FBXNode& subobject, object.children) {
|
||||
if (subobject.name == "TransformLink") {
|
||||
QVector<double> values = subobject.properties.at(0).value<QVector<double> >();
|
||||
transformLinkMatrices.insert(object.properties.at(0).value<qint64>(), createMat4(values));
|
||||
} else if (object.name == "Deformer") {
|
||||
if (object.properties.at(2) == "Cluster") {
|
||||
foreach (const FBXNode& subobject, object.children) {
|
||||
if (subobject.name == "TransformLink") {
|
||||
QVector<double> values = subobject.properties.at(0).value<QVector<double> >();
|
||||
transformLinkMatrices.insert(object.properties.at(0).value<qint64>(), createMat4(values));
|
||||
}
|
||||
}
|
||||
} else if (object.properties.at(2) == "BlendShapeChannel") {
|
||||
QByteArray name = object.properties.at(1).toByteArray();
|
||||
blendshapeChannelIndices.insert(object.properties.at(0).value<qint64>(),
|
||||
blendshapeIndices.value(name.left(name.indexOf('\0'))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -558,13 +602,21 @@ FBXGeometry extractFBXGeometry(const FBXNode& node) {
|
|||
// assign the blendshapes to their corresponding meshes
|
||||
foreach (const ExtractedBlendshape& extracted, blendshapes) {
|
||||
qint64 blendshapeChannelID = parentMap.value(extracted.id);
|
||||
QPair<int, float> index = blendshapeChannelIndices.value(blendshapeChannelID);
|
||||
qint64 blendshapeID = parentMap.value(blendshapeChannelID);
|
||||
qint64 meshID = parentMap.value(blendshapeID);
|
||||
FBXMesh& mesh = meshes[meshID];
|
||||
mesh.blendshapes.resize(max(mesh.blendshapes.size(), extracted.index + 1));
|
||||
mesh.blendshapes[extracted.index] = extracted.blendshape;
|
||||
mesh.blendshapes.resize(max(mesh.blendshapes.size(), index.first + 1));
|
||||
mesh.blendshapes[index.first] = extracted.blendshape;
|
||||
}
|
||||
|
||||
// get offset transform from mapping
|
||||
float offsetScale = mapping.value("scale", 1.0f).toFloat();
|
||||
glm::mat4 offset = glm::translate(mapping.value("tx").toFloat(), mapping.value("ty").toFloat(),
|
||||
mapping.value("tz").toFloat()) * glm::mat4_cast(glm::quat(glm::radians(glm::vec3(mapping.value("rx").toFloat(),
|
||||
mapping.value("ry").toFloat(), mapping.value("rz").toFloat())))) *
|
||||
glm::scale(offsetScale, offsetScale, offsetScale);
|
||||
|
||||
// as a temporary hack, put the mesh with the most blendshapes on top; assume it to be the face
|
||||
FBXGeometry geometry;
|
||||
int mostBlendshapes = 0;
|
||||
|
@ -601,11 +653,11 @@ FBXGeometry extractFBXGeometry(const FBXNode& node) {
|
|||
|
||||
// see http://stackoverflow.com/questions/13566608/loading-skinning-information-from-fbx for a discussion
|
||||
// of skinning information in FBX
|
||||
glm::mat4 jointTransform = getGlobalTransform(parentMap, localTransforms, jointID);
|
||||
glm::mat4 jointTransform = offset * getGlobalTransform(parentMap, localTransforms, jointID);
|
||||
mesh.transform = jointTransform * glm::inverse(transformLinkMatrices.value(clusterID)) * modelTransform;
|
||||
|
||||
// extract translation component for pivot
|
||||
glm::mat4 jointTransformScaled = getGlobalTransform(parentMap, localTransforms, jointID, true);
|
||||
glm::mat4 jointTransformScaled = offset * getGlobalTransform(parentMap, localTransforms, jointID, true);
|
||||
mesh.pivot = glm::vec3(jointTransformScaled[3][0], jointTransformScaled[3][1], jointTransformScaled[3][2]);
|
||||
}
|
||||
}
|
||||
|
@ -620,7 +672,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node) {
|
|||
}
|
||||
|
||||
// extract translation component for neck pivot
|
||||
glm::mat4 neckTransform = getGlobalTransform(parentMap, localTransforms, jointNeckID, true);
|
||||
glm::mat4 neckTransform = offset * getGlobalTransform(parentMap, localTransforms, jointNeckID, true);
|
||||
geometry.neckPivot = glm::vec3(neckTransform[3][0], neckTransform[3][1], neckTransform[3][2]);
|
||||
|
||||
return geometry;
|
||||
|
@ -638,3 +690,13 @@ void printNode(const FBXNode& node, int indent) {
|
|||
}
|
||||
}
|
||||
|
||||
FBXGeometry readFBX(const QByteArray& model, const QByteArray& mapping) {
|
||||
QBuffer modelBuffer(const_cast<QByteArray*>(&model));
|
||||
modelBuffer.open(QIODevice::ReadOnly);
|
||||
|
||||
QBuffer mappingBuffer(const_cast<QByteArray*>(&mapping));
|
||||
mappingBuffer.open(QIODevice::ReadOnly);
|
||||
|
||||
return extractFBXGeometry(parseFBX(&modelBuffer), parseMapping(&mappingBuffer));
|
||||
}
|
||||
|
||||
|
|
|
@ -14,8 +14,6 @@
|
|||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
class QIODevice;
|
||||
|
||||
class FBXNode;
|
||||
|
||||
typedef QList<FBXNode> FBXNodeList;
|
||||
|
@ -68,17 +66,8 @@ public:
|
|||
glm::vec3 neckPivot;
|
||||
};
|
||||
|
||||
/// Parses the input from the supplied data as an FBX file.
|
||||
/// Reads FBX geometry from the supplied model and mapping data.
|
||||
/// \exception QString if an error occurs in parsing
|
||||
FBXNode parseFBX(const QByteArray& data);
|
||||
|
||||
/// Parses the input from the supplied device as an FBX file.
|
||||
/// \exception QString if an error occurs in parsing
|
||||
FBXNode parseFBX(QIODevice* device);
|
||||
|
||||
/// Extracts the geometry from a parsed FBX node.
|
||||
FBXGeometry extractFBXGeometry(const FBXNode& node);
|
||||
|
||||
void printNode(const FBXNode& node, int indent = 0);
|
||||
FBXGeometry readFBX(const QByteArray& model, const QByteArray& mapping);
|
||||
|
||||
#endif /* defined(__interface__FBXReader__) */
|
||||
|
|
|
@ -250,21 +250,37 @@ QSharedPointer<NetworkGeometry> GeometryCache::getGeometry(const QUrl& url) {
|
|||
return geometry;
|
||||
}
|
||||
|
||||
NetworkGeometry::NetworkGeometry(const QUrl& url) : _reply(NULL) {
|
||||
NetworkGeometry::NetworkGeometry(const QUrl& url) :
|
||||
_modelReply(NULL),
|
||||
_mappingReply(NULL)
|
||||
{
|
||||
if (!url.isValid()) {
|
||||
return;
|
||||
}
|
||||
QNetworkRequest request(url);
|
||||
request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
|
||||
_reply = Application::getInstance()->getNetworkAccessManager()->get(request);
|
||||
QNetworkRequest modelRequest(url);
|
||||
modelRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
|
||||
_modelReply = Application::getInstance()->getNetworkAccessManager()->get(modelRequest);
|
||||
|
||||
connect(_reply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(handleDownloadProgress(qint64,qint64)));
|
||||
connect(_reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleReplyError()));
|
||||
connect(_modelReply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(maybeReadModelWithMapping()));
|
||||
connect(_modelReply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleModelReplyError()));
|
||||
|
||||
QUrl mappingURL = url;
|
||||
QString path = url.path();
|
||||
mappingURL.setPath(path.left(path.lastIndexOf('.')) + ".fst");
|
||||
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() {
|
||||
if (_reply != NULL) {
|
||||
delete _reply;
|
||||
if (_modelReply != NULL) {
|
||||
delete _modelReply;
|
||||
}
|
||||
if (_mappingReply != NULL) {
|
||||
delete _mappingReply;
|
||||
}
|
||||
foreach (const NetworkMesh& mesh, _meshes) {
|
||||
glDeleteBuffers(1, &mesh.indexBufferID);
|
||||
|
@ -272,19 +288,43 @@ NetworkGeometry::~NetworkGeometry() {
|
|||
}
|
||||
}
|
||||
|
||||
void NetworkGeometry::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) {
|
||||
if (bytesReceived < bytesTotal && !_reply->isFinished()) {
|
||||
void NetworkGeometry::handleModelReplyError() {
|
||||
qDebug() << _modelReply->errorString() << "\n";
|
||||
|
||||
_modelReply->disconnect(this);
|
||||
_modelReply->deleteLater();
|
||||
_modelReply = NULL;
|
||||
}
|
||||
|
||||
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 = _reply->url();
|
||||
QByteArray entirety = _reply->readAll();
|
||||
_reply->disconnect(this);
|
||||
_reply->deleteLater();
|
||||
_reply = NULL;
|
||||
|
||||
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 = extractFBXGeometry(parseFBX(entirety));
|
||||
_geometry = readFBX(model, mapping);
|
||||
|
||||
} catch (const QString& error) {
|
||||
qDebug() << "Error reading " << url << ": " << error << "\n";
|
||||
|
@ -338,11 +378,3 @@ void NetworkGeometry::handleDownloadProgress(qint64 bytesReceived, qint64 bytesT
|
|||
_meshes.append(networkMesh);
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkGeometry::handleReplyError() {
|
||||
qDebug() << _reply->errorString() << "\n";
|
||||
|
||||
_reply->disconnect(this);
|
||||
_reply->deleteLater();
|
||||
_reply = NULL;
|
||||
}
|
||||
|
|
|
@ -64,12 +64,14 @@ public:
|
|||
|
||||
private slots:
|
||||
|
||||
void handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
|
||||
void handleReplyError();
|
||||
void handleModelReplyError();
|
||||
void handleMappingReplyError();
|
||||
void maybeReadModelWithMapping();
|
||||
|
||||
private:
|
||||
|
||||
QNetworkReply* _reply;
|
||||
QNetworkReply* _modelReply;
|
||||
QNetworkReply* _mappingReply;
|
||||
|
||||
FBXGeometry _geometry;
|
||||
QVector<NetworkMesh> _meshes;
|
||||
|
|
Loading…
Reference in a new issue