Merge pull request #2687 from ey6es/animate

Provide basic support for loading FBX animations (including in agent code) and applying them to models.
This commit is contained in:
Stephen Birarda 2014-04-17 16:05:32 -07:00
commit 7c048b7512
32 changed files with 725 additions and 193 deletions

View file

@ -28,6 +28,7 @@ link_hifi_library(audio ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(avatars ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(voxels ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(fbx ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(particles ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(metavoxels ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}")
@ -51,4 +52,4 @@ IF (WIN32)
target_link_libraries(${TARGET_NAME} Winmm Ws2_32)
ENDIF(WIN32)
target_link_libraries(${TARGET_NAME} Qt5::Network Qt5::Widgets Qt5::Script "${GNUTLS_LIBRARY}")
target_link_libraries(${TARGET_NAME} Qt5::Network Qt5::Widgets Qt5::Script "${GNUTLS_LIBRARY}")

View file

@ -11,8 +11,10 @@
#include <QtCore/QCoreApplication>
#include <QtCore/QEventLoop>
#include <QtCore/QStandardPaths>
#include <QtCore/QTimer>
#include <QtNetwork/QNetworkAccessManager>
#include <QtNetwork/QNetworkDiskCache>
#include <QtNetwork/QNetworkRequest>
#include <QtNetwork/QNetworkReply>
@ -20,6 +22,7 @@
#include <AvatarData.h>
#include <NodeList.h>
#include <PacketHeaders.h>
#include <ResourceCache.h>
#include <UUID.h>
#include <VoxelConstants.h>
#include <ParticlesScriptingInterface.h>
@ -159,6 +162,10 @@ void Agent::run() {
QNetworkAccessManager *networkManager = new QNetworkAccessManager(this);
QNetworkReply *reply = networkManager->get(QNetworkRequest(scriptURL));
QNetworkDiskCache* cache = new QNetworkDiskCache(networkManager);
QString cachePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
cache->setCacheDirectory(!cachePath.isEmpty() ? cachePath : "agentCache");
networkManager->setCache(cache);
qDebug() << "Downloading script at" << scriptURL.toString();
@ -167,8 +174,9 @@ void Agent::run() {
loop.exec();
// let the AvatarData class use our QNetworkAcessManager
// let the AvatarData and ResourceCache classes use our QNetworkAccessManager
AvatarData::setNetworkAccessManager(networkManager);
ResourceCache::setNetworkAccessManager(networkManager);
QString scriptContents(reply->readAll());

View file

@ -15,12 +15,12 @@ var AMPLITUDE = 45.0;
var cumulativeTime = 0.0;
print("# Joint list start");
var jointList = MyAvatar.getJointNames();
var jointMappings = "\n# Joint list start";
for (var i = 0; i < jointList.length; i++) {
print("jointIndex = " + jointList[i] + " = " + i);
jointMappings = jointMappings + "\njointIndex = " + jointList[i] + " = " + i;
}
print("# Joint list end");
print(jointMappings + "\n# Joint list end");
Script.update.connect(function(deltaTime) {
cumulativeTime += deltaTime;

49
examples/dancing_bot.js Normal file
View file

@ -0,0 +1,49 @@
//
// dancing_bot.js
// examples
//
// Created by Andrzej Kapolka on 4/16/14.
// Copyright 2014 High Fidelity, Inc.
//
// This is an example script that demonstrates an NPC avatar running an FBX animation loop.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var animation = AnimationCache.getAnimation("http://www.fungibleinsight.com/faces/gangnam_style_2.fbx");
Avatar.skeletonModelURL = "http://www.fungibleinsight.com/faces/beta.fst";
Agent.isAvatar = true;
var jointMapping;
var frameIndex = 0.0;
var FRAME_RATE = 30.0; // frames per second
Script.update.connect(function(deltaTime) {
if (!jointMapping) {
var avatarJointNames = Avatar.jointNames;
var animationJointNames = animation.jointNames;
if (avatarJointNames === 0 || animationJointNames.length === 0) {
return;
}
jointMapping = new Array(avatarJointNames.length);
for (var i = 0; i < avatarJointNames.length; i++) {
jointMapping[i] = animationJointNames.indexOf(avatarJointNames[i]);
}
}
frameIndex += deltaTime * FRAME_RATE;
var frames = animation.frames;
var rotations = frames[Math.floor(frameIndex) % frames.length].rotations;
for (var j = 0; j < jointMapping.length; j++) {
var rotationIndex = jointMapping[j];
if (rotationIndex != -1) {
Avatar.setJointData(j, rotations[rotationIndex]);
}
}
});

View file

@ -120,6 +120,7 @@ include(${MACRO_DIR}/LinkHifiLibrary.cmake)
link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(voxels ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(fbx ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(metavoxels ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(particles ${TARGET_NAME} "${ROOT_DIR}")

View file

@ -3397,6 +3397,7 @@ void Application::loadScript(const QString& scriptName) {
scriptEngine->registerGlobalObject("Menu", MenuScriptingInterface::getInstance());
scriptEngine->registerGlobalObject("Settings", SettingsScriptingInterface::getInstance());
scriptEngine->registerGlobalObject("AudioDevice", AudioDeviceScriptingInterface::getInstance());
scriptEngine->registerGlobalObject("AnimationCache", &_animationCache);
scriptEngine->registerGlobalObject("AudioReflector", &_audioReflector);
QThread* workerThread = new QThread(this);

View file

@ -470,6 +470,7 @@ private:
QSet<int> _keysPressed;
GeometryCache _geometryCache;
AnimationCache _animationCache;
TextureCache _textureCache;
GlowEffect _glowEffect;

View file

@ -22,9 +22,9 @@
#include <AccountManager.h>
#include "Application.h"
#include "renderer/FBXReader.h"
#include <FBXReader.h>
#include "Application.h"
#include "ModelUploader.h"
@ -306,14 +306,14 @@ bool ModelUploader::addTextures(const QString& texdir, const QString fbxFile) {
foreach (FBXMesh mesh, geometry.meshes) {
foreach (FBXMeshPart part, mesh.parts) {
if (!part.diffuseFilename.isEmpty()) {
if (!addPart(texdir + "/" + part.diffuseFilename,
if (!part.diffuseTexture.filename.isEmpty()) {
if (!addPart(texdir + "/" + part.diffuseTexture.filename,
QString("texture%1").arg(++_texturesCount))) {
return false;
}
}
if (!part.normalFilename.isEmpty()) {
if (!addPart(texdir + "/" + part.normalFilename,
if (!part.normalTexture.filename.isEmpty()) {
if (!addPart(texdir + "/" + part.normalTexture.filename,
QString("texture%1").arg(++_texturesCount))) {
return false;
}

View file

@ -78,112 +78,6 @@ float angle_to(glm::vec3 head_pos, glm::vec3 source_pos, float render_yaw, float
return atan2(head_pos.x - source_pos.x, head_pos.z - source_pos.z) + render_yaw + head_yaw;
}
// Helper function returns the positive angle (in radians) between two 3D vectors
float angleBetween(const glm::vec3& v1, const glm::vec3& v2) {
return acosf((glm::dot(v1, v2)) / (glm::length(v1) * glm::length(v2)));
}
// Helper function return the rotation from the first vector onto the second
glm::quat rotationBetween(const glm::vec3& v1, const glm::vec3& v2) {
float angle = angleBetween(v1, v2);
if (glm::isnan(angle) || angle < EPSILON) {
return glm::quat();
}
glm::vec3 axis;
if (angle > 179.99f * RADIANS_PER_DEGREE) { // 180 degree rotation; must use another axis
axis = glm::cross(v1, glm::vec3(1.0f, 0.0f, 0.0f));
float axisLength = glm::length(axis);
if (axisLength < EPSILON) { // parallel to x; y will work
axis = glm::normalize(glm::cross(v1, glm::vec3(0.0f, 1.0f, 0.0f)));
} else {
axis /= axisLength;
}
} else {
axis = glm::normalize(glm::cross(v1, v2));
}
return glm::angleAxis(angle, axis);
}
glm::vec3 extractTranslation(const glm::mat4& matrix) {
return glm::vec3(matrix[3][0], matrix[3][1], matrix[3][2]);
}
void setTranslation(glm::mat4& matrix, const glm::vec3& translation) {
matrix[3][0] = translation.x;
matrix[3][1] = translation.y;
matrix[3][2] = translation.z;
}
glm::quat extractRotation(const glm::mat4& matrix, bool assumeOrthogonal) {
// uses the iterative polar decomposition algorithm described by Ken Shoemake at
// http://www.cs.wisc.edu/graphics/Courses/838-s2002/Papers/polar-decomp.pdf
// code adapted from Clyde, https://github.com/threerings/clyde/blob/master/src/main/java/com/threerings/math/Matrix4f.java
// start with the contents of the upper 3x3 portion of the matrix
glm::mat3 upper = glm::mat3(matrix);
if (!assumeOrthogonal) {
for (int i = 0; i < 10; i++) {
// store the results of the previous iteration
glm::mat3 previous = upper;
// compute average of the matrix with its inverse transpose
float sd00 = previous[1][1] * previous[2][2] - previous[2][1] * previous[1][2];
float sd10 = previous[0][1] * previous[2][2] - previous[2][1] * previous[0][2];
float sd20 = previous[0][1] * previous[1][2] - previous[1][1] * previous[0][2];
float det = previous[0][0] * sd00 + previous[2][0] * sd20 - previous[1][0] * sd10;
if (fabs(det) == 0.0f) {
// determinant is zero; matrix is not invertible
break;
}
float hrdet = 0.5f / det;
upper[0][0] = +sd00 * hrdet + previous[0][0] * 0.5f;
upper[1][0] = -sd10 * hrdet + previous[1][0] * 0.5f;
upper[2][0] = +sd20 * hrdet + previous[2][0] * 0.5f;
upper[0][1] = -(previous[1][0] * previous[2][2] - previous[2][0] * previous[1][2]) * hrdet + previous[0][1] * 0.5f;
upper[1][1] = +(previous[0][0] * previous[2][2] - previous[2][0] * previous[0][2]) * hrdet + previous[1][1] * 0.5f;
upper[2][1] = -(previous[0][0] * previous[1][2] - previous[1][0] * previous[0][2]) * hrdet + previous[2][1] * 0.5f;
upper[0][2] = +(previous[1][0] * previous[2][1] - previous[2][0] * previous[1][1]) * hrdet + previous[0][2] * 0.5f;
upper[1][2] = -(previous[0][0] * previous[2][1] - previous[2][0] * previous[0][1]) * hrdet + previous[1][2] * 0.5f;
upper[2][2] = +(previous[0][0] * previous[1][1] - previous[1][0] * previous[0][1]) * hrdet + previous[2][2] * 0.5f;
// compute the difference; if it's small enough, we're done
glm::mat3 diff = upper - previous;
if (diff[0][0] * diff[0][0] + diff[1][0] * diff[1][0] + diff[2][0] * diff[2][0] + diff[0][1] * diff[0][1] +
diff[1][1] * diff[1][1] + diff[2][1] * diff[2][1] + diff[0][2] * diff[0][2] + diff[1][2] * diff[1][2] +
diff[2][2] * diff[2][2] < EPSILON) {
break;
}
}
}
// now that we have a nice orthogonal matrix, we can extract the rotation quaternion
// using the method described in http://en.wikipedia.org/wiki/Rotation_matrix#Conversions
float x2 = fabs(1.0f + upper[0][0] - upper[1][1] - upper[2][2]);
float y2 = fabs(1.0f - upper[0][0] + upper[1][1] - upper[2][2]);
float z2 = fabs(1.0f - upper[0][0] - upper[1][1] + upper[2][2]);
float w2 = fabs(1.0f + upper[0][0] + upper[1][1] + upper[2][2]);
return glm::normalize(glm::quat(0.5f * sqrtf(w2),
0.5f * sqrtf(x2) * (upper[1][2] >= upper[2][1] ? 1.0f : -1.0f),
0.5f * sqrtf(y2) * (upper[2][0] >= upper[0][2] ? 1.0f : -1.0f),
0.5f * sqrtf(z2) * (upper[0][1] >= upper[1][0] ? 1.0f : -1.0f)));
}
glm::vec3 extractScale(const glm::mat4& matrix) {
return glm::vec3(glm::length(matrix[0]), glm::length(matrix[1]), glm::length(matrix[2]));
}
float extractUniformScale(const glm::mat4& matrix) {
return extractUniformScale(extractScale(matrix));
}
float extractUniformScale(const glm::vec3& scale) {
return (scale.x + scale.y + scale.z) / 3.0f;
}
// Draw a 3D vector floating in space
void drawVector(glm::vec3 * vector) {
glDisable(GL_LIGHTING);
@ -629,4 +523,4 @@ bool pointInSphere(glm::vec3& point, glm::vec3& sphereCenter, double sphereRadiu
return true;
}
return false;
}
}

View file

@ -44,22 +44,6 @@ void drawVector(glm::vec3* vector);
void printVector(glm::vec3 vec);
float angleBetween(const glm::vec3& v1, const glm::vec3& v2);
glm::quat rotationBetween(const glm::vec3& v1, const glm::vec3& v2);
glm::vec3 extractTranslation(const glm::mat4& matrix);
void setTranslation(glm::mat4& matrix, const glm::vec3& translation);
glm::quat extractRotation(const glm::mat4& matrix, bool assumeOrthogonal = false);
glm::vec3 extractScale(const glm::mat4& matrix);
float extractUniformScale(const glm::mat4& matrix);
float extractUniformScale(const glm::vec3& scale);
double diffclock(timeval *clock1,timeval *clock2);
void renderCollisionOverlay(int width, int height, float magnitude, float red = 0, float blue = 0, float green = 0);

View file

@ -15,9 +15,10 @@
#include <faceplus.h>
#endif
#include <FBXReader.h>
#include "Application.h"
#include "Faceplus.h"
#include "renderer/FBXReader.h"
static int floatVectorMetaTypeId = qRegisterMetaType<QVector<float> >();

View file

@ -13,9 +13,10 @@
#include <SharedUtil.h>
#include <FBXReader.h>
#include "Application.h"
#include "Visage.h"
#include "renderer/FBXReader.h"
// this has to go after our normal includes, because its definition of HANDLE conflicts with Qt's
#ifdef HAVE_VISAGE

View file

@ -543,14 +543,14 @@ void NetworkGeometry::setGeometry(const FBXGeometry& geometry) {
int totalIndices = 0;
foreach (const FBXMeshPart& part, mesh.parts) {
NetworkMeshPart networkPart;
if (!part.diffuseFilename.isEmpty()) {
if (!part.diffuseTexture.filename.isEmpty()) {
networkPart.diffuseTexture = Application::getInstance()->getTextureCache()->getTexture(
_textureBase.resolved(QUrl(part.diffuseFilename)), false, mesh.isEye);
_textureBase.resolved(QUrl(part.diffuseTexture.filename)), false, mesh.isEye, part.diffuseTexture.content);
networkPart.diffuseTexture->setLoadPriorities(_loadPriorities);
}
if (!part.normalFilename.isEmpty()) {
if (!part.normalTexture.filename.isEmpty()) {
networkPart.normalTexture = Application::getInstance()->getTextureCache()->getTexture(
_textureBase.resolved(QUrl(part.normalFilename)), true);
_textureBase.resolved(QUrl(part.normalTexture.filename)), true, false, part.normalTexture.content);
networkPart.normalTexture->setLoadPriorities(_loadPriorities);
}
networkMesh.parts.append(networkPart);

View file

@ -20,7 +20,7 @@
#include <ResourceCache.h>
#include "FBXReader.h"
#include <FBXReader.h>
class Model;
class NetworkGeometry;

View file

@ -105,13 +105,22 @@ GLuint TextureCache::getBlueTextureID() {
return _blueTextureID;
}
QSharedPointer<NetworkTexture> TextureCache::getTexture(const QUrl& url, bool normalMap, bool dilatable) {
/// Extra data for creating textures.
class TextureExtra {
public:
bool normalMap;
const QByteArray& content;
};
QSharedPointer<NetworkTexture> TextureCache::getTexture(const QUrl& url, bool normalMap,
bool dilatable, const QByteArray& content) {
if (!dilatable) {
return ResourceCache::getResource(url, QUrl(), false, &normalMap).staticCast<NetworkTexture>();
TextureExtra extra = { normalMap, content };
return ResourceCache::getResource(url, QUrl(), false, &extra).staticCast<NetworkTexture>();
}
QSharedPointer<NetworkTexture> texture = _dilatableNetworkTextures.value(url);
if (texture.isNull()) {
texture = QSharedPointer<NetworkTexture>(new DilatableNetworkTexture(url), &Resource::allReferencesCleared);
texture = QSharedPointer<NetworkTexture>(new DilatableNetworkTexture(url, content), &Resource::allReferencesCleared);
texture->setSelf(texture);
texture->setCache(this);
_dilatableNetworkTextures.insert(url, texture);
@ -215,7 +224,9 @@ bool TextureCache::eventFilter(QObject* watched, QEvent* event) {
QSharedPointer<Resource> TextureCache::createResource(const QUrl& url,
const QSharedPointer<Resource>& fallback, bool delayLoad, const void* extra) {
return QSharedPointer<Resource>(new NetworkTexture(url, *(const bool*)extra), &Resource::allReferencesCleared);
const TextureExtra* textureExtra = static_cast<const TextureExtra*>(extra);
return QSharedPointer<Resource>(new NetworkTexture(url, textureExtra->normalMap, textureExtra->content),
&Resource::allReferencesCleared);
}
QOpenGLFramebufferObject* TextureCache::createFramebufferObject() {
@ -238,8 +249,8 @@ Texture::~Texture() {
glDeleteTextures(1, &_id);
}
NetworkTexture::NetworkTexture(const QUrl& url, bool normalMap) :
Resource(url),
NetworkTexture::NetworkTexture(const QUrl& url, bool normalMap, const QByteArray& content) :
Resource(url, !content.isEmpty()),
_translucent(false) {
if (!url.isValid()) {
@ -250,12 +261,19 @@ NetworkTexture::NetworkTexture(const QUrl& url, bool normalMap) :
glBindTexture(GL_TEXTURE_2D, getID());
loadSingleColorTexture(normalMap ? OPAQUE_BLUE : OPAQUE_WHITE);
glBindTexture(GL_TEXTURE_2D, 0);
// if we have content, load it after we have our self pointer
if (!content.isEmpty()) {
_startedLoading = true;
QMetaObject::invokeMethod(this, "loadContent", Qt::QueuedConnection, Q_ARG(const QByteArray&, content));
}
}
class ImageReader : public QRunnable {
public:
ImageReader(const QWeakPointer<Resource>& texture, QNetworkReply* reply);
ImageReader(const QWeakPointer<Resource>& texture, QNetworkReply* reply, const QUrl& url = QUrl(),
const QByteArray& content = QByteArray());
virtual void run();
@ -263,27 +281,37 @@ private:
QWeakPointer<Resource> _texture;
QNetworkReply* _reply;
QUrl _url;
QByteArray _content;
};
ImageReader::ImageReader(const QWeakPointer<Resource>& texture, QNetworkReply* reply) :
ImageReader::ImageReader(const QWeakPointer<Resource>& texture, QNetworkReply* reply,
const QUrl& url, const QByteArray& content) :
_texture(texture),
_reply(reply) {
_reply(reply),
_url(url),
_content(content) {
}
void ImageReader::run() {
QSharedPointer<Resource> texture = _texture.toStrongRef();
if (texture.isNull()) {
_reply->deleteLater();
if (_reply) {
_reply->deleteLater();
}
return;
}
QUrl url = _reply->url();
QImage image = QImage::fromData(_reply->readAll());
_reply->deleteLater();
if (_reply) {
_url = _reply->url();
_content = _reply->readAll();
_reply->deleteLater();
}
QImage image = QImage::fromData(_content);
// enforce a fixed maximum
const int MAXIMUM_SIZE = 1024;
if (image.width() > MAXIMUM_SIZE || image.height() > MAXIMUM_SIZE) {
qDebug() << "Image greater than maximum size:" << url << image.width() << image.height();
qDebug() << "Image greater than maximum size:" << _url << image.width() << image.height();
image = image.scaled(MAXIMUM_SIZE, MAXIMUM_SIZE, Qt::KeepAspectRatio);
}
@ -315,7 +343,7 @@ void ImageReader::run() {
}
int imageArea = image.width() * image.height();
if (opaquePixels == imageArea) {
qDebug() << "Image with alpha channel is completely opaque:" << url;
qDebug() << "Image with alpha channel is completely opaque:" << _url;
image = image.convertToFormat(QImage::Format_RGB888);
}
QMetaObject::invokeMethod(texture.data(), "setImage", Q_ARG(const QImage&, image),
@ -327,6 +355,10 @@ void NetworkTexture::downloadFinished(QNetworkReply* reply) {
QThreadPool::globalInstance()->start(new ImageReader(_self, reply));
}
void NetworkTexture::loadContent(const QByteArray& content) {
QThreadPool::globalInstance()->start(new ImageReader(_self, NULL, _url, content));
}
void NetworkTexture::setImage(const QImage& image, bool translucent) {
_translucent = translucent;
@ -348,8 +380,8 @@ void NetworkTexture::imageLoaded(const QImage& image) {
// nothing by default
}
DilatableNetworkTexture::DilatableNetworkTexture(const QUrl& url) :
NetworkTexture(url, false),
DilatableNetworkTexture::DilatableNetworkTexture(const QUrl& url, const QByteArray& content) :
NetworkTexture(url, false, content),
_innerRadius(0),
_outerRadius(0)
{

View file

@ -44,7 +44,8 @@ public:
GLuint getBlueTextureID();
/// Loads a texture from the specified URL.
QSharedPointer<NetworkTexture> getTexture(const QUrl& url, bool normalMap = false, bool dilatable = false);
QSharedPointer<NetworkTexture> getTexture(const QUrl& url, bool normalMap = false, bool dilatable = false,
const QByteArray& content = QByteArray());
/// Returns a pointer to the primary framebuffer object. This render target includes a depth component, and is
/// used for scene rendering.
@ -115,7 +116,7 @@ class NetworkTexture : public Resource, public Texture {
public:
NetworkTexture(const QUrl& url, bool normalMap);
NetworkTexture(const QUrl& url, bool normalMap, const QByteArray& content);
/// Checks whether it "looks like" this texture is translucent
/// (majority of pixels neither fully opaque or fully transparent).
@ -124,10 +125,12 @@ public:
protected:
virtual void downloadFinished(QNetworkReply* reply);
virtual void imageLoaded(const QImage& image);
Q_INVOKABLE void loadContent(const QByteArray& content);
Q_INVOKABLE void setImage(const QImage& image, bool translucent);
virtual void imageLoaded(const QImage& image);
private:
bool _translucent;
@ -139,7 +142,7 @@ class DilatableNetworkTexture : public NetworkTexture {
public:
DilatableNetworkTexture(const QUrl& url);
DilatableNetworkTexture(const QUrl& url, const QByteArray& content);
/// Returns a pointer to a texture with the requested amount of dilation.
QSharedPointer<Texture> getDilatedTexture(float dilation);

View file

@ -99,6 +99,8 @@ class AvatarData : public QObject {
Q_PROPERTY(QString skeletonModelURL READ getSkeletonModelURLFromScript WRITE setSkeletonModelURLFromScript)
Q_PROPERTY(QString billboardURL READ getBillboardURL WRITE setBillboardFromURL)
Q_PROPERTY(QStringList jointNames READ getJointNames)
Q_PROPERTY(QUuid sessionUUID READ getSessionUUID);
public:
AvatarData();

View file

@ -0,0 +1,38 @@
cmake_minimum_required(VERSION 2.8)
if (WIN32)
cmake_policy (SET CMP0020 NEW)
endif (WIN32)
set(ROOT_DIR ../..)
set(MACRO_DIR "${ROOT_DIR}/cmake/macros")
# setup for find modules
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/modules/")
set(TARGET_NAME fbx)
include(${MACRO_DIR}/SetupHifiLibrary.cmake)
setup_hifi_library(${TARGET_NAME})
include(${MACRO_DIR}/IncludeGLM.cmake)
include_glm(${TARGET_NAME} "${ROOT_DIR}")
include(${MACRO_DIR}/LinkHifiLibrary.cmake)
link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(voxels ${TARGET_NAME} "${ROOT_DIR}")
# link ZLIB and GnuTLS
find_package(ZLIB)
find_package(GnuTLS REQUIRED)
# add a definition for ssize_t so that windows doesn't bail on gnutls.h
if (WIN32)
add_definitions(-Dssize_t=long)
endif ()
include_directories(SYSTEM "${ZLIB_INCLUDE_DIRS}" "${GNUTLS_INCLUDE_DIR}")
target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" Qt5::Widgets "${GNUTLS_LIBRARY}")

View file

@ -22,14 +22,14 @@
#include <glm/gtx/quaternion.hpp>
#include <glm/gtx/transform.hpp>
#include <OctalCode.h>
#include <GeometryUtil.h>
#include <OctalCode.h>
#include <Shape.h>
#include <SharedUtil.h>
#include <VoxelTree.h>
#include "FBXReader.h"
#include "Util.h"
using namespace std;
@ -72,6 +72,8 @@ bool FBXGeometry::hasBlendedMeshes() const {
}
static int fbxGeometryMetaTypeId = qRegisterMetaType<FBXGeometry>();
static int fbxAnimationFrameMetaTypeId = qRegisterMetaType<FBXAnimationFrame>();
static int fbxAnimationFrameVectorMetaTypeId = qRegisterMetaType<QVector<FBXAnimationFrame> >();
template<class T> QVariant readBinaryArray(QDataStream& in) {
quint32 arrayLength;
@ -444,6 +446,34 @@ QVector<int> getIntVector(const QVariantList& properties, int index) {
return vector;
}
QVector<qlonglong> getLongVector(const QVariantList& properties, int index) {
if (index >= properties.size()) {
return QVector<qlonglong>();
}
QVector<qlonglong> vector = properties.at(index).value<QVector<qlonglong> >();
if (!vector.isEmpty()) {
return vector;
}
for (; index < properties.size(); index++) {
vector.append(properties.at(index).toLongLong());
}
return vector;
}
QVector<float> getFloatVector(const QVariantList& properties, int index) {
if (index >= properties.size()) {
return QVector<float>();
}
QVector<float> vector = properties.at(index).value<QVector<float> >();
if (!vector.isEmpty()) {
return vector;
}
for (; index < properties.size(); index++) {
vector.append(properties.at(index).toFloat());
}
return vector;
}
QVector<double> getDoubleVector(const QVariantList& properties, int index) {
if (index >= properties.size()) {
return QVector<double>();
@ -478,8 +508,7 @@ glm::vec3 parseVec3(const QString& string) {
QString processID(const QString& id) {
// Blender (at least) prepends a type to the ID, so strip it out
int index = id.indexOf("::");
return (index == -1) ? id : id.mid(index + 2);
return id.mid(id.lastIndexOf(':') + 1);
}
QString getID(const QVariantList& properties, int index = 0) {
@ -901,6 +930,19 @@ public:
float averageRadius; // average distance from mesh points to averageVertex
};
class AnimationCurve {
public:
QVector<float> values;
};
FBXTexture getTexture(const QString& textureID, const QHash<QString, QByteArray>& textureFilenames,
const QHash<QByteArray, QByteArray>& textureContent) {
FBXTexture texture;
texture.filename = textureFilenames.value(textureID);
texture.content = textureContent.value(texture.filename);
return texture;
}
FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) {
QHash<QString, ExtractedMesh> meshes;
QVector<ExtractedBlendshape> blendshapes;
@ -908,10 +950,16 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
QMultiHash<QString, QString> childMap;
QHash<QString, FBXModel> models;
QHash<QString, Cluster> clusters;
QHash<QString, AnimationCurve> animationCurves;
QHash<QString, QByteArray> textureFilenames;
QHash<QByteArray, QByteArray> textureContent;
QHash<QString, Material> materials;
QHash<QString, QString> diffuseTextures;
QHash<QString, QString> bumpTextures;
QHash<QString, QString> localRotations;
QHash<QString, QString> xComponents;
QHash<QString, QString> yComponents;
QHash<QString, QString> zComponents;
QVariantHash joints = mapping.value("joint").toHash();
QString jointEyeLeftName = processID(getString(joints.value("jointEyeLeft", "jointEyeLeft")));
@ -974,7 +1022,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
QString name;
if (object.properties.size() == 3) {
name = object.properties.at(1).toString();
name = name.left(name.indexOf(QChar('\0')));
name = processID(name.left(name.indexOf(QChar('\0'))));
} else {
name = getID(object.properties);
@ -1124,7 +1172,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
model.postRotation = glm::quat(glm::radians(postRotation));
model.postTransform = glm::translate(-rotationPivot) * glm::translate(scalePivot) *
glm::scale(scale) * glm::translate(-scalePivot);
// NOTE: anbgles from the FBX file are in degrees
// NOTE: angles from the FBX file are in degrees
// so we convert them to radians for the FBXModel class
model.rotationMin = glm::radians(glm::vec3(rotationMinX ? rotationMin.x : -180.0f,
rotationMinY ? rotationMin.y : -180.0f, rotationMinZ ? rotationMin.z : -180.0f));
@ -1141,6 +1189,21 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
textureFilenames.insert(getID(object.properties), filename);
}
}
} else if (object.name == "Video") {
QByteArray filename;
QByteArray content;
foreach (const FBXNode& subobject, object.children) {
if (subobject.name == "RelativeFilename") {
filename = subobject.properties.at(0).toByteArray();
filename = filename.mid(qMax(filename.lastIndexOf('\\'), filename.lastIndexOf('/')) + 1);
} else if (subobject.name == "Content" && !subobject.properties.isEmpty()) {
content = subobject.properties.at(0).toByteArray();
}
}
if (!content.isEmpty()) {
textureContent.insert(filename, content);
}
} else if (object.name == "Material") {
Material material = { glm::vec3(1.0f, 1.0f, 1.0f), glm::vec3(1.0f, 1.0f, 1.0f), 96.0f };
foreach (const FBXNode& subobject, object.children) {
@ -1204,6 +1267,14 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
blendshapeChannelIndices.insert(id, index);
}
}
} else if (object.name == "AnimationCurve") {
AnimationCurve curve;
foreach (const FBXNode& subobject, object.children) {
if (subobject.name == "KeyValueFloat") {
curve.values = getFloatVector(subobject.properties, 0);
}
}
animationCurves.insert(getID(object.properties), curve);
}
}
} else if (child.name == "Connections") {
@ -1214,8 +1285,20 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
if (type.contains("diffuse")) {
diffuseTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1));
} else if (type.contains("bump")) {
} else if (type.contains("bump") || type.contains("normal")) {
bumpTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1));
} else if (type == "lcl rotation") {
localRotations.insert(getID(connection.properties, 2), getID(connection.properties, 1));
} else if (type == "d|x") {
xComponents.insert(getID(connection.properties, 2), getID(connection.properties, 1));
} else if (type == "d|y") {
yComponents.insert(getID(connection.properties, 2), getID(connection.properties, 1));
} else if (type == "d|z") {
zComponents.insert(getID(connection.properties, 2), getID(connection.properties, 1));
}
}
parentMap.insert(getID(connection.properties, 1), getID(connection.properties, 2));
@ -1239,7 +1322,8 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
glm::quat offsetRotation = glm::quat(glm::radians(glm::vec3(mapping.value("rx").toFloat(),
mapping.value("ry").toFloat(), mapping.value("rz").toFloat())));
geometry.offset = glm::translate(glm::vec3(mapping.value("tx").toFloat(), mapping.value("ty").toFloat(),
mapping.value("tz").toFloat())) * glm::mat4_cast(offsetRotation) * glm::scale(glm::vec3(offsetScale, offsetScale, offsetScale));
mapping.value("tz").toFloat())) * glm::mat4_cast(offsetRotation) *
glm::scale(glm::vec3(offsetScale, offsetScale, offsetScale));
// get the list of models in depth-first traversal order
QVector<QString> modelIDs;
@ -1277,6 +1361,17 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
appendModelIDs(parentMap.value(topID), childMap, models, remainingModels, modelIDs);
}
// figure the number of animation frames from the curves
int frameCount = 0;
foreach (const AnimationCurve& curve, animationCurves) {
frameCount = qMax(frameCount, curve.values.size());
}
for (int i = 0; i < frameCount; i++) {
FBXAnimationFrame frame;
frame.rotations.resize(modelIDs.size());
geometry.animationFrames.append(frame);
}
// convert the models to joints
QVariantList freeJoints = mapping.values("freeJoint");
foreach (const QString& modelID, modelIDs) {
@ -1286,7 +1381,8 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
joint.parentIndex = model.parentIndex;
// get the indices of all ancestors starting with the first free one (if any)
joint.freeLineage.append(geometry.joints.size());
int jointIndex = geometry.joints.size();
joint.freeLineage.append(jointIndex);
int lastFreeIndex = joint.isFree ? 0 : -1;
for (int index = joint.parentIndex; index != -1; index = geometry.joints.at(index).parentIndex) {
if (geometry.joints.at(index).isFree) {
@ -1325,6 +1421,18 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
joint.shapeType = Shape::UNKNOWN_SHAPE;
geometry.joints.append(joint);
geometry.jointIndices.insert(model.name, geometry.joints.size());
QString rotationID = localRotations.value(modelID);
AnimationCurve xCurve = animationCurves.value(xComponents.value(rotationID));
AnimationCurve yCurve = animationCurves.value(yComponents.value(rotationID));
AnimationCurve zCurve = animationCurves.value(zComponents.value(rotationID));
glm::vec3 defaultValues = glm::degrees(safeEulerAngles(joint.rotation));
for (int i = 0; i < frameCount; i++) {
geometry.animationFrames[i].rotations[jointIndex] = glm::quat(glm::radians(glm::vec3(
xCurve.values.isEmpty() ? defaultValues.x : xCurve.values.at(i % xCurve.values.size()),
yCurve.values.isEmpty() ? defaultValues.y : yCurve.values.at(i % yCurve.values.size()),
zCurve.values.isEmpty() ? defaultValues.z : zCurve.values.at(i % zCurve.values.size()))));
}
}
// for each joint we allocate a JointShapeInfo in which we'll store collision shape info
QVector<JointShapeInfo> jointShapeInfos;
@ -1377,23 +1485,23 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
if (materials.contains(childID)) {
Material material = materials.value(childID);
QByteArray diffuseFilename;
FBXTexture diffuseTexture;
QString diffuseTextureID = diffuseTextures.value(childID);
if (!diffuseTextureID.isNull()) {
diffuseFilename = textureFilenames.value(diffuseTextureID);
diffuseTexture = getTexture(diffuseTextureID, textureFilenames, textureContent);
// FBX files generated by 3DSMax have an intermediate texture parent, apparently
foreach (const QString& childTextureID, childMap.values(diffuseTextureID)) {
if (textureFilenames.contains(childTextureID)) {
diffuseFilename = textureFilenames.value(childTextureID);
diffuseTexture = getTexture(diffuseTextureID, textureFilenames, textureContent);
}
}
}
QByteArray normalFilename;
FBXTexture normalTexture;
QString bumpTextureID = bumpTextures.value(childID);
if (!bumpTextureID.isNull()) {
normalFilename = textureFilenames.value(bumpTextureID);
normalTexture = getTexture(bumpTextureID, textureFilenames, textureContent);
generateTangents = true;
}
@ -1403,21 +1511,21 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
part.diffuseColor = material.diffuse;
part.specularColor = material.specular;
part.shininess = material.shininess;
if (!diffuseFilename.isNull()) {
part.diffuseFilename = diffuseFilename;
if (!diffuseTexture.filename.isNull()) {
part.diffuseTexture = diffuseTexture;
}
if (!normalFilename.isNull()) {
part.normalFilename = normalFilename;
if (!normalTexture.filename.isNull()) {
part.normalTexture = normalTexture;
}
}
}
materialIndex++;
} else if (textureFilenames.contains(childID)) {
QByteArray filename = textureFilenames.value(childID);
FBXTexture texture = getTexture(childID, textureFilenames, textureContent);
for (int j = 0; j < extracted.partMaterialTextures.size(); j++) {
if (extracted.partMaterialTextures.at(j).second == textureIndex) {
extracted.mesh.parts[j].diffuseFilename = filename;
extracted.mesh.parts[j].diffuseTexture = texture;
}
}
textureIndex++;

View file

@ -103,6 +103,14 @@ public:
glm::mat4 inverseBindMatrix;
};
/// A texture map in an FBX document.
class FBXTexture {
public:
QByteArray filename;
QByteArray content;
};
/// A single part of a mesh (with the same material).
class FBXMeshPart {
public:
@ -114,8 +122,8 @@ public:
glm::vec3 specularColor;
float shininess;
QByteArray diffuseFilename;
QByteArray normalFilename;
FBXTexture diffuseTexture;
FBXTexture normalTexture;
};
/// A single mesh (with optional blendshapes) extracted from an FBX document.
@ -139,6 +147,16 @@ public:
QVector<FBXBlendshape> blendshapes;
};
/// A single animation frame extracted from an FBX document.
class FBXAnimationFrame {
public:
QVector<glm::quat> rotations;
};
Q_DECLARE_METATYPE(FBXAnimationFrame)
Q_DECLARE_METATYPE(QVector<FBXAnimationFrame>)
/// An attachment to an FBX document.
class FBXAttachment {
public:
@ -183,6 +201,8 @@ public:
Extents bindExtents;
Extents meshExtents;
QVector<FBXAnimationFrame> animationFrames;
QVector<FBXAttachment> attachments;
int getJointIndex(const QString& name) const { return jointIndices.value(name) - 1; }

View file

@ -23,6 +23,7 @@ include_glm(${TARGET_NAME} "${ROOT_DIR}")
include(${MACRO_DIR}/LinkHifiLibrary.cmake)
link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(fbx ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}")
# link ZLIB and GnuTLS
@ -35,4 +36,4 @@ if (WIN32)
endif ()
include_directories(SYSTEM "${ZLIB_INCLUDE_DIRS}" "${GNUTLS_INCLUDE_DIR}")
target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" Qt5::Widgets "${GNUTLS_LIBRARY}")
target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" Qt5::Widgets "${GNUTLS_LIBRARY}")

View file

@ -24,6 +24,7 @@ include(${MACRO_DIR}/LinkHifiLibrary.cmake)
link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(voxels ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(fbx ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(particles ${TARGET_NAME} "${ROOT_DIR}")
# link ZLIB
@ -36,4 +37,4 @@ if (WIN32)
endif ()
include_directories(SYSTEM "${ZLIB_INCLUDE_DIRS}" "${GNUTLS_INCLUDE_DIR}")
target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" "${GNUTLS_LIBRARY}" Qt5::Widgets)
target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" "${GNUTLS_LIBRARY}" Qt5::Widgets)

View file

@ -0,0 +1,102 @@
//
// AnimationCache.cpp
// libraries/script-engine/src/
//
// Created by Andrzej Kapolka on 4/14/14.
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QRunnable>
#include <QThreadPool>
#include "AnimationCache.h"
static int animationPointerMetaTypeId = qRegisterMetaType<AnimationPointer>();
AnimationCache::AnimationCache(QObject* parent) :
ResourceCache(parent) {
}
AnimationPointer AnimationCache::getAnimation(const QUrl& url) {
if (QThread::currentThread() != thread()) {
AnimationPointer result;
QMetaObject::invokeMethod(this, "getAnimation", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(AnimationPointer, result), Q_ARG(const QUrl&, url));
return result;
}
return getResource(url).staticCast<Animation>();
}
QSharedPointer<Resource> AnimationCache::createResource(const QUrl& url, const QSharedPointer<Resource>& fallback,
bool delayLoad, const void* extra) {
return QSharedPointer<Resource>(new Animation(url), &Resource::allReferencesCleared);
}
Animation::Animation(const QUrl& url) :
Resource(url) {
}
class AnimationReader : public QRunnable {
public:
AnimationReader(const QWeakPointer<Resource>& animation, QNetworkReply* reply);
virtual void run();
private:
QWeakPointer<Resource> _animation;
QNetworkReply* _reply;
};
AnimationReader::AnimationReader(const QWeakPointer<Resource>& animation, QNetworkReply* reply) :
_animation(animation),
_reply(reply) {
}
void AnimationReader::run() {
QSharedPointer<Resource> animation = _animation.toStrongRef();
if (!animation.isNull()) {
QMetaObject::invokeMethod(animation.data(), "setGeometry",
Q_ARG(const FBXGeometry&, readFBX(_reply->readAll(), QVariantHash())));
}
_reply->deleteLater();
}
QStringList Animation::getJointNames() const {
if (QThread::currentThread() != thread()) {
QStringList result;
QMetaObject::invokeMethod(const_cast<Animation*>(this), "getJointNames", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QStringList, result));
return result;
}
QStringList names;
foreach (const FBXJoint& joint, _geometry.joints) {
names.append(joint.name);
}
return names;
}
QVector<FBXAnimationFrame> Animation::getFrames() const {
if (QThread::currentThread() != thread()) {
QVector<FBXAnimationFrame> result;
QMetaObject::invokeMethod(const_cast<Animation*>(this), "getFrames", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QVector<FBXAnimationFrame>, result));
return result;
}
return _geometry.animationFrames;
}
void Animation::setGeometry(const FBXGeometry& geometry) {
_geometry = geometry;
finishedLoading(true);
}
void Animation::downloadFinished(QNetworkReply* reply) {
// send the reader off to the thread pool
QThreadPool::globalInstance()->start(new AnimationReader(_self, reply));
}

View file

@ -0,0 +1,68 @@
//
// AnimationCache.h
// libraries/script-engine/src/
//
// Created by Andrzej Kapolka on 4/14/14.
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_AnimationCache_h
#define hifi_AnimationCache_h
#include <ResourceCache.h>
#include <FBXReader.h>
class Animation;
typedef QSharedPointer<Animation> AnimationPointer;
/// Scriptable interface for FBX animation loading.
class AnimationCache : public ResourceCache {
Q_OBJECT
public:
AnimationCache(QObject* parent = NULL);
Q_INVOKABLE AnimationPointer getAnimation(const QString& url) { return getAnimation(QUrl(url)); }
Q_INVOKABLE AnimationPointer getAnimation(const QUrl& url);
protected:
virtual QSharedPointer<Resource> createResource(const QUrl& url,
const QSharedPointer<Resource>& fallback, bool delayLoad, const void* extra);
};
Q_DECLARE_METATYPE(AnimationPointer)
/// An animation loaded from the network.
class Animation : public Resource {
Q_OBJECT
public:
Animation(const QUrl& url);
const FBXGeometry& getGeometry() const { return _geometry; }
Q_INVOKABLE QStringList getJointNames() const;
Q_INVOKABLE QVector<FBXAnimationFrame> getFrames() const;
protected:
Q_INVOKABLE void setGeometry(const FBXGeometry& geometry);
virtual void downloadFinished(QNetworkReply* reply);
private:
FBXGeometry _geometry;
};
#endif // hifi_AnimationCache_h

View file

@ -0,0 +1,36 @@
//
// AnimationObject.cpp
// libraries/script-engine/src/
//
// Created by Andrzej Kapolka on 4/17/14.
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QScriptEngine>
#include "AnimationCache.h"
#include "AnimationObject.h"
QStringList AnimationObject::getJointNames() const {
return qscriptvalue_cast<AnimationPointer>(thisObject())->getJointNames();
}
QVector<FBXAnimationFrame> AnimationObject::getFrames() const {
return qscriptvalue_cast<AnimationPointer>(thisObject())->getFrames();
}
QVector<glm::quat> AnimationFrameObject::getRotations() const {
return qscriptvalue_cast<FBXAnimationFrame>(thisObject()).rotations;
}
void registerAnimationTypes(QScriptEngine* engine) {
qScriptRegisterSequenceMetaType<QVector<FBXAnimationFrame> >(engine);
engine->setDefaultPrototype(qMetaTypeId<FBXAnimationFrame>(), engine->newQObject(
new AnimationFrameObject(), QScriptEngine::ScriptOwnership));
engine->setDefaultPrototype(qMetaTypeId<AnimationPointer>(), engine->newQObject(
new AnimationObject(), QScriptEngine::ScriptOwnership));
}

View file

@ -0,0 +1,47 @@
//
// AnimationObject.h
// libraries/script-engine/src/
//
// Created by Andrzej Kapolka on 4/17/14.
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_AnimationObject_h
#define hifi_AnimationObject_h
#include <QObject>
#include <QScriptable>
#include <FBXReader.h>
class QScriptEngine;
/// Scriptable wrapper for animation pointers.
class AnimationObject : public QObject, protected QScriptable {
Q_OBJECT
Q_PROPERTY(QStringList jointNames READ getJointNames)
Q_PROPERTY(QVector<FBXAnimationFrame> frames READ getFrames)
public:
Q_INVOKABLE QStringList getJointNames() const;
Q_INVOKABLE QVector<FBXAnimationFrame> getFrames() const;
};
/// Scriptable wrapper for animation frames.
class AnimationFrameObject : public QObject, protected QScriptable {
Q_OBJECT
Q_PROPERTY(QVector<glm::quat> rotations READ getRotations)
public:
Q_INVOKABLE QVector<glm::quat> getRotations() const;
};
void registerAnimationTypes(QScriptEngine* engine);
#endif // hifi_AnimationObject_h

View file

@ -28,6 +28,7 @@
#include <Sound.h>
#include "AnimationObject.h"
#include "MenuItemProperties.h"
#include "LocalVoxels.h"
#include "ScriptEngine.h"
@ -64,7 +65,8 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNam
_fileNameString(fileNameString),
_quatLibrary(),
_vec3Library(),
_uuidLibrary()
_uuidLibrary(),
_animationCache(this)
{
}
@ -88,7 +90,8 @@ ScriptEngine::ScriptEngine(const QUrl& scriptURL,
_fileNameString(),
_quatLibrary(),
_vec3Library(),
_uuidLibrary()
_uuidLibrary(),
_animationCache(this)
{
QString scriptURLString = scriptURL.toString();
_fileNameString = scriptURLString;
@ -180,11 +183,13 @@ void ScriptEngine::init() {
registerVoxelMetaTypes(&_engine);
registerEventTypes(&_engine);
registerMenuItemProperties(&_engine);
registerAnimationTypes(&_engine);
qScriptRegisterMetaType(&_engine, ParticlePropertiesToScriptValue, ParticlePropertiesFromScriptValue);
qScriptRegisterMetaType(&_engine, ParticleIDtoScriptValue, ParticleIDfromScriptValue);
qScriptRegisterSequenceMetaType<QVector<ParticleID> >(&_engine);
qScriptRegisterSequenceMetaType<QVector<glm::vec2> >(&_engine);
qScriptRegisterSequenceMetaType<QVector<glm::quat> >(&_engine);
qScriptRegisterSequenceMetaType<QVector<QString> >(&_engine);
QScriptValue soundConstructorValue = _engine.newFunction(soundConstructor);
@ -204,7 +209,8 @@ void ScriptEngine::init() {
registerGlobalObject("Quat", &_quatLibrary);
registerGlobalObject("Vec3", &_vec3Library);
registerGlobalObject("Uuid", &_uuidLibrary);
registerGlobalObject("AnimationCache", &_animationCache);
registerGlobalObject("Voxels", &_voxelsScriptingInterface);
QScriptValue treeScaleValue = _engine.newVariant(QVariant(TREE_SCALE));

View file

@ -23,6 +23,7 @@
#include <AvatarData.h>
#include "AnimationCache.h"
#include "AbstractControllerScriptingInterface.h"
#include "Quat.h"
#include "ScriptUUID.h"
@ -125,6 +126,7 @@ private:
Quat _quatLibrary;
Vec3 _vec3Library;
ScriptUUID _uuidLibrary;
AnimationCache _animationCache;
};
#endif // hifi_ScriptEngine_h

View file

@ -12,6 +12,7 @@
#include <cfloat>
#include <cmath>
#include <QThread>
#include <QTimer>
#include <QtDebug>
@ -174,6 +175,10 @@ float Resource::getLoadPriority() {
}
void Resource::allReferencesCleared() {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "allReferencesCleared");
return;
}
if (_cache) {
// create and reinsert new shared pointer
QSharedPointer<Resource> self(this, &Resource::allReferencesCleared);

View file

@ -123,7 +123,7 @@ public:
void setCache(ResourceCache* cache) { _cache = cache; }
void allReferencesCleared();
Q_INVOKABLE void allReferencesCleared();
protected slots:

View file

@ -663,6 +663,110 @@ glm::vec3 safeEulerAngles(const glm::quat& q) {
}
}
// Helper function returns the positive angle (in radians) between two 3D vectors
float angleBetween(const glm::vec3& v1, const glm::vec3& v2) {
return acosf((glm::dot(v1, v2)) / (glm::length(v1) * glm::length(v2)));
}
// Helper function return the rotation from the first vector onto the second
glm::quat rotationBetween(const glm::vec3& v1, const glm::vec3& v2) {
float angle = angleBetween(v1, v2);
if (glm::isnan(angle) || angle < EPSILON) {
return glm::quat();
}
glm::vec3 axis;
if (angle > 179.99f * RADIANS_PER_DEGREE) { // 180 degree rotation; must use another axis
axis = glm::cross(v1, glm::vec3(1.0f, 0.0f, 0.0f));
float axisLength = glm::length(axis);
if (axisLength < EPSILON) { // parallel to x; y will work
axis = glm::normalize(glm::cross(v1, glm::vec3(0.0f, 1.0f, 0.0f)));
} else {
axis /= axisLength;
}
} else {
axis = glm::normalize(glm::cross(v1, v2));
}
return glm::angleAxis(angle, axis);
}
glm::vec3 extractTranslation(const glm::mat4& matrix) {
return glm::vec3(matrix[3][0], matrix[3][1], matrix[3][2]);
}
void setTranslation(glm::mat4& matrix, const glm::vec3& translation) {
matrix[3][0] = translation.x;
matrix[3][1] = translation.y;
matrix[3][2] = translation.z;
}
glm::quat extractRotation(const glm::mat4& matrix, bool assumeOrthogonal) {
// uses the iterative polar decomposition algorithm described by Ken Shoemake at
// http://www.cs.wisc.edu/graphics/Courses/838-s2002/Papers/polar-decomp.pdf
// code adapted from Clyde, https://github.com/threerings/clyde/blob/master/src/main/java/com/threerings/math/Matrix4f.java
// start with the contents of the upper 3x3 portion of the matrix
glm::mat3 upper = glm::mat3(matrix);
if (!assumeOrthogonal) {
for (int i = 0; i < 10; i++) {
// store the results of the previous iteration
glm::mat3 previous = upper;
// compute average of the matrix with its inverse transpose
float sd00 = previous[1][1] * previous[2][2] - previous[2][1] * previous[1][2];
float sd10 = previous[0][1] * previous[2][2] - previous[2][1] * previous[0][2];
float sd20 = previous[0][1] * previous[1][2] - previous[1][1] * previous[0][2];
float det = previous[0][0] * sd00 + previous[2][0] * sd20 - previous[1][0] * sd10;
if (fabs(det) == 0.0f) {
// determinant is zero; matrix is not invertible
break;
}
float hrdet = 0.5f / det;
upper[0][0] = +sd00 * hrdet + previous[0][0] * 0.5f;
upper[1][0] = -sd10 * hrdet + previous[1][0] * 0.5f;
upper[2][0] = +sd20 * hrdet + previous[2][0] * 0.5f;
upper[0][1] = -(previous[1][0] * previous[2][2] - previous[2][0] * previous[1][2]) * hrdet + previous[0][1] * 0.5f;
upper[1][1] = +(previous[0][0] * previous[2][2] - previous[2][0] * previous[0][2]) * hrdet + previous[1][1] * 0.5f;
upper[2][1] = -(previous[0][0] * previous[1][2] - previous[1][0] * previous[0][2]) * hrdet + previous[2][1] * 0.5f;
upper[0][2] = +(previous[1][0] * previous[2][1] - previous[2][0] * previous[1][1]) * hrdet + previous[0][2] * 0.5f;
upper[1][2] = -(previous[0][0] * previous[2][1] - previous[2][0] * previous[0][1]) * hrdet + previous[1][2] * 0.5f;
upper[2][2] = +(previous[0][0] * previous[1][1] - previous[1][0] * previous[0][1]) * hrdet + previous[2][2] * 0.5f;
// compute the difference; if it's small enough, we're done
glm::mat3 diff = upper - previous;
if (diff[0][0] * diff[0][0] + diff[1][0] * diff[1][0] + diff[2][0] * diff[2][0] + diff[0][1] * diff[0][1] +
diff[1][1] * diff[1][1] + diff[2][1] * diff[2][1] + diff[0][2] * diff[0][2] + diff[1][2] * diff[1][2] +
diff[2][2] * diff[2][2] < EPSILON) {
break;
}
}
}
// now that we have a nice orthogonal matrix, we can extract the rotation quaternion
// using the method described in http://en.wikipedia.org/wiki/Rotation_matrix#Conversions
float x2 = fabs(1.0f + upper[0][0] - upper[1][1] - upper[2][2]);
float y2 = fabs(1.0f - upper[0][0] + upper[1][1] - upper[2][2]);
float z2 = fabs(1.0f - upper[0][0] - upper[1][1] + upper[2][2]);
float w2 = fabs(1.0f + upper[0][0] + upper[1][1] + upper[2][2]);
return glm::normalize(glm::quat(0.5f * sqrtf(w2),
0.5f * sqrtf(x2) * (upper[1][2] >= upper[2][1] ? 1.0f : -1.0f),
0.5f * sqrtf(y2) * (upper[2][0] >= upper[0][2] ? 1.0f : -1.0f),
0.5f * sqrtf(z2) * (upper[0][1] >= upper[1][0] ? 1.0f : -1.0f)));
}
glm::vec3 extractScale(const glm::mat4& matrix) {
return glm::vec3(glm::length(matrix[0]), glm::length(matrix[1]), glm::length(matrix[2]));
}
float extractUniformScale(const glm::mat4& matrix) {
return extractUniformScale(extractScale(matrix));
}
float extractUniformScale(const glm::vec3& scale) {
return (scale.x + scale.y + scale.z) / 3.0f;
}
bool isNaN(float value) {
return value != value;
}

View file

@ -167,6 +167,22 @@ int unpackFloatVec3FromSignedTwoByteFixed(const unsigned char* sourceBuffer, glm
/// \return vec3 with euler angles in radians
glm::vec3 safeEulerAngles(const glm::quat& q);
float angleBetween(const glm::vec3& v1, const glm::vec3& v2);
glm::quat rotationBetween(const glm::vec3& v1, const glm::vec3& v2);
glm::vec3 extractTranslation(const glm::mat4& matrix);
void setTranslation(glm::mat4& matrix, const glm::vec3& translation);
glm::quat extractRotation(const glm::mat4& matrix, bool assumeOrthogonal = false);
glm::vec3 extractScale(const glm::mat4& matrix);
float extractUniformScale(const glm::mat4& matrix);
float extractUniformScale(const glm::vec3& scale);
/// \return bool are two orientations similar to each other
const float ORIENTATION_SIMILAR_ENOUGH = 5.0f; // 10 degrees in any direction
bool isSimilarOrientation(const glm::quat& orientionA, const glm::quat& orientionB,