mirror of
https://github.com/overte-org/overte.git
synced 2025-08-06 23:09:52 +02:00
make agent avatar animations work again, and use them in crowds
This commit is contained in:
parent
abb05bc686
commit
884d22a59b
4 changed files with 40 additions and 24 deletions
|
@ -46,14 +46,21 @@ AnimationDetails ScriptableAvatar::getAnimationDetails() {
|
||||||
return _animationDetails;
|
return _animationDetails;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ScriptableAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
||||||
|
_bind.reset();
|
||||||
|
_animSkeleton.reset();
|
||||||
|
AvatarData::setSkeletonModelURL(skeletonModelURL);
|
||||||
|
}
|
||||||
void ScriptableAvatar::update(float deltatime) {
|
void ScriptableAvatar::update(float deltatime) {
|
||||||
if (_bind.isNull() && !_skeletonFBXURL.isEmpty()) { // AvatarData will parse the .fst, but not get the .fbx skeleton.
|
if (_bind.isNull() && !_skeletonFBXURL.isEmpty()) { // AvatarData will parse the .fst, but not get the .fbx skeleton.
|
||||||
_bind = DependencyManager::get<AnimationCache>()->getAnimation(_skeletonFBXURL);
|
_bind = DependencyManager::get<AnimationCache>()->getAnimation(_skeletonFBXURL);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run animation
|
// 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<AnimSkeleton>(_bind->getGeometry());
|
||||||
|
}
|
||||||
float currentFrame = _animationDetails.currentFrame + deltatime * _animationDetails.fps;
|
float currentFrame = _animationDetails.currentFrame + deltatime * _animationDetails.fps;
|
||||||
if (_animationDetails.loop || currentFrame < _animationDetails.lastFrame) {
|
if (_animationDetails.loop || currentFrame < _animationDetails.lastFrame) {
|
||||||
while (currentFrame >= _animationDetails.lastFrame) {
|
while (currentFrame >= _animationDetails.lastFrame) {
|
||||||
|
@ -64,14 +71,16 @@ void ScriptableAvatar::update(float deltatime) {
|
||||||
const QVector<FBXJoint>& modelJoints = _bind->getGeometry().joints;
|
const QVector<FBXJoint>& modelJoints = _bind->getGeometry().joints;
|
||||||
QStringList animationJointNames = _animation->getJointNames();
|
QStringList animationJointNames = _animation->getJointNames();
|
||||||
|
|
||||||
if (_jointData.size() != modelJoints.size()) {
|
const int nJoints = modelJoints.size();
|
||||||
_jointData.resize(modelJoints.size());
|
if (_jointData.size() != nJoints) {
|
||||||
|
_jointData.resize(nJoints);
|
||||||
}
|
}
|
||||||
|
|
||||||
const int frameCount = _animation->getFrames().size();
|
const int frameCount = _animation->getFrames().size();
|
||||||
const FBXAnimationFrame& floorFrame = _animation->getFrames().at((int)glm::floor(currentFrame) % frameCount);
|
const FBXAnimationFrame& floorFrame = _animation->getFrames().at((int)glm::floor(currentFrame) % frameCount);
|
||||||
const FBXAnimationFrame& ceilFrame = _animation->getFrames().at((int)glm::ceil(currentFrame) % frameCount);
|
const FBXAnimationFrame& ceilFrame = _animation->getFrames().at((int)glm::ceil(currentFrame) % frameCount);
|
||||||
const float frameFraction = glm::fract(currentFrame);
|
const float frameFraction = glm::fract(currentFrame);
|
||||||
|
std::vector<AnimPose> poses = _animSkeleton->getRelativeDefaultPoses();
|
||||||
|
|
||||||
for (int i = 0; i < animationJointNames.size(); i++) {
|
for (int i = 0; i < animationJointNames.size(); i++) {
|
||||||
const QString& name = animationJointNames[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).
|
// trusting the .fst (which is sometimes not updated to match changes to .fbx).
|
||||||
int mapping = _bind->getGeometry().getJointIndex(name);
|
int mapping = _bind->getGeometry().getJointIndex(name);
|
||||||
if (mapping != -1 && !_maskedJoints.contains(name)) {
|
if (mapping != -1 && !_maskedJoints.contains(name)) {
|
||||||
JointData& data = _jointData[mapping];
|
// Eventually, this should probably deal with post rotations and translations, too.
|
||||||
|
poses[mapping].rot = modelJoints[mapping].preRotation *
|
||||||
auto newRotation = modelJoints[mapping].preRotation *
|
safeMix(floorFrame.rotations.at(i), ceilFrame.rotations.at(i), frameFraction);;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_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 {
|
} else {
|
||||||
_animation.clear();
|
_animation.clear();
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
#define hifi_ScriptableAvatar_h
|
#define hifi_ScriptableAvatar_h
|
||||||
|
|
||||||
#include <AnimationCache.h>
|
#include <AnimationCache.h>
|
||||||
|
#include <AnimSkeleton.h>
|
||||||
#include <AvatarData.h>
|
#include <AvatarData.h>
|
||||||
#include <ScriptEngine.h>
|
#include <ScriptEngine.h>
|
||||||
|
|
||||||
|
@ -25,6 +26,7 @@ public:
|
||||||
bool hold = false, float firstFrame = 0.0f, float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList());
|
bool hold = false, float firstFrame = 0.0f, float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList());
|
||||||
Q_INVOKABLE void stopAnimation();
|
Q_INVOKABLE void stopAnimation();
|
||||||
Q_INVOKABLE AnimationDetails getAnimationDetails();
|
Q_INVOKABLE AnimationDetails getAnimationDetails();
|
||||||
|
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL) override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void update(float deltatime);
|
void update(float deltatime);
|
||||||
|
@ -34,6 +36,7 @@ private:
|
||||||
AnimationDetails _animationDetails;
|
AnimationDetails _animationDetails;
|
||||||
QStringList _maskedJoints;
|
QStringList _maskedJoints;
|
||||||
AnimationPointer _bind; // a sleazy way to get the skeleton, given the various library/cmake dependencies
|
AnimationPointer _bind; // a sleazy way to get the skeleton, given the various library/cmake dependencies
|
||||||
|
std::shared_ptr<AnimSkeleton> _animSkeleton;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_ScriptableAvatar_h
|
#endif // hifi_ScriptableAvatar_h
|
|
@ -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.
|
- 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.
|
- URLs are cached regardless of server headers. Must use cache-defeating query parameters.
|
||||||
- JSON.stringify(Avatar) silently fails (even when Agent.isAvatar)
|
- JSON.stringify(Avatar) silently fails (even when Agent.isAvatar)
|
||||||
|
- If you run from a dev build directory, you must link assignment-client/<target>/resources to ../../interface/<target>/resources
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function messageSend(message) {
|
function messageSend(message) {
|
||||||
|
|
|
@ -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 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 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 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 spread = Math.sqrt(MINIMUM_AVATARS * DENSITY); // meters
|
||||||
var turnSpread = 90; // How many degrees should turn from front range over.
|
var turnSpread = 90; // How many degrees should turn from front range over.
|
||||||
|
@ -71,18 +79,10 @@ function messageHandler(channel, messageString, senderID) {
|
||||||
rcpt: senderID,
|
rcpt: senderID,
|
||||||
position: Vec3.sum(MyAvatar.position, {x: coord(), y: 0, z: coord()}),
|
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),
|
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
|
// 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"/,
|
//skeletonModelURL: "file:///c:/Program Files/High Fidelity Release/resources/meshes/defaultAvatar_full.fst"/,
|
||||||
animationData: { // T-pose until we get animations working again.
|
animationData: ANIMATION_DATA
|
||||||
"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
|
|
||||||
}*/
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
Loading…
Reference in a new issue