From 6e15ae9d7d91de96a174c9ca0b9107401c2bf01f Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Tue, 1 Dec 2015 11:49:17 -0800 Subject: [PATCH 1/3] Update robot agent script to randomly select among all HiFi avatars, identified by name, and with a random turn from front. --- examples/acScripts/animatedAvatarAgent.js | 53 +++++++++++++++++++++-- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/examples/acScripts/animatedAvatarAgent.js b/examples/acScripts/animatedAvatarAgent.js index 3e5c90ed1a..74b1032328 100644 --- a/examples/acScripts/animatedAvatarAgent.js +++ b/examples/acScripts/animatedAvatarAgent.js @@ -1,6 +1,6 @@ "use strict"; /*jslint vars: true, plusplus: true*/ -/*global Agent, Avatar, Script, Entities, Vec3, print*/ +/*global Agent, Avatar, Script, Entities, Vec3, Quat, print*/ // // animatedAvatar.js // examples/acScripts @@ -16,15 +16,62 @@ var origin = {x: 500, y: 500, z: 500}; var spread = 20; // meters +var turnSpread = 90; // How many degrees should turn from front range over. var animationData = {url: "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/walk_fwd.fbx", lastFrame: 35}; -Avatar.skeletonModelURL = "https://hifi-public.s3.amazonaws.com/marketplace/contents/dd03b8e3-52fb-4ab3-9ac9-3b17e00cd85d/98baa90b3b66803c5d7bd4537fca6993.fst"; //lovejoy -Avatar.displayName = "'Bot"; +var models = [ // Commented-out avatars do not animate properly on AC's. Presumably because ScriptableAvatar doesn't use model pre-rotations. + "https://hifi-public.s3.amazonaws.com/ozan/avatars/hifi_team/alan/alan.fst", + "https://hifi-public.s3.amazonaws.com/ozan/avatars/hifi_team/andrew/andrew.fst", + "https://hifi-public.s3.amazonaws.com/ozan/avatars/hifi_team/austin/austin.fst", + "https://hifi-public.s3.amazonaws.com/ozan/avatars/hifi_team/birarda/birarda.fst", + "https://hifi-public.s3.amazonaws.com/ozan/avatars/hifi_team/brad/brad.fst", + "https://hifi-public.s3.amazonaws.com/ozan/avatars/hifi_team/chris/chris2.fst", + "https://hifi-public.s3.amazonaws.com/ozan/avatars/hifi_team/clement/clement.fst", + "https://hifi-public.s3.amazonaws.com/ozan/avatars/hifi_team/emily/emily.fst", + "https://hifi-public.s3.amazonaws.com/ozan/avatars/hifi_team/ewing/ewing.fst", + "https://hifi-public.s3.amazonaws.com/ozan/avatars/hifi_team/howard/howard.fst", + "https://hifi-public.s3.amazonaws.com/ozan/avatars/hifi_team/huffman/huffman.fst", + "https://hifi-public.s3.amazonaws.com/ozan/avatars/hifi_team/james/james.fst", + "https://hifi-public.s3.amazonaws.com/ozan/avatars/hifi_team/philip/philip.fst", + "https://hifi-public.s3.amazonaws.com/ozan/avatars/hifi_team/ryan/ryan.fst", + "https://hifi-public.s3.amazonaws.com/ozan/avatars/hifi_team/sam/sam.fst", + "https://hifi-public.s3.amazonaws.com/ozan/avatars/hifi_team/tony/tony.fst", + + "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/contents/1e57c395-612e-4acd-9561-e79dbda0bc49/faafb83c63a3e5e265884d270fc29f8b.fst", + "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/contents/615ca447-06ee-4215-8dd1-a609c2fcd0cd/c7af6d4224c501fdd9cb54e0101ff281.fst", + "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/contents/731c39d7-559a-4ce2-947c-3e2768f5471c/8d5eba2fd5bf068259556aec1861c5dd.fst", + "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/contents/8bdaa1ec-99df-4a29-a249-6941c7fd1930/37351a18a3dea468088fc3d822aaf29c.fst", + "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/contents/0909d7b7-c67e-45fb-acd9-a07380dc6b9c/da76b8c59dbc680bdda90df4b9a46faa.fst", + "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/contents/ad0dffd7-f811-487b-a20a-2509235491ef/29106da1f7e6a42c7907603421fd7df5.fst", + "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/contents/3ebe5c84-8b88-4d91-86ac-f104f3620fe3/3534b032d079514aa8960a316500ce13.fst", + "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/contents/dd03b8e3-52fb-4ab3-9ac9-3b17e00cd85d/98baa90b3b66803c5d7bd4537fca6993.fst", + "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/contents/ff060580-2fc7-4b6c-8e12-f023d05363cf/dadef29b1e60f23b413d1850d7e0dd4a.fst", + "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/contents/b55d3baf-4eb3-4cac-af4c-0fb66d0c907b/ad2c9157f3924ab1f7f6ea87a8cce6cc.fst", + "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/contents/d029ae8d-2905-4eb7-ba46-4bd1b8cb9d73/4618d52e711fbb34df442b414da767bb.fst" +]; +var nameMap = { + faafb83c63a3e5e265884d270fc29f8b: 'albert', + c7af6d4224c501fdd9cb54e0101ff281: 'boss', + '8d5eba2fd5bf068259556aec1861c5dd': 'dougland', + '37351a18a3dea468088fc3d822aaf29c': 'fightbot blue', + da76b8c59dbc680bdda90df4b9a46faa: 'judd', + '29106da1f7e6a42c7907603421fd7df5': 'kate', + '3534b032d079514aa8960a316500ce13': 'lenny', + '98baa90b3b66803c5d7bd4537fca6993': 'lovejoy', + dadef29b1e60f23b413d1850d7e0dd4a: 'mery', // eyes no good + ad2c9157f3924ab1f7f6ea87a8cce6cc: 'owen', + '4618d52e711fbb34df442b414da767bb': 'will' +}; + +Avatar.skeletonModelURL = models[Math.round(Math.random() * (models.length - 1))]; // pick one +Avatar.displayName = Avatar.skeletonModelURL.match(/\/(\w*).fst/)[1]; // grab the file basename +Avatar.displayName = nameMap[Avatar.displayName] || Avatar.displayName; var millisecondsToWaitBeforeStarting = 10 * 1000; // To give the various servers a chance to start. Agent.isAvatar = true; function coord() { return (Math.random() * spread) - (spread / 2); } // randomly distribute a coordinate zero += spread/2. Script.setTimeout(function () { Avatar.position = Vec3.sum(origin, {x: coord(), y: 0, z: coord()}); + Avatar.orientation = Quat.fromPitchYawRollDegrees(0, turnSpread * (Math.random() - 0.5), 0); print("Starting at", JSON.stringify(Avatar.position)); Avatar.startAnimation(animationData.url, animationData.fps || 30, 1, true, false, animationData.firstFrame || 0, animationData.lastFrame); }, millisecondsToWaitBeforeStarting); From ef32853d4666ad29afc35cb6e351138b302cd0a3 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Tue, 1 Dec 2015 11:51:22 -0800 Subject: [PATCH 2/3] When AvatarData parses the .fst, store the (merged) .fbx filename. (Interface gets the .fbx by a separate path, but agents don't have that chance.) --- libraries/avatars/src/AvatarData.cpp | 9 ++++++++- libraries/avatars/src/AvatarData.h | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 0f588b5013..e25e4f243f 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -1212,7 +1212,14 @@ void AvatarData::setJointMappingsFromNetworkReply() { QByteArray line; while (!(line = networkReply->readLine()).isEmpty()) { - if (!(line = line.trimmed()).startsWith("jointIndex")) { + line = line.trimmed(); + if (line.startsWith("filename")) { + int filenameIndex = line.indexOf('=') + 1; + if (filenameIndex > 0) { + _skeletonFBXURL = _skeletonModelURL.resolved(QString(line.mid(filenameIndex).trimmed())); + } + } + if (!line.startsWith("jointIndex")) { continue; } int jointNameIndex = line.indexOf('=') + 1; diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index bc71bc52cd..2d5a395e2a 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -392,6 +392,7 @@ protected: QUrl _faceModelURL; // These need to be empty so that on first time setting them they will not short circuit QUrl _skeletonModelURL; // These need to be empty so that on first time setting them they will not short circuit + QUrl _skeletonFBXURL; QVector _attachmentData; QString _displayName; From 8f7f3eed6e351c831859619cf9e38d44300381b4 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Tue, 1 Dec 2015 11:52:42 -0800 Subject: [PATCH 3/3] ScriptAvatar now gets the .fbx joints: Because the joint mappings in the .fst can be wrong when the .fst isn't updated for quick changes in the .fbx. Because models now really need the preRotation, which is only in the avatar .fbx. --- .../src/avatars/ScriptableAvatar.cpp | 31 ++++++++++++------- .../src/avatars/ScriptableAvatar.h | 1 + 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/assignment-client/src/avatars/ScriptableAvatar.cpp b/assignment-client/src/avatars/ScriptableAvatar.cpp index 9b3d2ed24e..82e88f67ef 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.cpp +++ b/assignment-client/src/avatars/ScriptableAvatar.cpp @@ -12,7 +12,6 @@ #include #include - #include "ScriptableAvatar.h" // hold and priority unused but kept so that client side JS can run. @@ -48,14 +47,12 @@ AnimationDetails ScriptableAvatar::getAnimationDetails() { } 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) { - QStringList modelJoints = getJointNames(); - QStringList animationJoints = _animation->getJointNames(); - - if (_jointData.size() != modelJoints.size()) { - _jointData.resize(modelJoints.size()); - } + if (_animation && _animation->isLoaded() && _animation->getFrames().size() > 0 && _bind->isLoaded()) { float currentFrame = _animationDetails.currentFrame + deltatime * _animationDetails.fps; if (_animationDetails.loop || currentFrame < _animationDetails.lastFrame) { @@ -63,19 +60,29 @@ void ScriptableAvatar::update(float deltatime) { currentFrame -= (_animationDetails.lastFrame - _animationDetails.firstFrame); } _animationDetails.currentFrame = currentFrame; + + const QVector& modelJoints = _bind->getGeometry().joints; + QStringList animationJointNames = _animation->getJointNames(); + + if (_jointData.size() != modelJoints.size()) { + _jointData.resize(modelJoints.size()); + } 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); - for (int i = 0; i < animationJoints.size(); i++) { - const QString& name = animationJoints[i]; - int mapping = getJointIndex(name); + for (int i = 0; i < animationJointNames.size(); i++) { + const QString& name = animationJointNames[i]; + // As long as we need the model preRotations anyway, let's get the jointIndex from the bind skeleton rather than + // 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 = safeMix(floorFrame.rotations.at(i), ceilFrame.rotations.at(i), frameFraction); + 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) { diff --git a/assignment-client/src/avatars/ScriptableAvatar.h b/assignment-client/src/avatars/ScriptableAvatar.h index 78b2be1057..30c48d02bf 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.h +++ b/assignment-client/src/avatars/ScriptableAvatar.h @@ -33,6 +33,7 @@ private: AnimationPointer _animation; AnimationDetails _animationDetails; QStringList _maskedJoints; + AnimationPointer _bind; // a sleazy way to get the skeleton, given the various library/cmake dependencies }; #endif // hifi_ScriptableAvatar_h \ No newline at end of file