mirror of
https://github.com/overte-org/overte.git
synced 2025-08-06 16:36:54 +02:00
Merge branch 'master' of github.com:highfidelity/hifi into serverless-domains
This commit is contained in:
commit
2d374583b4
14 changed files with 167 additions and 248 deletions
Binary file not shown.
|
@ -257,7 +257,11 @@ Item {
|
||||||
id: octreeCol
|
id: octreeCol
|
||||||
spacing: 4; x: 4; y: 4;
|
spacing: 4; x: 4; y: 4;
|
||||||
StatText {
|
StatText {
|
||||||
text: "Engine: " + root.engineFrameTime.toFixed(1) + " ms"
|
text: "Render Engine: " + root.engineFrameTime.toFixed(1) + " ms"
|
||||||
|
}
|
||||||
|
StatText {
|
||||||
|
visible: root.expanded
|
||||||
|
text: root.renderEngineStats
|
||||||
}
|
}
|
||||||
StatText {
|
StatText {
|
||||||
text: "Batch: " + root.batchFrameTime.toFixed(1) + " ms"
|
text: "Batch: " + root.batchFrameTime.toFixed(1) + " ms"
|
||||||
|
|
|
@ -187,9 +187,10 @@ Windows.ScrollingWindow {
|
||||||
var textures = JSON.stringify({ "tex.picture": defaultURL});
|
var textures = JSON.stringify({ "tex.picture": defaultURL});
|
||||||
var shapeType = "box";
|
var shapeType = "box";
|
||||||
var dynamic = false;
|
var dynamic = false;
|
||||||
|
var collisionless = true;
|
||||||
var position = Vec3.sum(MyAvatar.position, Vec3.multiply(2, Quat.getForward(MyAvatar.orientation)));
|
var position = Vec3.sum(MyAvatar.position, Vec3.multiply(2, Quat.getForward(MyAvatar.orientation)));
|
||||||
var gravity = Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 0);
|
var gravity = Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 0);
|
||||||
Entities.addModelEntity(name, modelURL, textures, shapeType, dynamic, position, gravity);
|
Entities.addModelEntity(name, modelURL, textures, shapeType, dynamic, collisionless, position, gravity);
|
||||||
} else {
|
} else {
|
||||||
var SHAPE_TYPE_NONE = 0;
|
var SHAPE_TYPE_NONE = 0;
|
||||||
var SHAPE_TYPE_SIMPLE_HULL = 1;
|
var SHAPE_TYPE_SIMPLE_HULL = 1;
|
||||||
|
@ -234,6 +235,7 @@ Windows.ScrollingWindow {
|
||||||
var result = JSON.parse(jsonResult);
|
var result = JSON.parse(jsonResult);
|
||||||
var url = result.textInput.trim();
|
var url = result.textInput.trim();
|
||||||
var shapeType;
|
var shapeType;
|
||||||
|
var collisionless = false;
|
||||||
switch (result.comboBox) {
|
switch (result.comboBox) {
|
||||||
case SHAPE_TYPE_SIMPLE_HULL:
|
case SHAPE_TYPE_SIMPLE_HULL:
|
||||||
shapeType = "simple-hull";
|
shapeType = "simple-hull";
|
||||||
|
@ -252,6 +254,7 @@ Windows.ScrollingWindow {
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
shapeType = "none";
|
shapeType = "none";
|
||||||
|
collisionless = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var dynamic = result.checkBox !== null ? result.checkBox : DYNAMIC_DEFAULT;
|
var dynamic = result.checkBox !== null ? result.checkBox : DYNAMIC_DEFAULT;
|
||||||
|
@ -273,7 +276,7 @@ Windows.ScrollingWindow {
|
||||||
print("Asset browser - adding asset " + url + " (" + name + ") to world.");
|
print("Asset browser - adding asset " + url + " (" + name + ") to world.");
|
||||||
|
|
||||||
// Entities.addEntity doesn't work from QML, so we use this.
|
// Entities.addEntity doesn't work from QML, so we use this.
|
||||||
Entities.addModelEntity(name, url, "", shapeType, dynamic, addPosition, gravity);
|
Entities.addModelEntity(name, url, "", shapeType, dynamic, collisionless, addPosition, gravity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -144,7 +144,7 @@ Rectangle {
|
||||||
}
|
}
|
||||||
|
|
||||||
function canAddToWorld(path) {
|
function canAddToWorld(path) {
|
||||||
var supportedExtensions = [/\.fbx\b/i, /\.obj\b/i];
|
var supportedExtensions = [/\.fbx\b/i, /\.obj\b/i, /\.jpg\b/i, /\.png\b/i];
|
||||||
|
|
||||||
if (selectedItemCount > 1) {
|
if (selectedItemCount > 1) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -188,9 +188,10 @@ Rectangle {
|
||||||
var textures = JSON.stringify({ "tex.picture": defaultURL});
|
var textures = JSON.stringify({ "tex.picture": defaultURL});
|
||||||
var shapeType = "box";
|
var shapeType = "box";
|
||||||
var dynamic = false;
|
var dynamic = false;
|
||||||
|
var collisionless = true;
|
||||||
var position = Vec3.sum(MyAvatar.position, Vec3.multiply(2, Quat.getForward(MyAvatar.orientation)));
|
var position = Vec3.sum(MyAvatar.position, Vec3.multiply(2, Quat.getForward(MyAvatar.orientation)));
|
||||||
var gravity = Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 0);
|
var gravity = Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 0);
|
||||||
Entities.addModelEntity(name, modelURL, textures, shapeType, dynamic, position, gravity);
|
Entities.addModelEntity(name, modelURL, textures, shapeType, dynamic, collisionless, position, gravity);
|
||||||
} else {
|
} else {
|
||||||
var SHAPE_TYPE_NONE = 0;
|
var SHAPE_TYPE_NONE = 0;
|
||||||
var SHAPE_TYPE_SIMPLE_HULL = 1;
|
var SHAPE_TYPE_SIMPLE_HULL = 1;
|
||||||
|
@ -235,6 +236,7 @@ Rectangle {
|
||||||
var result = JSON.parse(jsonResult);
|
var result = JSON.parse(jsonResult);
|
||||||
var url = result.textInput.trim();
|
var url = result.textInput.trim();
|
||||||
var shapeType;
|
var shapeType;
|
||||||
|
var collisionless = false;
|
||||||
switch (result.comboBox) {
|
switch (result.comboBox) {
|
||||||
case SHAPE_TYPE_SIMPLE_HULL:
|
case SHAPE_TYPE_SIMPLE_HULL:
|
||||||
shapeType = "simple-hull";
|
shapeType = "simple-hull";
|
||||||
|
@ -253,6 +255,7 @@ Rectangle {
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
shapeType = "none";
|
shapeType = "none";
|
||||||
|
collisionless = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var dynamic = result.checkBox !== null ? result.checkBox : DYNAMIC_DEFAULT;
|
var dynamic = result.checkBox !== null ? result.checkBox : DYNAMIC_DEFAULT;
|
||||||
|
@ -274,7 +277,7 @@ Rectangle {
|
||||||
print("Asset browser - adding asset " + url + " (" + name + ") to world.");
|
print("Asset browser - adding asset " + url + " (" + name + ") to world.");
|
||||||
|
|
||||||
// Entities.addEntity doesn't work from QML, so we use this.
|
// Entities.addEntity doesn't work from QML, so we use this.
|
||||||
Entities.addModelEntity(name, url, "", shapeType, dynamic, addPosition, gravity);
|
Entities.addModelEntity(name, url, "", shapeType, dynamic, collisionless, addPosition, gravity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -6754,8 +6754,8 @@ void Application::addAssetToWorldSetMapping(QString filePath, QString mapping, Q
|
||||||
addAssetToWorldError(filenameFromPath(filePath), errorInfo);
|
addAssetToWorldError(filenameFromPath(filePath), errorInfo);
|
||||||
} else {
|
} else {
|
||||||
// to prevent files that aren't models from being loaded into world automatically
|
// to prevent files that aren't models from being loaded into world automatically
|
||||||
if (filePath.endsWith(OBJ_EXTENSION) || filePath.endsWith(FBX_EXTENSION) ||
|
if (filePath.toLower().endsWith(OBJ_EXTENSION) || filePath.toLower().endsWith(FBX_EXTENSION) ||
|
||||||
filePath.endsWith(JPG_EXTENSION) || filePath.endsWith(PNG_EXTENSION)) {
|
filePath.toLower().endsWith(JPG_EXTENSION) || filePath.toLower().endsWith(PNG_EXTENSION)) {
|
||||||
addAssetToWorldAddEntity(filePath, mapping);
|
addAssetToWorldAddEntity(filePath, mapping);
|
||||||
} else {
|
} else {
|
||||||
qCDebug(interfaceapp) << "Zipped contents are not supported entity files";
|
qCDebug(interfaceapp) << "Zipped contents are not supported entity files";
|
||||||
|
@ -6772,7 +6772,7 @@ void Application::addAssetToWorldAddEntity(QString filePath, QString mapping) {
|
||||||
EntityItemProperties properties;
|
EntityItemProperties properties;
|
||||||
properties.setType(EntityTypes::Model);
|
properties.setType(EntityTypes::Model);
|
||||||
properties.setName(mapping.right(mapping.length() - 1));
|
properties.setName(mapping.right(mapping.length() - 1));
|
||||||
if (filePath.endsWith(PNG_EXTENSION) || filePath.endsWith(JPG_EXTENSION)) {
|
if (filePath.toLower().endsWith(PNG_EXTENSION) || filePath.toLower().endsWith(JPG_EXTENSION)) {
|
||||||
QJsonObject textures {
|
QJsonObject textures {
|
||||||
{"tex.picture", QString("atp:" + mapping) }
|
{"tex.picture", QString("atp:" + mapping) }
|
||||||
};
|
};
|
||||||
|
@ -6868,7 +6868,9 @@ void Application::addAssetToWorldCheckModelSize() {
|
||||||
EntityItemProperties properties;
|
EntityItemProperties properties;
|
||||||
properties.setDimensions(dimensions);
|
properties.setDimensions(dimensions);
|
||||||
properties.setVisible(true);
|
properties.setVisible(true);
|
||||||
properties.setCollisionless(false);
|
if (!name.toLower().endsWith(PNG_EXTENSION) && !name.toLower().endsWith(JPG_EXTENSION)) {
|
||||||
|
properties.setCollisionless(false);
|
||||||
|
}
|
||||||
properties.setUserData(GRABBABLE_USER_DATA);
|
properties.setUserData(GRABBABLE_USER_DATA);
|
||||||
properties.setLastEdited(usecTimestampNow());
|
properties.setLastEdited(usecTimestampNow());
|
||||||
entityScriptingInterface->editEntity(entityID, properties);
|
entityScriptingInterface->editEntity(entityID, properties);
|
||||||
|
|
|
@ -482,7 +482,7 @@ void Stats::updateStats(bool force) {
|
||||||
float dt = (float)itr.value().getMovingAverage() / (float)USECS_PER_MSEC;
|
float dt = (float)itr.value().getMovingAverage() / (float)USECS_PER_MSEC;
|
||||||
_gameUpdateStats = QString("/idle/update = %1 ms").arg(dt);
|
_gameUpdateStats = QString("/idle/update = %1 ms").arg(dt);
|
||||||
|
|
||||||
QVector<QString> categories = { "devices", "physics", "otherAvatars", "MyAvatar", "misc" };
|
QVector<QString> categories = { "devices", "physics", "otherAvatars", "MyAvatar", "pickManager", "postUpdateLambdas", "misc" };
|
||||||
for (int32_t j = 0; j < categories.size(); ++j) {
|
for (int32_t j = 0; j < categories.size(); ++j) {
|
||||||
QString recordKey = "/idle/update/" + categories[j];
|
QString recordKey = "/idle/update/" + categories[j];
|
||||||
itr = allRecords.find(recordKey);
|
itr = allRecords.find(recordKey);
|
||||||
|
@ -502,10 +502,39 @@ void Stats::updateStats(bool force) {
|
||||||
_gameUpdateStats = "";
|
_gameUpdateStats = "";
|
||||||
emit gameUpdateStatsChanged();
|
emit gameUpdateStatsChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
itr = allRecords.find("/paintGL/display/EngineRun/Engine");
|
||||||
|
std::priority_queue<SortableStat> renderEngineStats;
|
||||||
|
if (itr != allRecords.end()) {
|
||||||
|
float dt = (float)itr.value().getMovingAverage() / (float)USECS_PER_MSEC;
|
||||||
|
_renderEngineStats = QString("/render = %1 ms").arg(dt);
|
||||||
|
|
||||||
|
QVector<QString> categories = { "RenderMainView", "SecondaryCameraJob", "UpdateScene"};
|
||||||
|
for (int32_t j = 0; j < categories.size(); ++j) {
|
||||||
|
QString recordKey = "/paintGL/display/EngineRun/Engine/" + categories[j];
|
||||||
|
itr = allRecords.find(recordKey);
|
||||||
|
if (itr != allRecords.end()) {
|
||||||
|
float dt = (float)itr.value().getMovingAverage() / (float)USECS_PER_MSEC;
|
||||||
|
QString message = QString("\n %1 = %2").arg(categories[j]).arg(dt);
|
||||||
|
renderEngineStats.push(SortableStat(message, dt));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (!renderEngineStats.empty()) {
|
||||||
|
SortableStat stat = renderEngineStats.top();
|
||||||
|
_renderEngineStats += stat.message;
|
||||||
|
renderEngineStats.pop();
|
||||||
|
}
|
||||||
|
emit renderEngineStatsChanged();
|
||||||
|
} else if (_renderEngineStats != "") {
|
||||||
|
_renderEngineStats = "";
|
||||||
|
emit renderEngineStatsChanged();
|
||||||
|
}
|
||||||
} else if (_showGameUpdateStats) {
|
} else if (_showGameUpdateStats) {
|
||||||
_showGameUpdateStats = false;
|
_showGameUpdateStats = false;
|
||||||
_gameUpdateStats = "";
|
_gameUpdateStats = "";
|
||||||
|
_renderEngineStats = "";
|
||||||
emit gameUpdateStatsChanged();
|
emit gameUpdateStatsChanged();
|
||||||
|
emit renderEngineStatsChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -106,6 +106,7 @@ class Stats : public QQuickItem {
|
||||||
STATS_PROPERTY(QString, lodStatus, QString())
|
STATS_PROPERTY(QString, lodStatus, QString())
|
||||||
STATS_PROPERTY(QString, timingStats, QString())
|
STATS_PROPERTY(QString, timingStats, QString())
|
||||||
STATS_PROPERTY(QString, gameUpdateStats, QString())
|
STATS_PROPERTY(QString, gameUpdateStats, QString())
|
||||||
|
STATS_PROPERTY(QString, renderEngineStats, QString())
|
||||||
STATS_PROPERTY(int, serverElements, 0)
|
STATS_PROPERTY(int, serverElements, 0)
|
||||||
STATS_PROPERTY(int, serverInternal, 0)
|
STATS_PROPERTY(int, serverInternal, 0)
|
||||||
STATS_PROPERTY(int, serverLeaves, 0)
|
STATS_PROPERTY(int, serverLeaves, 0)
|
||||||
|
@ -239,6 +240,7 @@ signals:
|
||||||
void localLeavesChanged();
|
void localLeavesChanged();
|
||||||
void timingStatsChanged();
|
void timingStatsChanged();
|
||||||
void gameUpdateStatsChanged();
|
void gameUpdateStatsChanged();
|
||||||
|
void renderEngineStatsChanged();
|
||||||
void glContextSwapchainMemoryChanged();
|
void glContextSwapchainMemoryChanged();
|
||||||
void qmlTextureMemoryChanged();
|
void qmlTextureMemoryChanged();
|
||||||
void texturePendingTransfersChanged();
|
void texturePendingTransfersChanged();
|
||||||
|
|
|
@ -304,8 +304,9 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QUuid EntityScriptingInterface::addModelEntity(const QString& name, const QString& modelUrl, const QString& textures, const QString& shapeType,
|
QUuid EntityScriptingInterface::addModelEntity(const QString& name, const QString& modelUrl, const QString& textures,
|
||||||
bool dynamic, const glm::vec3& position, const glm::vec3& gravity) {
|
const QString& shapeType, bool dynamic, bool collisionless,
|
||||||
|
const glm::vec3& position, const glm::vec3& gravity) {
|
||||||
_activityTracking.addedEntityCount++;
|
_activityTracking.addedEntityCount++;
|
||||||
|
|
||||||
EntityItemProperties properties;
|
EntityItemProperties properties;
|
||||||
|
@ -314,6 +315,7 @@ QUuid EntityScriptingInterface::addModelEntity(const QString& name, const QStrin
|
||||||
properties.setModelURL(modelUrl);
|
properties.setModelURL(modelUrl);
|
||||||
properties.setShapeTypeFromString(shapeType);
|
properties.setShapeTypeFromString(shapeType);
|
||||||
properties.setDynamic(dynamic);
|
properties.setDynamic(dynamic);
|
||||||
|
properties.setCollisionless(collisionless);
|
||||||
properties.setPosition(position);
|
properties.setPosition(position);
|
||||||
properties.setGravity(gravity);
|
properties.setGravity(gravity);
|
||||||
if (!textures.isEmpty()) {
|
if (!textures.isEmpty()) {
|
||||||
|
|
|
@ -165,7 +165,7 @@ public slots:
|
||||||
/// temporary method until addEntity can be used from QJSEngine
|
/// temporary method until addEntity can be used from QJSEngine
|
||||||
/// Deliberately not adding jsdoc, only used internally.
|
/// Deliberately not adding jsdoc, only used internally.
|
||||||
Q_INVOKABLE QUuid addModelEntity(const QString& name, const QString& modelUrl, const QString& textures, const QString& shapeType, bool dynamic,
|
Q_INVOKABLE QUuid addModelEntity(const QString& name, const QString& modelUrl, const QString& textures, const QString& shapeType, bool dynamic,
|
||||||
const glm::vec3& position, const glm::vec3& gravity);
|
bool collisionless, const glm::vec3& position, const glm::vec3& gravity);
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
* Return the properties for the specified {EntityID}.
|
* Return the properties for the specified {EntityID}.
|
||||||
|
|
|
@ -565,11 +565,12 @@ var toolBar = (function () {
|
||||||
createNewEntity({
|
createNewEntity({
|
||||||
type: "Model",
|
type: "Model",
|
||||||
dimensions: {
|
dimensions: {
|
||||||
x: 4.16,
|
x: 0.5385,
|
||||||
y: 0.02,
|
y: 0.2819,
|
||||||
z: 2.58
|
z: 0.0092
|
||||||
},
|
},
|
||||||
shapeType: "box",
|
shapeType: "box",
|
||||||
|
collisionless: true,
|
||||||
modelURL: IMAGE_MODEL,
|
modelURL: IMAGE_MODEL,
|
||||||
textures: JSON.stringify({ "tex.picture": DEFAULT_IMAGE })
|
textures: JSON.stringify({ "tex.picture": DEFAULT_IMAGE })
|
||||||
});
|
});
|
||||||
|
|
|
@ -93,6 +93,9 @@ void makeTestFBXJoints(FBXGeometry& geometry) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnimInverseKinematicsTests::testSingleChain() {
|
void AnimInverseKinematicsTests::testSingleChain() {
|
||||||
|
|
||||||
|
AnimContext context(false, false, false, glm::mat4(), glm::mat4());
|
||||||
|
|
||||||
FBXGeometry geometry;
|
FBXGeometry geometry;
|
||||||
makeTestFBXJoints(geometry);
|
makeTestFBXJoints(geometry);
|
||||||
|
|
||||||
|
@ -108,14 +111,14 @@ void AnimInverseKinematicsTests::testSingleChain() {
|
||||||
//
|
//
|
||||||
// A------>B------>C------>D
|
// A------>B------>C------>D
|
||||||
AnimPose pose;
|
AnimPose pose;
|
||||||
pose.scale = glm::vec3(1.0f);
|
pose.scale() = glm::vec3(1.0f);
|
||||||
pose.rot = identity;
|
pose.rot() = identity;
|
||||||
pose.trans = origin;
|
pose.trans() = origin;
|
||||||
|
|
||||||
AnimPoseVec poses;
|
AnimPoseVec poses;
|
||||||
poses.push_back(pose);
|
poses.push_back(pose);
|
||||||
|
|
||||||
pose.trans = xAxis;
|
pose.trans() = xAxis;
|
||||||
for (int i = 1; i < (int)geometry.joints.size(); ++i) {
|
for (int i = 1; i < (int)geometry.joints.size(); ++i) {
|
||||||
poses.push_back(pose);
|
poses.push_back(pose);
|
||||||
}
|
}
|
||||||
|
@ -133,8 +136,13 @@ void AnimInverseKinematicsTests::testSingleChain() {
|
||||||
AnimVariantMap varMap;
|
AnimVariantMap varMap;
|
||||||
varMap.set("positionD", targetPosition);
|
varMap.set("positionD", targetPosition);
|
||||||
varMap.set("rotationD", targetRotation);
|
varMap.set("rotationD", targetRotation);
|
||||||
varMap.set("targetType", (int)IKTarget::Type::RotationAndPosition);
|
varMap.set("targetTypeD", (int)IKTarget::Type::RotationAndPosition);
|
||||||
ikDoll.setTargetVars(QString("D"), QString("positionD"), QString("rotationD"), QString("targetType"));
|
varMap.set("poleVectorEnabledD", false);
|
||||||
|
|
||||||
|
std::vector<float> flexCoefficients = {1.0f, 1.0f, 1.0f, 1.0f};
|
||||||
|
ikDoll.setTargetVars(QString("D"), QString("positionD"), QString("rotationD"), QString("targetTypeD"),
|
||||||
|
QString("weightD"), 1.0f, flexCoefficients, QString("poleVectorEnabledD"),
|
||||||
|
QString("poleReferenceVectorD"), QString("poleVectorD"));
|
||||||
AnimNode::Triggers triggers;
|
AnimNode::Triggers triggers;
|
||||||
|
|
||||||
// the IK solution should be:
|
// the IK solution should be:
|
||||||
|
@ -147,14 +155,12 @@ void AnimInverseKinematicsTests::testSingleChain() {
|
||||||
|
|
||||||
// iterate several times
|
// iterate several times
|
||||||
float dt = 1.0f;
|
float dt = 1.0f;
|
||||||
ikDoll.overlay(varMap, dt, triggers, poses);
|
|
||||||
ikDoll.overlay(varMap, dt, triggers, poses);
|
|
||||||
ikDoll.overlay(varMap, dt, triggers, poses);
|
|
||||||
|
|
||||||
ikDoll.overlay(varMap, dt, triggers, poses);
|
const int NUM_FRAMES = 10;
|
||||||
ikDoll.overlay(varMap, dt, triggers, poses);
|
for (int i = 0; i < NUM_FRAMES; i++) {
|
||||||
ikDoll.overlay(varMap, dt, triggers, poses);
|
poses = ikDoll.overlay(varMap, context, dt, triggers, poses);
|
||||||
const AnimPoseVec& relativePoses = ikDoll.overlay(varMap, dt, triggers, poses);
|
}
|
||||||
|
const AnimPoseVec& relativePoses = ikDoll.overlay(varMap, context, dt, triggers, poses);
|
||||||
|
|
||||||
// verify absolute results
|
// verify absolute results
|
||||||
// NOTE: since we expect this solution to converge very quickly (one loop)
|
// NOTE: since we expect this solution to converge very quickly (one loop)
|
||||||
|
@ -164,28 +170,30 @@ void AnimInverseKinematicsTests::testSingleChain() {
|
||||||
absolutePoses.push_back(pose);
|
absolutePoses.push_back(pose);
|
||||||
}
|
}
|
||||||
ikDoll.computeAbsolutePoses(absolutePoses);
|
ikDoll.computeAbsolutePoses(absolutePoses);
|
||||||
|
|
||||||
const float acceptableAngleError = 0.001f;
|
const float acceptableAngleError = 0.001f;
|
||||||
QCOMPARE_QUATS(absolutePoses[0].rot, identity, acceptableAngleError);
|
QCOMPARE_QUATS(absolutePoses[0].rot(), identity, acceptableAngleError);
|
||||||
QCOMPARE_QUATS(absolutePoses[1].rot, identity, acceptableAngleError);
|
QCOMPARE_QUATS(absolutePoses[1].rot(), identity, acceptableAngleError);
|
||||||
QCOMPARE_QUATS(absolutePoses[2].rot, quaterTurnAroundZ, acceptableAngleError);
|
QCOMPARE_QUATS(absolutePoses[2].rot(), quaterTurnAroundZ, acceptableAngleError);
|
||||||
QCOMPARE_QUATS(absolutePoses[3].rot, quaterTurnAroundZ, acceptableAngleError);
|
QCOMPARE_QUATS(absolutePoses[3].rot(), quaterTurnAroundZ, acceptableAngleError);
|
||||||
|
|
||||||
const float acceptableTranslationError = 0.025f;
|
const float acceptableTranslationError = 0.025f;
|
||||||
QCOMPARE_WITH_ABS_ERROR(absolutePoses[0].trans, origin, acceptableTranslationError);
|
QCOMPARE_WITH_ABS_ERROR(absolutePoses[0].trans(), origin, acceptableTranslationError);
|
||||||
QCOMPARE_WITH_ABS_ERROR(absolutePoses[1].trans, xAxis, acceptableTranslationError);
|
QCOMPARE_WITH_ABS_ERROR(absolutePoses[1].trans(), xAxis, acceptableTranslationError);
|
||||||
QCOMPARE_WITH_ABS_ERROR(absolutePoses[2].trans, 2.0f * xAxis, acceptableTranslationError);
|
QCOMPARE_WITH_ABS_ERROR(absolutePoses[2].trans(), 2.0f * xAxis, acceptableTranslationError);
|
||||||
QCOMPARE_WITH_ABS_ERROR(absolutePoses[3].trans, targetPosition, acceptableTranslationError);
|
QCOMPARE_WITH_ABS_ERROR(absolutePoses[3].trans(), targetPosition, acceptableTranslationError);
|
||||||
|
|
||||||
// verify relative results
|
// verify relative results
|
||||||
QCOMPARE_QUATS(relativePoses[0].rot, identity, acceptableAngleError);
|
QCOMPARE_QUATS(relativePoses[0].rot(), identity, acceptableAngleError);
|
||||||
QCOMPARE_QUATS(relativePoses[1].rot, identity, acceptableAngleError);
|
QCOMPARE_QUATS(relativePoses[1].rot(), identity, acceptableAngleError);
|
||||||
QCOMPARE_QUATS(relativePoses[2].rot, quaterTurnAroundZ, acceptableAngleError);
|
QCOMPARE_QUATS(relativePoses[2].rot(), quaterTurnAroundZ, acceptableAngleError);
|
||||||
QCOMPARE_QUATS(relativePoses[3].rot, identity, acceptableAngleError);
|
QCOMPARE_QUATS(relativePoses[3].rot(), identity, acceptableAngleError);
|
||||||
|
|
||||||
|
QCOMPARE_WITH_ABS_ERROR(relativePoses[0].trans(), origin, acceptableTranslationError);
|
||||||
|
QCOMPARE_WITH_ABS_ERROR(relativePoses[1].trans(), xAxis, acceptableTranslationError);
|
||||||
|
QCOMPARE_WITH_ABS_ERROR(relativePoses[2].trans(), xAxis, acceptableTranslationError);
|
||||||
|
QCOMPARE_WITH_ABS_ERROR(relativePoses[3].trans(), xAxis, acceptableTranslationError);
|
||||||
|
|
||||||
QCOMPARE_WITH_ABS_ERROR(relativePoses[0].trans, origin, acceptableTranslationError);
|
|
||||||
QCOMPARE_WITH_ABS_ERROR(relativePoses[1].trans, xAxis, acceptableTranslationError);
|
|
||||||
QCOMPARE_WITH_ABS_ERROR(relativePoses[2].trans, xAxis, acceptableTranslationError);
|
|
||||||
QCOMPARE_WITH_ABS_ERROR(relativePoses[3].trans, xAxis, acceptableTranslationError);
|
|
||||||
}
|
}
|
||||||
{ // hard test IK of joint C
|
{ // hard test IK of joint C
|
||||||
// load intial poses that look like this:
|
// load intial poses that look like this:
|
||||||
|
@ -196,15 +204,15 @@ void AnimInverseKinematicsTests::testSingleChain() {
|
||||||
// A------>B
|
// A------>B
|
||||||
//
|
//
|
||||||
AnimPose pose;
|
AnimPose pose;
|
||||||
pose.scale = glm::vec3(1.0f);
|
pose.scale() = glm::vec3(1.0f);
|
||||||
pose.rot = identity;
|
pose.rot() = identity;
|
||||||
pose.trans = origin;
|
pose.trans() = origin;
|
||||||
|
|
||||||
AnimPoseVec poses;
|
AnimPoseVec poses;
|
||||||
poses.push_back(pose);
|
poses.push_back(pose);
|
||||||
pose.trans = xAxis;
|
pose.trans() = xAxis;
|
||||||
|
|
||||||
pose.rot = quaterTurnAroundZ;
|
pose.rot() = quaterTurnAroundZ;
|
||||||
poses.push_back(pose);
|
poses.push_back(pose);
|
||||||
poses.push_back(pose);
|
poses.push_back(pose);
|
||||||
poses.push_back(pose);
|
poses.push_back(pose);
|
||||||
|
@ -222,8 +230,12 @@ void AnimInverseKinematicsTests::testSingleChain() {
|
||||||
AnimVariantMap varMap;
|
AnimVariantMap varMap;
|
||||||
varMap.set("positionD", targetPosition);
|
varMap.set("positionD", targetPosition);
|
||||||
varMap.set("rotationD", targetRotation);
|
varMap.set("rotationD", targetRotation);
|
||||||
varMap.set("targetType", (int)IKTarget::Type::RotationAndPosition);
|
varMap.set("targetTypeD", (int)IKTarget::Type::RotationAndPosition);
|
||||||
ikDoll.setTargetVars(QString("D"), QString("positionD"), QString("rotationD"), QString("targetType"));
|
varMap.set("poleVectorEnabledD", false);
|
||||||
|
std::vector<float> flexCoefficients = {1.0f, 1.0f, 1.0f, 1.0f};
|
||||||
|
ikDoll.setTargetVars(QString("D"), QString("positionD"), QString("rotationD"), QString("targetTypeD"),
|
||||||
|
QString("weightD"), 1.0f, flexCoefficients, QString("poleVectorEnabledD"),
|
||||||
|
QString("poleReferenceVectorD"), QString("poleVectorD"));
|
||||||
AnimNode::Triggers triggers;
|
AnimNode::Triggers triggers;
|
||||||
|
|
||||||
// the IK solution should be:
|
// the IK solution should be:
|
||||||
|
@ -233,15 +245,11 @@ void AnimInverseKinematicsTests::testSingleChain() {
|
||||||
|
|
||||||
// iterate several times
|
// iterate several times
|
||||||
float dt = 1.0f;
|
float dt = 1.0f;
|
||||||
ikDoll.overlay(varMap, dt, triggers, poses);
|
const int NUM_FRAMES = 50;
|
||||||
ikDoll.overlay(varMap, dt, triggers, poses);
|
for (int i = 0; i < NUM_FRAMES; i++) {
|
||||||
ikDoll.overlay(varMap, dt, triggers, poses);
|
poses = ikDoll.overlay(varMap, context, dt, triggers, poses);
|
||||||
ikDoll.overlay(varMap, dt, triggers, poses);
|
}
|
||||||
ikDoll.overlay(varMap, dt, triggers, poses);
|
const AnimPoseVec& relativePoses = ikDoll.overlay(varMap, context, dt, triggers, poses);
|
||||||
ikDoll.overlay(varMap, dt, triggers, poses);
|
|
||||||
ikDoll.overlay(varMap, dt, triggers, poses);
|
|
||||||
ikDoll.overlay(varMap, dt, triggers, poses);
|
|
||||||
const AnimPoseVec& relativePoses = ikDoll.overlay(varMap, dt, triggers, poses);
|
|
||||||
|
|
||||||
// verify absolute results
|
// verify absolute results
|
||||||
// NOTE: the IK algorithm doesn't converge very fast for full-reach targets,
|
// NOTE: the IK algorithm doesn't converge very fast for full-reach targets,
|
||||||
|
@ -255,28 +263,29 @@ void AnimInverseKinematicsTests::testSingleChain() {
|
||||||
absolutePoses.push_back(pose);
|
absolutePoses.push_back(pose);
|
||||||
}
|
}
|
||||||
ikDoll.computeAbsolutePoses(absolutePoses);
|
ikDoll.computeAbsolutePoses(absolutePoses);
|
||||||
float acceptableAngle = 0.01f; // radians
|
|
||||||
QCOMPARE_QUATS(absolutePoses[0].rot, identity, acceptableAngle);
|
|
||||||
QCOMPARE_QUATS(absolutePoses[1].rot, identity, acceptableAngle);
|
|
||||||
QCOMPARE_QUATS(absolutePoses[2].rot, identity, acceptableAngle);
|
|
||||||
QCOMPARE_QUATS(absolutePoses[3].rot, identity, acceptableAngle);
|
|
||||||
|
|
||||||
float acceptableDistance = 0.03f;
|
float acceptableAngle = 0.01f; // radians
|
||||||
QCOMPARE_WITH_ABS_ERROR(absolutePoses[0].trans, origin, acceptableDistance);
|
QCOMPARE_QUATS(absolutePoses[0].rot(), identity, acceptableAngle);
|
||||||
QCOMPARE_WITH_ABS_ERROR(absolutePoses[1].trans, xAxis, acceptableDistance);
|
QCOMPARE_QUATS(absolutePoses[1].rot(), identity, acceptableAngle);
|
||||||
QCOMPARE_WITH_ABS_ERROR(absolutePoses[2].trans, 2.0f * xAxis, acceptableDistance);
|
QCOMPARE_QUATS(absolutePoses[2].rot(), identity, acceptableAngle);
|
||||||
QCOMPARE_WITH_ABS_ERROR(absolutePoses[3].trans, 3.0f * xAxis, acceptableDistance);
|
QCOMPARE_QUATS(absolutePoses[3].rot(), identity, acceptableAngle);
|
||||||
|
|
||||||
|
float acceptableDistance = 0.1f;
|
||||||
|
QCOMPARE_WITH_ABS_ERROR(absolutePoses[0].trans(), origin, acceptableDistance);
|
||||||
|
QCOMPARE_WITH_ABS_ERROR(absolutePoses[1].trans(), xAxis, acceptableDistance);
|
||||||
|
QCOMPARE_WITH_ABS_ERROR(absolutePoses[2].trans(), 2.0f * xAxis, acceptableDistance);
|
||||||
|
QCOMPARE_WITH_ABS_ERROR(absolutePoses[3].trans(), 3.0f * xAxis, acceptableDistance);
|
||||||
|
|
||||||
// verify relative results
|
// verify relative results
|
||||||
QCOMPARE_QUATS(relativePoses[0].rot, identity, acceptableAngle);
|
QCOMPARE_QUATS(relativePoses[0].rot(), identity, acceptableAngle);
|
||||||
QCOMPARE_QUATS(relativePoses[1].rot, identity, acceptableAngle);
|
QCOMPARE_QUATS(relativePoses[1].rot(), identity, acceptableAngle);
|
||||||
QCOMPARE_QUATS(relativePoses[2].rot, identity, acceptableAngle);
|
QCOMPARE_QUATS(relativePoses[2].rot(), identity, acceptableAngle);
|
||||||
QCOMPARE_QUATS(relativePoses[3].rot, identity, acceptableAngle);
|
QCOMPARE_QUATS(relativePoses[3].rot(), identity, acceptableAngle);
|
||||||
|
|
||||||
QCOMPARE_WITH_ABS_ERROR(relativePoses[0].trans, origin, acceptableDistance);
|
QCOMPARE_WITH_ABS_ERROR(relativePoses[0].trans(), origin, acceptableDistance);
|
||||||
QCOMPARE_WITH_ABS_ERROR(relativePoses[1].trans, xAxis, acceptableDistance);
|
QCOMPARE_WITH_ABS_ERROR(relativePoses[1].trans(), xAxis, acceptableDistance);
|
||||||
QCOMPARE_WITH_ABS_ERROR(relativePoses[2].trans, xAxis, acceptableDistance);
|
QCOMPARE_WITH_ABS_ERROR(relativePoses[2].trans(), xAxis, acceptableDistance);
|
||||||
QCOMPARE_WITH_ABS_ERROR(relativePoses[3].trans, xAxis, acceptableDistance);
|
QCOMPARE_WITH_ABS_ERROR(relativePoses[3].trans(), xAxis, acceptableDistance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -293,6 +302,6 @@ void AnimInverseKinematicsTests::testBar() {
|
||||||
AnimPose poseC = poseA * poseB;
|
AnimPose poseC = poseA * poseB;
|
||||||
|
|
||||||
glm::vec3 expectedTransC = transA + transB;
|
glm::vec3 expectedTransC = transA + transB;
|
||||||
QCOMPARE_WITH_ABS_ERROR(expectedTransC, poseC.trans, EPSILON);
|
QCOMPARE_WITH_ABS_ERROR(expectedTransC, poseC.trans(), EPSILON);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,11 @@
|
||||||
#include <AnimVariant.h>
|
#include <AnimVariant.h>
|
||||||
#include <AnimExpression.h>
|
#include <AnimExpression.h>
|
||||||
#include <AnimUtil.h>
|
#include <AnimUtil.h>
|
||||||
|
#include <NodeList.h>
|
||||||
|
#include <AddressManager.h>
|
||||||
|
#include <AccountManager.h>
|
||||||
|
#include <ResourceManager.h>
|
||||||
|
#include <StatTracker.h>
|
||||||
#include <../QTestExtensions.h>
|
#include <../QTestExtensions.h>
|
||||||
|
|
||||||
QTEST_MAIN(AnimTests)
|
QTEST_MAIN(AnimTests)
|
||||||
|
@ -23,12 +27,18 @@ QTEST_MAIN(AnimTests)
|
||||||
const float EPSILON = 0.001f;
|
const float EPSILON = 0.001f;
|
||||||
|
|
||||||
void AnimTests::initTestCase() {
|
void AnimTests::initTestCase() {
|
||||||
auto animationCache = DependencyManager::set<AnimationCache>();
|
DependencyManager::registerInheritance<LimitedNodeList, NodeList>();
|
||||||
auto resourceCacheSharedItems = DependencyManager::set<ResourceCacheSharedItems>();
|
DependencyManager::set<AccountManager>();
|
||||||
|
DependencyManager::set<AddressManager>();
|
||||||
|
DependencyManager::set<NodeList>(NodeType::Agent);
|
||||||
|
DependencyManager::set<ResourceManager>();
|
||||||
|
DependencyManager::set<AnimationCache>();
|
||||||
|
DependencyManager::set<ResourceCacheSharedItems>();
|
||||||
|
DependencyManager::set<StatTracker>();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnimTests::cleanupTestCase() {
|
void AnimTests::cleanupTestCase() {
|
||||||
DependencyManager::destroy<AnimationCache>();
|
//DependencyManager::destroy<AnimationCache>();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnimTests::testClipInternalState() {
|
void AnimTests::testClipInternalState() {
|
||||||
|
@ -59,6 +69,7 @@ static float framesToSec(float secs) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnimTests::testClipEvaulate() {
|
void AnimTests::testClipEvaulate() {
|
||||||
|
AnimContext context(false, false, false, glm::mat4(), glm::mat4());
|
||||||
QString id = "myClipNode";
|
QString id = "myClipNode";
|
||||||
QString url = "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_idle.fbx";
|
QString url = "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_idle.fbx";
|
||||||
float startFrame = 2.0f;
|
float startFrame = 2.0f;
|
||||||
|
@ -73,12 +84,12 @@ void AnimTests::testClipEvaulate() {
|
||||||
AnimClip clip(id, url, startFrame, endFrame, timeScale, loopFlag, mirrorFlag);
|
AnimClip clip(id, url, startFrame, endFrame, timeScale, loopFlag, mirrorFlag);
|
||||||
|
|
||||||
AnimNode::Triggers triggers;
|
AnimNode::Triggers triggers;
|
||||||
clip.evaluate(vars, framesToSec(10.0f), triggers);
|
clip.evaluate(vars, context, framesToSec(10.0f), triggers);
|
||||||
QCOMPARE_WITH_ABS_ERROR(clip._frame, 12.0f, EPSILON);
|
QCOMPARE_WITH_ABS_ERROR(clip._frame, 12.0f, EPSILON);
|
||||||
|
|
||||||
// does it loop?
|
// does it loop?
|
||||||
triggers.clear();
|
triggers.clear();
|
||||||
clip.evaluate(vars, framesToSec(12.0f), triggers);
|
clip.evaluate(vars, context, framesToSec(12.0f), triggers);
|
||||||
QCOMPARE_WITH_ABS_ERROR(clip._frame, 3.0f, EPSILON); // Note: frame 3 and not 4, because extra frame between start and end.
|
QCOMPARE_WITH_ABS_ERROR(clip._frame, 3.0f, EPSILON); // Note: frame 3 and not 4, because extra frame between start and end.
|
||||||
|
|
||||||
// did we receive a loop trigger?
|
// did we receive a loop trigger?
|
||||||
|
@ -87,7 +98,7 @@ void AnimTests::testClipEvaulate() {
|
||||||
// does it pause at end?
|
// does it pause at end?
|
||||||
triggers.clear();
|
triggers.clear();
|
||||||
clip.setLoopFlagVar("FalseVar");
|
clip.setLoopFlagVar("FalseVar");
|
||||||
clip.evaluate(vars, framesToSec(20.0f), triggers);
|
clip.evaluate(vars, context, framesToSec(20.0f), triggers);
|
||||||
QCOMPARE_WITH_ABS_ERROR(clip._frame, 22.0f, EPSILON);
|
QCOMPARE_WITH_ABS_ERROR(clip._frame, 22.0f, EPSILON);
|
||||||
|
|
||||||
// did we receive a done trigger?
|
// did we receive a done trigger?
|
||||||
|
@ -95,6 +106,7 @@ void AnimTests::testClipEvaulate() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnimTests::testClipEvaulateWithVars() {
|
void AnimTests::testClipEvaulateWithVars() {
|
||||||
|
AnimContext context(false, false, false, glm::mat4(), glm::mat4());
|
||||||
QString id = "myClipNode";
|
QString id = "myClipNode";
|
||||||
QString url = "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_idle.fbx";
|
QString url = "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_idle.fbx";
|
||||||
float startFrame = 2.0f;
|
float startFrame = 2.0f;
|
||||||
|
@ -121,7 +133,7 @@ void AnimTests::testClipEvaulateWithVars() {
|
||||||
clip.setLoopFlagVar("loopFlag2");
|
clip.setLoopFlagVar("loopFlag2");
|
||||||
|
|
||||||
AnimNode::Triggers triggers;
|
AnimNode::Triggers triggers;
|
||||||
clip.evaluate(vars, framesToSec(0.1f), triggers);
|
clip.evaluate(vars, context, framesToSec(0.1f), triggers);
|
||||||
|
|
||||||
// verify that the values from the AnimVariantMap made it into the clipNode's
|
// verify that the values from the AnimVariantMap made it into the clipNode's
|
||||||
// internal state
|
// internal state
|
||||||
|
@ -132,7 +144,7 @@ void AnimTests::testClipEvaulateWithVars() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnimTests::testLoader() {
|
void AnimTests::testLoader() {
|
||||||
auto url = QUrl("https://gist.githubusercontent.com/hyperlogic/857129fe04567cbe670f/raw/0c54500f480fd7314a5aeb147c45a8a707edcc2e/test.json");
|
auto url = QUrl("https://gist.githubusercontent.com/hyperlogic/756e6b7018c96c9778dba4ffb959c3c7/raw/4b37f10c9d2636608916208ba7b415c1a3f842ff/test.json");
|
||||||
// NOTE: This will warn about missing "test01.fbx", "test02.fbx", etc. if the resource loading code doesn't handle relative pathnames!
|
// NOTE: This will warn about missing "test01.fbx", "test02.fbx", etc. if the resource loading code doesn't handle relative pathnames!
|
||||||
// However, the test will proceed.
|
// However, the test will proceed.
|
||||||
AnimNodeLoader loader(url);
|
AnimNodeLoader loader(url);
|
||||||
|
@ -173,14 +185,22 @@ void AnimTests::testLoader() {
|
||||||
QVERIFY(nodes[2]->getChildCount() == 0);
|
QVERIFY(nodes[2]->getChildCount() == 0);
|
||||||
|
|
||||||
auto test01 = std::static_pointer_cast<AnimClip>(nodes[0]);
|
auto test01 = std::static_pointer_cast<AnimClip>(nodes[0]);
|
||||||
QVERIFY(test01->_url == "test01.fbx");
|
|
||||||
|
QUrl relativeUrl01("test01.fbx");
|
||||||
|
QString url01 = url.resolved(relativeUrl01).toString();
|
||||||
|
|
||||||
|
QVERIFY(test01->_url == url01);
|
||||||
QVERIFY(test01->_startFrame == 1.0f);
|
QVERIFY(test01->_startFrame == 1.0f);
|
||||||
QVERIFY(test01->_endFrame == 20.0f);
|
QVERIFY(test01->_endFrame == 20.0f);
|
||||||
QVERIFY(test01->_timeScale == 1.0f);
|
QVERIFY(test01->_timeScale == 1.0f);
|
||||||
QVERIFY(test01->_loopFlag == false);
|
QVERIFY(test01->_loopFlag == false);
|
||||||
|
|
||||||
auto test02 = std::static_pointer_cast<AnimClip>(nodes[1]);
|
auto test02 = std::static_pointer_cast<AnimClip>(nodes[1]);
|
||||||
QVERIFY(test02->_url == "test02.fbx");
|
|
||||||
|
QUrl relativeUrl02("test02.fbx");
|
||||||
|
QString url02 = url.resolved(relativeUrl02).toString();
|
||||||
|
|
||||||
|
QVERIFY(test02->_url == url02);
|
||||||
QVERIFY(test02->_startFrame == 2.0f);
|
QVERIFY(test02->_startFrame == 2.0f);
|
||||||
QVERIFY(test02->_endFrame == 21.0f);
|
QVERIFY(test02->_endFrame == 21.0f);
|
||||||
QVERIFY(test02->_timeScale == 0.9f);
|
QVERIFY(test02->_timeScale == 0.9f);
|
||||||
|
|
|
@ -1,101 +0,0 @@
|
||||||
//
|
|
||||||
// RigTests.cpp
|
|
||||||
// tests/rig/src
|
|
||||||
//
|
|
||||||
// Created by Howard Stearns on 6/16/15
|
|
||||||
// Copyright 2015 High Fidelity, Inc.
|
|
||||||
//
|
|
||||||
// Distributed under the Apache License, Version 2.0.
|
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
||||||
//
|
|
||||||
/* FIXME/TBD:
|
|
||||||
|
|
||||||
The following lower level functionality might be separated out into a separate class, covered by a separate test case class:
|
|
||||||
- With no input, initial pose is standing, arms at side
|
|
||||||
- Some single animation produces correct results at a given keyframe time.
|
|
||||||
- Some single animation produces correct results at a given interpolated time.
|
|
||||||
- Blend between two animations, started at separate times, produces correct result at a given interpolated time.
|
|
||||||
- Head orientation can be overridden to produce change that doesn't come from the playing animation.
|
|
||||||
- Hand position/orientation can be overridden to produce change that doesn't come from the playing animation.
|
|
||||||
- Hand position/orientation can be overrridden to produce elbow change that doesn't come from the playing animation.
|
|
||||||
- Respect scaling? (e.g., so that MyAvatar.increase/decreaseSize can alter rig, such that anti-scating and footfalls-on-stairs works)
|
|
||||||
|
|
||||||
Higher level functionality:
|
|
||||||
- start/stopAnimation adds the animation to that which is playing, blending/fading as needed.
|
|
||||||
- thrust causes walk role animation to be used.
|
|
||||||
- turning causes turn role animation to be used. (two tests, correctly symmetric left & right)
|
|
||||||
- walk/turn do not skate (footfalls match over-ground velocity)
|
|
||||||
- (Later?) walk up stairs / hills have proper footfall for terrain
|
|
||||||
- absence of above causes return to idle role animation to be used
|
|
||||||
- (later?) The lower-level head/hand placements respect previous state. E.g., actual hand movement can be slower than requested.
|
|
||||||
- (later) The lower-level head/hand placements can move whole skeleton. E.g., turning head past a limit may turn whole body. Reaching up can move shoulders and hips.
|
|
||||||
|
|
||||||
Backward-compatability operations. We should think of this behavior as deprecated:
|
|
||||||
- clearJointData return to standing. TBD: presumably with idle and all other animations NOT playing, until explicitly reenabled with a new TBD method?
|
|
||||||
- setJointData applies the given data. Same TBD.
|
|
||||||
These can be defined true or false, but the tests document the behavior and tells us if something's changed:
|
|
||||||
- An external change to the original skeleton IS/ISN'T seen by the rig.
|
|
||||||
- An external change to the original skeleton's head orientation IS/ISN'T seen by the rig.
|
|
||||||
- An external change to the original skeleton's hand orientiation IS/ISN'T seen by the rig.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
#include "FBXReader.h"
|
|
||||||
#include "OBJReader.h"
|
|
||||||
|
|
||||||
#include <Rig.h>
|
|
||||||
#include "RigTests.h"
|
|
||||||
|
|
||||||
static void reportJoint(const Rig& rig, int index) { // Handy for debugging
|
|
||||||
std::cout << "\n";
|
|
||||||
std::cout << index << " " << rig->getAnimSkeleton()->getJointName(index).toUtf8().data() << "\n";
|
|
||||||
glm::vec3 pos;
|
|
||||||
rig->getJointPosition(index, pos);
|
|
||||||
glm::quat rot;
|
|
||||||
rig->getJointRotation(index, rot);
|
|
||||||
std::cout << " pos:" << pos << "\n";
|
|
||||||
std::cout << " rot:" << safeEulerAngles(rot) << "\n";
|
|
||||||
std::cout << "\n";
|
|
||||||
}
|
|
||||||
static void reportByName(const Rig& rig, const QString& name) {
|
|
||||||
int jointIndex = rig->indexOfJoint(name);
|
|
||||||
reportJoint(rig, jointIndex);
|
|
||||||
}
|
|
||||||
static void reportAll(const Rig& rig) {
|
|
||||||
for (int i = 0; i < rig->getJointStateCount(); i++) {
|
|
||||||
reportJoint(rig, i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
static void reportSome(const Rig& rig) {
|
|
||||||
QString names[] = {"Head", "Neck", "RightShoulder", "RightArm", "RightForeArm", "RightHand", "Spine2", "Spine1", "Spine", "Hips", "RightUpLeg", "RightLeg", "RightFoot", "RightToeBase", "RightToe_End"};
|
|
||||||
for (auto name : names) {
|
|
||||||
reportByName(rig, name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QTEST_MAIN(RigTests)
|
|
||||||
|
|
||||||
void RigTests::initTestCase() {
|
|
||||||
//#define FROM_FILE "/Users/howardstearns/howardHiFi/Zack.fbx"
|
|
||||||
#ifdef FROM_FILE
|
|
||||||
QFile file(FROM_FILE);
|
|
||||||
QCOMPARE(file.open(QIODevice::ReadOnly), true);
|
|
||||||
FBXGeometry* geometry = readFBX(file.readAll(), QVariantHash());
|
|
||||||
#else
|
|
||||||
QUrl fbxUrl("https://s3.amazonaws.com/hifi-public/models/skeletons/Zack/Zack.fbx");
|
|
||||||
QNetworkReply* reply = OBJReader().request(fbxUrl, false); // Just a convenience hack for synchronoud http request
|
|
||||||
auto fbxHttpCode = !reply->isFinished() ? -1 : reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
|
||||||
QCOMPARE(fbxHttpCode, 200);
|
|
||||||
FBXGeometry* geometry = readFBX(reply->readAll(), QVariantHash());
|
|
||||||
#endif
|
|
||||||
QVERIFY((bool)geometry);
|
|
||||||
|
|
||||||
_rig.initJointStates(*geometry, glm::mat4());
|
|
||||||
std::cout << "Rig is ready " << geometry->joints.count() << " joints " << std::endl;
|
|
||||||
reportAll(_rig);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RigTests::initialPoseArmsDown() {
|
|
||||||
reportSome(_rig);
|
|
||||||
}
|
|
|
@ -1,55 +0,0 @@
|
||||||
//
|
|
||||||
// RigTests.h
|
|
||||||
// tests/rig/src
|
|
||||||
//
|
|
||||||
// Created by Howard Stearns on 6/16/15
|
|
||||||
// Copyright 2015 High Fidelity, Inc.
|
|
||||||
//
|
|
||||||
// Distributed under the Apache License, Version 2.0.
|
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef hifi_RigTests_h
|
|
||||||
#define hifi_RigTests_h
|
|
||||||
|
|
||||||
#include <QtTest/QtTest>
|
|
||||||
#include <Rig.h>
|
|
||||||
|
|
||||||
//#include "../QTestExtensions.h"
|
|
||||||
|
|
||||||
|
|
||||||
// The QTest terminology is not consistent with itself or with industry:
|
|
||||||
// The whole directory, and the rig-tests target, doesn't seem to be a QTest concept, an corresponds roughly to a toplevel suite of suites.
|
|
||||||
// The directory can contain any number of classes like this one. (Don't forget to wipe your build dir, and rerun cmake when you add one.):
|
|
||||||
// QTest doc (http://doc.qt.io/qt-5/qtest-overview.html) calls this a "test case".
|
|
||||||
// The output of QTest's 'ctest' runner calls this a "test" when run in the whole directory (e.g., when reporting success/failure counts).
|
|
||||||
// The test case (like this class) can contain any number of test slots:
|
|
||||||
// QTest doc calls these "test functions"
|
|
||||||
// When you run a single test case executable (e.g., "rig-RigTests"), the (unlabeled) count includes these test functions and also the before method, which is auto generated as initTestCase.
|
|
||||||
|
|
||||||
// To build and run via make:
|
|
||||||
// make help | grep tests # shows all test targets, including all-tests and rig-tests.
|
|
||||||
// make all-tests # will compile and then die as soon as any test case dies, even if its not in your directory
|
|
||||||
// make rig-tests # will compile and run `ctest .` in the tests/rig directory, running all the test cases found there.
|
|
||||||
// Alas, only summary output is shown on stdout. The real results, including any stdout that your code does, is in tests/rig/Testing/Temporary/LastTest.log, or...
|
|
||||||
// tests/rig/rig-RigTests (or the executable corresponding to any test case you define here) will run just that case and give output directly.
|
|
||||||
//
|
|
||||||
// To build and run via Xcode:
|
|
||||||
// On some machines, xcode can't find cmake on the path it uses. I did, effectively: sudo ln -s `which cmake` /usr/bin
|
|
||||||
// Note the above make instructions.
|
|
||||||
// all-tests, rig-tests, and rig-RigTests are all targets:
|
|
||||||
// The first two of these show no output at all, but if there's a failure you can see it by clicking on the red failure in the "issue navigator" (or by externally viewing the .log above).
|
|
||||||
// The last (or any other individual test case executable) does show output in the Xcode output display.
|
|
||||||
|
|
||||||
class RigTests : public QObject {
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void initTestCase();
|
|
||||||
void initialPoseArmsDown();
|
|
||||||
|
|
||||||
private:
|
|
||||||
Rig _rig;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // hifi_RigTests_h
|
|
Loading…
Reference in a new issue