Working on skeleton loading, added support for FBX text format (exported by

Blender).
This commit is contained in:
Andrzej Kapolka 2013-10-17 15:45:57 -07:00
parent e63923774f
commit 9ba3c3719f
9 changed files with 287 additions and 23 deletions

View file

@ -805,6 +805,11 @@ void Menu::editPreferences() {
faceURLEdit->setMinimumWidth(QLINE_MINIMUM_WIDTH);
form->addRow("Face URL:", faceURLEdit);
QString skeletonURLString = applicationInstance->getProfile()->getSkeletonModelURL().toString();
QLineEdit* skeletonURLEdit = new QLineEdit(skeletonURLString);
skeletonURLEdit->setMinimumWidth(QLINE_MINIMUM_WIDTH);
form->addRow("Skeleton URL:", skeletonURLEdit);
QSlider* pupilDilation = new QSlider(Qt::Horizontal);
pupilDilation->setValue(applicationInstance->getAvatar()->getHead().getPupilDilation() * pupilDilation->maximum());
form->addRow("Pupil Dilation:", pupilDilation);

View file

@ -84,6 +84,7 @@ void Avatar::sendAvatarURLsMessage(const QUrl& voxelURL) {
Avatar::Avatar(Node* owningNode) :
AvatarData(owningNode),
_head(this),
_body(this),
_hand(this),
_ballSpringsInitialized(false),
_bodyYawDelta(0.0f),
@ -420,6 +421,7 @@ void Avatar::simulate(float deltaTime, Transmitter* transmitter) {
_head.setPosition(_bodyBall[ BODY_BALL_HEAD_BASE ].position);
_head.setSkinColor(glm::vec3(SKIN_COLOR[0], SKIN_COLOR[1], SKIN_COLOR[2]));
_head.simulate(deltaTime, false);
_body.simulate(deltaTime);
_hand.simulate(deltaTime, false);
// use speed and angular velocity to determine walking vs. standing

View file

@ -18,6 +18,7 @@
#include "AvatarTouch.h"
#include "AvatarVoxelSystem.h"
#include "Balls.h"
#include "Body.h"
#include "Hand.h"
#include "Head.h"
#include "InterfaceConfig.h"
@ -150,8 +151,9 @@ public:
const glm::vec3& getHeadJointPosition() const { return _skeleton.joint[ AVATAR_JOINT_HEAD_BASE ].position; }
float getScale() const { return _scale; }
const glm::vec3& getVelocity() const { return _velocity; }
Head& getHead() {return _head; }
Hand& getHand() {return _hand; }
Head& getHead() { return _head; }
Body& getBody() { return _body; }
Hand& getHand() { return _hand; }
glm::quat getOrientation() const;
glm::quat getWorldAlignedOrientation() const;
AvatarVoxelSystem* getVoxels() { return &_voxels; }
@ -196,6 +198,7 @@ protected:
};
Head _head;
Body _body;
Hand _hand;
Skeleton _skeleton;
bool _ballSpringsInitialized;

View file

@ -0,0 +1,66 @@
//
// Body.cpp
// interface
//
// Created by Andrzej Kapolka on 10/17/13.
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
//
#include <glm/gtx/transform.hpp>
#include "Application.h"
#include "Body.h"
Body::Body(Avatar* owningAvatar) : _owningAvatar(owningAvatar) {
// we may have been created in the network thread, but we live in the main thread
moveToThread(Application::getInstance()->thread());
}
void Body::simulate(float deltaTime) {
if (!isActive()) {
return;
}
// set up joint states on first simulate after load
const FBXGeometry& geometry = _skeletonGeometry->getFBXGeometry();
if (_jointStates.isEmpty()) {
foreach (const FBXJoint& joint, geometry.joints) {
JointState state;
state.rotation = joint.rotation;
_jointStates.append(state);
}
}
glm::quat orientation = _owningAvatar->getOrientation();
const float MODEL_SCALE = 0.0006f;
glm::vec3 scale = glm::vec3(-1.0f, 1.0f, -1.0f) * _owningAvatar->getScale() * MODEL_SCALE;
glm::mat4 baseTransform = glm::translate(_owningAvatar->getPosition()) * glm::mat4_cast(orientation) * glm::scale(scale);
// update the world space transforms for all joints
for (int i = 0; i < _jointStates.size(); i++) {
JointState& state = _jointStates[i];
const FBXJoint& joint = geometry.joints.at(i);
if (joint.parentIndex == -1) {
state.transform = baseTransform * geometry.offset * joint.preRotation *
glm::mat4_cast(state.rotation) * joint.postRotation;
} else {
state.transform = _jointStates[joint.parentIndex].transform * joint.preRotation *
glm::mat4_cast(state.rotation) * joint.postRotation;
}
}
}
bool Body::render(float alpha) {
return false;
}
void Body::setSkeletonModelURL(const QUrl& url) {
// don't recreate the geometry if it's the same URL
if (_skeletonModelURL == url) {
return;
}
_skeletonModelURL = url;
_skeletonGeometry = Application::getInstance()->getGeometryCache()->getGeometry(url);
}

View file

@ -0,0 +1,50 @@
//
// Body.h
// interface
//
// Created by Andrzej Kapolka on 10/17/13.
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
//
#ifndef __interface__Body__
#define __interface__Body__
#include <QObject>
#include <QUrl>
#include "renderer/GeometryCache.h"
/// An avatar body with an arbitrary skeleton.
class Body : public QObject {
Q_OBJECT
public:
Body(Avatar* owningAvatar);
bool isActive() const { return _skeletonGeometry && _skeletonGeometry->isLoaded(); }
void simulate(float deltaTime);
bool render(float alpha);
Q_INVOKABLE void setSkeletonModelURL(const QUrl& url);
const QUrl& getSkeletonModelURL() const { return _skeletonModelURL; }
private:
Avatar* _owningAvatar;
QUrl _skeletonModelURL;
QSharedPointer<NetworkGeometry> _skeletonGeometry;
class JointState {
public:
glm::quat rotation;
glm::mat4 transform;
};
QVector<JointState> _jointStates;
};
#endif /* defined(__interface__Body__) */

View file

@ -49,6 +49,14 @@ void Profile::setFaceModelURL(const QUrl& faceModelURL) {
Q_ARG(QUrl, _faceModelURL));
}
void Profile::setSkeletonModelURL(const QUrl& skeletonModelURL) {
_skeletonModelURL = skeletonModelURL;
QMetaObject::invokeMethod(&Application::getInstance()->getAvatar()->getBody(),
"setSkeletonModelURL",
Q_ARG(QUrl, _skeletonModelURL));
}
void Profile::updateDomain(const QString& domain) {
if (_lastDomain != domain) {
_lastDomain = domain;
@ -91,6 +99,7 @@ void Profile::saveData(QSettings* settings) {
settings->setValue("username", _username);
settings->setValue("UUID", _uuid);
settings->setValue("faceModelURL", _faceModelURL);
settings->setValue("skeletonModelURL", _skeletonModelURL);
settings->endGroup();
}
@ -101,6 +110,7 @@ void Profile::loadData(QSettings* settings) {
_username = settings->value("username").toString();
this->setUUID(settings->value("UUID").toUuid());
_faceModelURL = settings->value("faceModelURL").toUrl();
_skeletonModelURL = settings->value("skeletonModelURL").toUrl();
settings->endGroup();
}
}

View file

@ -29,6 +29,9 @@ public:
void setFaceModelURL(const QUrl& faceModelURL);
const QUrl& getFaceModelURL() const { return _faceModelURL; }
void setSkeletonModelURL(const QUrl& skeletonModelURL);
const QUrl& getSkeletonModelURL() const { return _skeletonModelURL; }
void updateDomain(const QString& domain);
void updatePosition(const glm::vec3 position);
@ -43,6 +46,7 @@ private:
QString _lastDomain;
glm::vec3 _lastPosition;
QUrl _faceModelURL;
QUrl _skeletonModelURL;
};
#endif /* defined(__hifi__Profile__) */

View file

@ -9,6 +9,7 @@
#include <QBuffer>
#include <QDataStream>
#include <QIODevice>
#include <QTextStream>
#include <QtDebug>
#include <QtEndian>
@ -19,7 +20,7 @@
using namespace std;
template<class T> QVariant readArray(QDataStream& in) {
template<class T> QVariant readBinaryArray(QDataStream& in) {
quint32 arrayLength;
quint32 encoding;
quint32 compressedLength;
@ -54,7 +55,7 @@ template<class T> QVariant readArray(QDataStream& in) {
return QVariant::fromValue(values);
}
QVariant parseFBXProperty(QDataStream& in) {
QVariant parseBinaryFBXProperty(QDataStream& in) {
char ch;
in.device()->getChar(&ch);
switch (ch) {
@ -89,19 +90,19 @@ QVariant parseFBXProperty(QDataStream& in) {
return QVariant::fromValue(value);
}
case 'f': {
return readArray<float>(in);
return readBinaryArray<float>(in);
}
case 'd': {
return readArray<double>(in);
return readBinaryArray<double>(in);
}
case 'l': {
return readArray<qint64>(in);
return readBinaryArray<qint64>(in);
}
case 'i': {
return readArray<qint32>(in);
return readBinaryArray<qint32>(in);
}
case 'b': {
return readArray<bool>(in);
return readBinaryArray<bool>(in);
}
case 'S':
case 'R': {
@ -114,7 +115,7 @@ QVariant parseFBXProperty(QDataStream& in) {
}
}
FBXNode parseFBXNode(QDataStream& in) {
FBXNode parseBinaryFBXNode(QDataStream& in) {
quint32 endOffset;
quint32 propertyCount;
quint32 propertyListLength;
@ -134,11 +135,11 @@ FBXNode parseFBXNode(QDataStream& in) {
node.name = in.device()->read(nameLength);
for (int i = 0; i < propertyCount; i++) {
node.properties.append(parseFBXProperty(in));
node.properties.append(parseBinaryFBXProperty(in));
}
while (endOffset > in.device()->pos()) {
FBXNode child = parseFBXNode(in);
FBXNode child = parseBinaryFBXNode(in);
if (child.name.isNull()) {
return node;
@ -150,28 +151,149 @@ FBXNode parseFBXNode(QDataStream& in) {
return node;
}
class Tokenizer {
public:
Tokenizer(QIODevice* device) : _device(device), _pushedBackToken(-1) { }
enum SpecialToken { DATUM_TOKEN = 0x100 };
int nextToken();
const QByteArray& getDatum() const { return _datum; }
void pushBackToken(int token) { _pushedBackToken = token; }
private:
QIODevice* _device;
QByteArray _datum;
int _pushedBackToken;
};
int Tokenizer::nextToken() {
if (_pushedBackToken != -1) {
int token = _pushedBackToken;
_pushedBackToken = -1;
return token;
}
char ch;
while (_device->getChar(&ch)) {
if (QChar(ch).isSpace()) {
continue; // skip whitespace
}
switch (ch) {
case ';':
_device->readLine(); // skip the comment
break;
case ':':
case '{':
case '}':
case ',':
return ch; // special punctuation
case '\"':
_datum = "";
while (_device->getChar(&ch)) {
if (ch == '\"') { // end on closing quote
break;
}
if (ch == '\\') { // handle escaped quotes
if (_device->getChar(&ch) && ch != '\"') {
_datum.append('\\');
}
}
_datum.append(ch);
}
return DATUM_TOKEN;
default:
_datum = "";
_datum.append(ch);
while (_device->getChar(&ch)) {
if (QChar(ch).isSpace() || ch == ';' || ch == ':' || ch == '{' || ch == '}' || ch == ',' || ch == '\"') {
_device->ungetChar(ch); // read until we encounter a special character, then replace it
break;
}
_datum.append(ch);
}
return DATUM_TOKEN;
}
}
return -1;
}
FBXNode parseTextFBXNode(Tokenizer& tokenizer) {
FBXNode node;
if (tokenizer.nextToken() != Tokenizer::DATUM_TOKEN) {
return node;
}
node.name = tokenizer.getDatum();
if (tokenizer.nextToken() != ':') {
return node;
}
int token;
bool expectingDatum = true;
while ((token = tokenizer.nextToken()) != -1) {
if (token == '{') {
for (FBXNode child = parseTextFBXNode(tokenizer); !child.name.isNull(); child = parseTextFBXNode(tokenizer)) {
node.children.append(child);
}
return node;
}
if (token == ',') {
expectingDatum = true;
} else if (token == Tokenizer::DATUM_TOKEN && expectingDatum) {
node.properties.append(tokenizer.getDatum());
expectingDatum = false;
} else {
tokenizer.pushBackToken(token);
return node;
}
}
return node;
}
FBXNode parseFBX(QIODevice* device) {
// verify the prolog
const QByteArray BINARY_PROLOG = "Kaydara FBX Binary ";
if (device->peek(BINARY_PROLOG.size()) != BINARY_PROLOG) {
// parse as a text file
FBXNode top;
Tokenizer tokenizer(device);
while (device->bytesAvailable()) {
FBXNode next = parseTextFBXNode(tokenizer);
if (next.name.isNull()) {
return top;
} else {
top.children.append(next);
}
}
return top;
}
QDataStream in(device);
in.setByteOrder(QDataStream::LittleEndian);
in.setVersion(QDataStream::Qt_4_5); // for single/double precision switch
// see http://code.blender.org/index.php/2013/08/fbx-binary-file-format-specification/ for an explanation
// of the FBX format
// verify the prolog
const QByteArray EXPECTED_PROLOG = "Kaydara FBX Binary ";
if (device->read(EXPECTED_PROLOG.size()) != EXPECTED_PROLOG) {
throw QString("Invalid header.");
}
// of the FBX binary format
// skip the rest of the header
const int HEADER_SIZE = 27;
in.skipRawData(HEADER_SIZE - EXPECTED_PROLOG.size());
in.skipRawData(HEADER_SIZE);
// parse the top-level node
FBXNode top;
while (device->bytesAvailable()) {
FBXNode next = parseFBXNode(in);
FBXNode next = parseBinaryFBXNode(in);
if (next.name.isNull()) {
return top;
@ -750,7 +872,8 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
joint.transform = geometry.joints.at(joint.parentIndex).transform *
model.preRotation * glm::mat4_cast(model.rotation) * model.postRotation;
}
geometry.joints.append(joint);
geometry.joints.append(joint);
geometry.jointIndices.insert(model.name, geometry.joints.size() - 1);
}
// find our special joints

View file

@ -99,6 +99,7 @@ class FBXGeometry {
public:
QVector<FBXJoint> joints;
QHash<QString, int> jointIndices;
QVector<FBXMesh> meshes;