From 884d22a59b94d8a5676ea20598a022cf9e082dee Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Tue, 11 Oct 2016 16:06:55 -0700 Subject: [PATCH 1/2] make agent avatar animations work again, and use them in crowds --- .../src/avatars/ScriptableAvatar.cpp | 40 ++++++++++++------- .../src/avatars/ScriptableAvatar.h | 3 ++ .../tests/performance/crowd-agent.js | 1 + scripts/developer/tests/performance/summon.js | 20 +++++----- 4 files changed, 40 insertions(+), 24 deletions(-) diff --git a/assignment-client/src/avatars/ScriptableAvatar.cpp b/assignment-client/src/avatars/ScriptableAvatar.cpp index 82e88f67ef..15877eed25 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.cpp +++ b/assignment-client/src/avatars/ScriptableAvatar.cpp @@ -46,14 +46,21 @@ AnimationDetails ScriptableAvatar::getAnimationDetails() { return _animationDetails; } +void ScriptableAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { + _bind.reset(); + _animSkeleton.reset(); + AvatarData::setSkeletonModelURL(skeletonModelURL); +} void ScriptableAvatar::update(float deltatime) { if (_bind.isNull() && !_skeletonFBXURL.isEmpty()) { // AvatarData will parse the .fst, but not get the .fbx skeleton. _bind = DependencyManager::get()->getAnimation(_skeletonFBXURL); } // Run animation - if (_animation && _animation->isLoaded() && _animation->getFrames().size() > 0 && _bind->isLoaded()) { - + if (_animation && _animation->isLoaded() && _animation->getFrames().size() > 0 && !_bind.isNull() && _bind->isLoaded()) { + if (!_animSkeleton) { + _animSkeleton = std::make_shared(_bind->getGeometry()); + } float currentFrame = _animationDetails.currentFrame + deltatime * _animationDetails.fps; if (_animationDetails.loop || currentFrame < _animationDetails.lastFrame) { while (currentFrame >= _animationDetails.lastFrame) { @@ -64,14 +71,16 @@ void ScriptableAvatar::update(float deltatime) { const QVector& modelJoints = _bind->getGeometry().joints; QStringList animationJointNames = _animation->getJointNames(); - if (_jointData.size() != modelJoints.size()) { - _jointData.resize(modelJoints.size()); + const int nJoints = modelJoints.size(); + if (_jointData.size() != nJoints) { + _jointData.resize(nJoints); } const int frameCount = _animation->getFrames().size(); const FBXAnimationFrame& floorFrame = _animation->getFrames().at((int)glm::floor(currentFrame) % frameCount); const FBXAnimationFrame& ceilFrame = _animation->getFrames().at((int)glm::ceil(currentFrame) % frameCount); const float frameFraction = glm::fract(currentFrame); + std::vector poses = _animSkeleton->getRelativeDefaultPoses(); for (int i = 0; i < animationJointNames.size(); i++) { const QString& name = animationJointNames[i]; @@ -79,18 +88,21 @@ void ScriptableAvatar::update(float deltatime) { // trusting the .fst (which is sometimes not updated to match changes to .fbx). int mapping = _bind->getGeometry().getJointIndex(name); if (mapping != -1 && !_maskedJoints.contains(name)) { - JointData& data = _jointData[mapping]; - - auto newRotation = modelJoints[mapping].preRotation * - safeMix(floorFrame.rotations.at(i), ceilFrame.rotations.at(i), frameFraction); - // We could probably do translations as in interpolation in model space (rather than the parent space that each frame is in), - // but we don't do so for MyAvatar yet, so let's not be different here. - if (data.rotation != newRotation) { - data.rotation = newRotation; - data.rotationSet = true; - } + // Eventually, this should probably deal with post rotations and translations, too. + poses[mapping].rot = modelJoints[mapping].preRotation * + safeMix(floorFrame.rotations.at(i), ceilFrame.rotations.at(i), frameFraction);; } } + _animSkeleton->convertRelativePosesToAbsolute(poses); + for (int i = 0; i < nJoints; i++) { + JointData& data = _jointData[i]; + AnimPose& pose = poses[i]; + if (data.rotation != pose.rot) { + data.rotation = pose.rot; + data.rotationSet = true; + } + } + } else { _animation.clear(); } diff --git a/assignment-client/src/avatars/ScriptableAvatar.h b/assignment-client/src/avatars/ScriptableAvatar.h index 30c48d02bf..56707de471 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.h +++ b/assignment-client/src/avatars/ScriptableAvatar.h @@ -13,6 +13,7 @@ #define hifi_ScriptableAvatar_h #include +#include #include #include @@ -25,6 +26,7 @@ public: bool hold = false, float firstFrame = 0.0f, float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList()); Q_INVOKABLE void stopAnimation(); Q_INVOKABLE AnimationDetails getAnimationDetails(); + virtual void setSkeletonModelURL(const QUrl& skeletonModelURL) override; private slots: void update(float deltatime); @@ -34,6 +36,7 @@ private: AnimationDetails _animationDetails; QStringList _maskedJoints; AnimationPointer _bind; // a sleazy way to get the skeleton, given the various library/cmake dependencies + std::shared_ptr _animSkeleton; }; #endif // hifi_ScriptableAvatar_h \ No newline at end of file diff --git a/scripts/developer/tests/performance/crowd-agent.js b/scripts/developer/tests/performance/crowd-agent.js index 6b73d66c4f..2e54e21f45 100644 --- a/scripts/developer/tests/performance/crowd-agent.js +++ b/scripts/developer/tests/performance/crowd-agent.js @@ -22,6 +22,7 @@ print('crowd-agent version 2'); - File urls for AC scripts silently fail. Use a local server (e.g., python SimpleHTTPServer) for development. - URLs are cached regardless of server headers. Must use cache-defeating query parameters. - JSON.stringify(Avatar) silently fails (even when Agent.isAvatar) +- If you run from a dev build directory, you must link assignment-client//resources to ../../interface//resources */ function messageSend(message) { diff --git a/scripts/developer/tests/performance/summon.js b/scripts/developer/tests/performance/summon.js index 8118f553f1..25ecd4860b 100644 --- a/scripts/developer/tests/performance/summon.js +++ b/scripts/developer/tests/performance/summon.js @@ -23,6 +23,14 @@ var DENSITY = 0.3; // square meters per person. Some say 10 sq ft is arm's lengt var SOUND_DATA = {url: "http://howard-stearns.github.io/models/sounds/piano1.wav"}; var AVATARS_CHATTERING_AT_ONCE = 4; // How many of the agents should we request to play SOUND at once. var NEXT_SOUND_SPREAD = 500; // millisecond range of how long to wait after one sound finishes, before playing the next +var ANIMATION_DATA = { // T-pose until we get animations working again. + "url": "http://howard-stearns.github.io/models/resources/avatar/animations/idle.fbx", + //"url": "http://howard-stearns.github.io/models/resources/avatar/animations/walk_fwd.fbx", + "startFrame": 0.0, + "endFrame": 300.0, + "timeScale": 1.0, + "loopFlag": true +}; var spread = Math.sqrt(MINIMUM_AVATARS * DENSITY); // meters var turnSpread = 90; // How many degrees should turn from front range over. @@ -71,18 +79,10 @@ function messageHandler(channel, messageString, senderID) { rcpt: senderID, position: Vec3.sum(MyAvatar.position, {x: coord(), y: 0, z: coord()}), orientation: Quat.fromPitchYawRollDegrees(0, Quat.safeEulerAngles(MyAvatar.orientation).y + (turnSpread * (Math.random() - 0.5)), 0), - soundData: chatter && SOUND_DATA/* + soundData: chatter && SOUND_DATA, // No need to specify skeletonModelURL - //skeletonModelURL: "file:///c:/Program Files/High Fidelity Release/resources/meshes/being_of_light/being_of_light.fbx", //skeletonModelURL: "file:///c:/Program Files/High Fidelity Release/resources/meshes/defaultAvatar_full.fst"/, - animationData: { // T-pose until we get animations working again. - "url": "file:///C:/Program Files/High Fidelity Release/resources/avatar/animations/idle.fbx", - //"url": "file:///c:/Program Files/High Fidelity Release/resources/avatar/animations/walk_fwd.fbx", - "startFrame": 0.0, - "endFrame": 300.0, - "timeScale": 1.0, - "loopFlag": true - }*/ + animationData: ANIMATION_DATA }); } break; From 579c95b5dd9074f3745668d244edf9faac919c17 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 12 Oct 2016 14:42:41 -0700 Subject: [PATCH 2/2] pr feedback --- scripts/developer/tests/performance/summon.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/scripts/developer/tests/performance/summon.js b/scripts/developer/tests/performance/summon.js index 25ecd4860b..3eecba8413 100644 --- a/scripts/developer/tests/performance/summon.js +++ b/scripts/developer/tests/performance/summon.js @@ -23,9 +23,9 @@ var DENSITY = 0.3; // square meters per person. Some say 10 sq ft is arm's lengt var SOUND_DATA = {url: "http://howard-stearns.github.io/models/sounds/piano1.wav"}; var AVATARS_CHATTERING_AT_ONCE = 4; // How many of the agents should we request to play SOUND at once. var NEXT_SOUND_SPREAD = 500; // millisecond range of how long to wait after one sound finishes, before playing the next -var ANIMATION_DATA = { // T-pose until we get animations working again. +var ANIMATION_DATA = { "url": "http://howard-stearns.github.io/models/resources/avatar/animations/idle.fbx", - //"url": "http://howard-stearns.github.io/models/resources/avatar/animations/walk_fwd.fbx", + // "url": "http://howard-stearns.github.io/models/resources/avatar/animations/walk_fwd.fbx", // alternative example "startFrame": 0.0, "endFrame": 300.0, "timeScale": 1.0, @@ -80,8 +80,6 @@ function messageHandler(channel, messageString, senderID) { position: Vec3.sum(MyAvatar.position, {x: coord(), y: 0, z: coord()}), orientation: Quat.fromPitchYawRollDegrees(0, Quat.safeEulerAngles(MyAvatar.orientation).y + (turnSpread * (Math.random() - 0.5)), 0), soundData: chatter && SOUND_DATA, - // No need to specify skeletonModelURL - //skeletonModelURL: "file:///c:/Program Files/High Fidelity Release/resources/meshes/defaultAvatar_full.fst"/, animationData: ANIMATION_DATA }); }