Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for explicit control over model animations #9339

Merged
merged 6 commits into from
May 10, 2022

Conversation

markw65
Copy link
Contributor

@markw65 markw65 commented Jan 25, 2021

Cesium currently only supports time based animation. This can be inconvenient if the phase of the animation is related to something other than time (eg distance along a path of an object moving at a variable speed).

This came up before in a pull request here, but the author was persuaded that it was better to use nodeTransformations to explicitly control the model. That was (just) doable with that example, because there were just 3 pairs of wheels, all of which needed the exact same, relatively trivial, transformations.

My model has a bicycle chain with 90+ links, two wheels, two chain rings (with different rates of rotation, which are both different again from the rate of rotation of the wheels), two drive arms, and a rider. Using nodeTransformations would be extremely complex, and any changes to the model would require updating all the associated javascript. Also, on the site where I use the model, I actually use different models for different types of activity - so I want to just be able to drop in a new model, and have it "just work" (to make it work, I have to ensure that the animation's "duration" in seconds is equal to the distance in meters the model would move during one iteration of the animation).

This adds an animationTime property to ModelAnimation. If set, it's used by ModelAnimationCollection.update to determine the localAnimationTime, rather than using the current clock time.

I also added a manualAnimation property to ModelAnimationCollection so we can still do the short circuit exit from ModelAnimationCollection.update.

The new sandcastle example is just a clone of Time Dynamic Wheels, rewritten to use a more complex model, and the new functionality.

@cesium-concierge
Copy link

Thank you so much for the pull request @markw65! I noticed this is your first pull request and I wanted to say welcome to the Cesium community!

The Pull Request Guidelines is a handy reference for making sure your PR gets accepted quickly, so make sure to skim that.

  • ❌ Missing CONTRIBUTORS.md entry.
  • ✔️ Signed CLA found.
  • CHANGES.md was not updated.
    • If this change updates the public API in any way, please add a bullet point to CHANGES.md.
  • ❔ Unit tests were not updated.
    • Make sure you've updated tests to reflect your changes, added tests for any new code, and ran the code coverage tool.

Reviewers, don't forget to make sure that:

  • Cesium Viewer works.
  • Works in 2D/CV.
  • Works (or fails gracefully) in IE11.

@markw65 markw65 force-pushed the manual-animation branch 2 times, most recently from dd84add to 4277a6b Compare January 25, 2021 16:52
@markw65
Copy link
Contributor Author

markw65 commented Jan 25, 2021

@mramato @hpinkos You reviewed #7361 which originally had a similar intent (although a different approach). Any chance you could take a look? If it is something worth pursuing, any directions on how to write tests for this would be appreciated.

@markw65
Copy link
Contributor Author

markw65 commented Feb 9, 2021

@mramato @hpinkos I think this is fully ready for review; I found the existing tests, and modified one to test the new features. I've also updated CONTRIBUTERS.md and CHANGES.md, but don't know how to get cesium-concierge to notice.

@markw65
Copy link
Contributor Author

markw65 commented Feb 10, 2021

@OmarShehata maybe you could take a look?

@cesium-concierge
Copy link

Thanks again for your contribution @markw65!

No one has commented on this pull request in 30 days. Maintainers, can you review, merge or close to keep things tidy?

I'm going to re-bump this in 30 days. If you'd like me to stop, just comment with @cesium-concierge stop. If you want me to start again, just delete the comment.

@cesium-concierge
Copy link

Thanks again for your contribution @markw65!

No one has commented on this pull request in 90 days. Maintainers, can you review, merge or close to keep things tidy?

I'm going to re-bump this in 90 days. If you'd like me to stop, just comment with @cesium-concierge stop. If you want me to start again, just delete the comment.

1 similar comment
@cesium-concierge
Copy link

Thanks again for your contribution @markw65!

No one has commented on this pull request in 90 days. Maintainers, can you review, merge or close to keep things tidy?

I'm going to re-bump this in 90 days. If you'd like me to stop, just comment with @cesium-concierge stop. If you want me to start again, just delete the comment.

@markw65
Copy link
Contributor Author

markw65 commented Oct 19, 2021

@ebogo1 any chance you could look at this, or find someone who could look at it? I've tried pinging people who seemed likely to be interested, and posting in the forums (https://community.cesium.com/t/position-based-control-over-animations/11558 and then https://community.cesium.com/t/how-do-i-champion-my-pull-request/12286), but no response.

@ebogo1
Copy link
Contributor

ebogo1 commented Oct 21, 2021

@markw65 apologies for the delay - I'm currently trying to get a few things wrapped up for the upcoming release but I'll try to get back to this as soon as possible. Thanks!

@cesium-concierge
Copy link

Thanks again for your contribution @markw65!

No one has commented on this pull request in 90 days. Maintainers, can you review, merge or close to keep things tidy?

I'm going to re-bump this in 90 days. If you'd like me to stop, just comment with @cesium-concierge stop. If you want me to start again, just delete the comment.

1 similar comment
@cesium-concierge
Copy link

Thanks again for your contribution @markw65!

No one has commented on this pull request in 90 days. Maintainers, can you review, merge or close to keep things tidy?

I'm going to re-bump this in 90 days. If you'd like me to stop, just comment with @cesium-concierge stop. If you want me to start again, just delete the comment.

@markw65
Copy link
Contributor Author

markw65 commented Apr 21, 2022

I'm still very interested in getting this merged. I simply have no idea how to "champion" it. Nobody responds to anything I say about it. I'll rebase again, just in case...

@ebogo1
Copy link
Contributor

ebogo1 commented Apr 22, 2022

Hi @markw65 - apologies for the long wait and thanks for keeping the PR up to date.

I think adding some kind of manual control over "local time" is a fair extension for animations but the current implementation seems a bit restrictive to me. I could be misunderstanding, but to me it looks like the design around the new animationTime property assumes that it'll be used with multiple models updating it similarly (i.e. with the sampled distance property in your new Sandcastle). I wonder if there's a more generalized solution to control the animation time deltas - maybe it'd require a slightly different approach in ModelAnimationCollection.

@j9liu
Copy link
Contributor

j9liu commented Apr 22, 2022

Hi @markw65,

I noticed this while working on bringing animations to ModelExperimental in #10314. It'd be useful to give users more control over animations!

I feel like the implementation can be reworked somehow. It would be nice to have animations be independent of the scene frame's time, that's for sure. But, at least to me, it doesn't feel right that the user would have to manually increment some value and directly set it in the animation every frame. I'm not sure what would be better API design here, to be honest.

My thoughts aren't very refined, but this is what I'm thinking:

  • having some flag to allow animation independent of scene time is not a bad idea, though maybe rename from manualAnimation to something else
  • multiplier is currently a readonly property -- what would happen if we allowed the user to set this dynamically?
  • if the above suggestion doesn't work, let's rework animationTime. Instead of animationTime being a number that is changed, we could make it a property that's some sort of callback. The default would involve the current time (in real life), so that the animation can play even if the scene time pauses, the animation will still continue. But we can also allow this to be overridden by the user.

These are just thoughts, not concrete suggestions, so don't take this as a call to implement them right away. I think having input from other community members and developers would help us figure out where to go from here.

@markw65
Copy link
Contributor Author

markw65 commented Apr 23, 2022

Thanks for the feedback!

@ebogo1

the design around the new animationTime property assumes that it'll be used with multiple models updating it similarly

I don't really follow? If there are multiple models, they will presumably be moving separately, and will consequently need to have their animationTimes set separately?

eg this example

@j9liu

  • I'm not attached to the naming scheme in any way
  • multiplier really isn't a good way to go - and this was discussed in Sandcastle example for model with spinning wheels #7295, which initially suggested using multiplier for a similar reason. It doesn't work well because you want (eg) the amount the wheels turn by to be proportional to the distance covered by the model. In theory you could get that by modifying multiplier, but in practice, with varying speeds, its not easy. In addition, the implementation of multiplier is such that any time you change it, you effectively reset the phase of the animation, so every alteration to multiplier causes a skip in the animation (at least, thats what I remember from following Sandcastle example for model with spinning wheels #7295).
  • making animationTime a callback which defaults to mimicking the current behavior sounds reasonable. Let me investigate. And if @ebogo1 has any feedback on that suggestion I'd appreciate it...

@ebogo1
Copy link
Contributor

ebogo1 commented Apr 25, 2022

I like the idea of turning the new animationTime property into something more generic. In this example a sampled distance property works but I think if we're adding more control over the animation system we should keep in mind more generalized use cases. I do agree with @j9liu that setting animationTime each tick feels a bit clunky - ideally a callback function that has a generic interface for computing its own local time would be able to handle all sorts of animations.

As a note, I believe Three.js and Babylon.js both expose a time multiplier as the main way to control animation speed.

@markw65
Copy link
Contributor Author

markw65 commented Apr 27, 2022

As a note, I believe Three.js and Babylon.js both expose a time multiplier as the main way to control animation speed.

So I agree that multiplier is useful - I'm even using it in the project that uses this patch. Essentially, I use it as a scale factor so that each model would move by durationSeconds meters across its full animation cycle, assuming no slipping of wheels.

My claim is that if you want non-time based control over the animation, then multiplier is a very awkward way to go; and the current implementation of multiplier simply doesn't work because every time you change it, it causes a jump in where the animation is.

eg suppose multiplier is 1, and you're halfway through the animation cycle, and then you change multiplier to 2 (while only allowing time to advance by an insignificant epsilon). Then suddenly, you're at the end of the animation cycle (this assumes you update _duration at the same time as multiplier; much worse things happen if you don't). You could obviously fix that by also maintaining an _offset, and updating that every time multiplier changes. But its still not a convenient way to map an arbitrary function onto time...

[Edit] I've thought about this more, and I think I can explain my objection to multiplier much more clearly.

The multiplier approach is trying to recreate a function by integrating its derivative. The problem with that is that this will always introduce an "arbitrary constant" (which manifests as _offset above).

To make this concrete, take my model. After it has rolled a certain distance, there is a specific point in its animation cycle that it should be at. So eg if it covers 10m per animation cycle, after going 45m, it should be exactly half way through. If you run the clock from the start, and set multiplier appropriately at each tick (and have some mechanism that adjusts _offset as described above to prevent jumps) you can just about arrange for that to happen. But what if the user just uses the timeline to jump to a particular time? There is no way to set multiplier alone and get the correct offset.

In other words, multiplier is an ok way to control the animation incrementally along the path, but no good at all if you're jumping around.

@markw65
Copy link
Contributor Author

markw65 commented Apr 27, 2022

@j9liu

The default would involve the current time (in real life), so that the animation can play even if the scene time pauses, the animation will still continue.

Just wanted to clarify, because as I understand it, thats not the current behavior. It uses the simulator time, not real time.

@j9liu
Copy link
Contributor

j9liu commented Apr 27, 2022

@markw65 -- You're right, I misspoke. Thanks for the catch!

@markw65
Copy link
Contributor Author

markw65 commented Apr 28, 2022

Ok, I've updated the branch. I've added it as a patch on top of the old one, but I think it's probably better to just flatten it; its got more in common with the original code than with my first attempt. Let me know what you'd prefer.

I got rid of manualAnimation, and replaced it with animateWhilePaused. The basic reason for the flag is the same; when its false, we can skip the entire update loop when the scene time hasn't changed (as the current code does). But this time, you only need to set it if you want to provide an animation function that animates while scene time is paused. So unlike the manualAnimation flag, most uses of animationTime won't need to set it (so eg my own use case, although its distance, rather than time based, doesn't need the flag because the distance can't change unless the time does).

I'm not too familiar with the comment based typing scheme; but I think I eventually got it right(?), because my own project is written in typescript, and kept complaining until I got all the comments in place...

@markw65
Copy link
Contributor Author

markw65 commented Apr 28, 2022

I think the build failure was a travis glitch (makeZipFile seems to have timed out). I've rebased the branch to get another build going.

@markw65
Copy link
Contributor Author

markw65 commented Apr 28, 2022

No, it was a prettier check on my sandcastle example. Should be fixed now.

Copy link
Contributor

@j9liu j9liu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @markw65,

Thanks for the quick updates! I wasn't able to completely review the new changes, but I wanted to give it at least a quick look. Here's a couple suggestions regarding documentation / code style; next week we can take a closer look at the code to make sure it's robust.

Source/Scene/ModelAnimationCollection.js Show resolved Hide resolved
Source/Scene/ModelAnimation.js Outdated Show resolved Hide resolved
* @callback ModelAnimation.AnimationTime
*
* @param {Number} duration The animation's duration in seconds.
* @returns {Number} The fractional part of the return value gives
Copy link
Contributor

@j9liu j9liu Apr 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This @returns comment seems a bit complicated, but I have to think about how to best rewrite / summarize this. Will come back to it later

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this part can be simplified to

@returns {Number} Returns the local animation time.

The loop behaviors are handled automatically by ModelAnimationCollection.update and I don't think users need to think hard about what it's doing in order to write their own animationTime callbacks. If developers are more concerned about this they can infer it from the math in ModelAnimationCollection.update.

Source/Scene/ModelAnimationCollection.js Outdated Show resolved Hide resolved
Source/Scene/ModelAnimation.js Show resolved Hide resolved
Copy link
Contributor

@j9liu j9liu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@markw65 Thanks for your patience. I was able to go more thoroughly through the PR and I left some feedback.

Overall the code looks good. I left a handful of suggestions for readability and documentation. This way if we have to return to the code in the future, the logic is more clearly documented.

Also, I should point out that we plan to replace the old Model class with ModelExperimental, sometime this year. I added animations to ModelExperimental in #10314. We should preserve your changes by adding these to ModelExperimentalAnimationCollection and ModelExperimentalAnimation as well. Do you have the bandwidth to do this? The architecture and update code is almost the same, though feel free to ask any questions along the way.

Source/Scene/ModelAnimation.js Show resolved Hide resolved
Source/Scene/ModelAnimationCollection.js Outdated Show resolved Hide resolved
Source/Scene/ModelAnimationCollection.js Outdated Show resolved Hide resolved
Source/Scene/ModelAnimationCollection.js Outdated Show resolved Hide resolved
Source/Scene/ModelAnimationCollection.js Outdated Show resolved Hide resolved
Source/Scene/ModelAnimation.js Outdated Show resolved Hide resolved
Source/Scene/ModelAnimation.js Outdated Show resolved Hide resolved
Source/Scene/ModelAnimation.js Show resolved Hide resolved
* @callback ModelAnimation.AnimationTime
*
* @param {Number} duration The animation's duration in seconds.
* @returns {Number} The fractional part of the return value gives
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this part can be simplified to

@returns {Number} Returns the local animation time.

The loop behaviors are handled automatically by ModelAnimationCollection.update and I don't think users need to think hard about what it's doing in order to write their own animationTime callbacks. If developers are more concerned about this they can infer it from the math in ModelAnimationCollection.update.

@markw65
Copy link
Contributor Author

markw65 commented May 4, 2022

I think the latest update covers all the feedback/requested changes, except for updating ModelExperimental. I'll take a look at applying the changes there too.

@markw65
Copy link
Contributor Author

markw65 commented May 4, 2022

I've added the animationTime changes to ModelExperimentalAnimation and ModelExperimentalAnimationCollection, added the tests to ModelExperimentalAnimationCollectionSpec.js, and added a second copy of the sandcastle example that uses ModelExperimental.

@markw65 markw65 requested a review from j9liu May 5, 2022 00:12
Copy link
Contributor

@j9liu j9liu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @markw65,

This PR is looking really close, thanks for the quick changes! I left another round of suggestions. Once those are done, I'll do a final pass and look to merge this with main.

@@ -0,0 +1,167 @@
<!DOCTYPE html>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's okay to just have the "Manually Controlled ModelAnimation" sandcastle, since the functionality should be the same between Model and ModelExperimental. Let's remove this and the corresponding screenshot, and squash the commit history.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do.

One thing I noticed is that Model.fromGltf takes a "url" option, while ModelExperimental.fromGltf takes a "gltf" option, so (at least on that one point) they're not drop-in compatible. Is that going to be fixed?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point... we might want to add a url option for compatibility, then deprecate it after a few months. I'll make a note of it for the future

@@ -0,0 +1,167 @@
<!DOCTYPE html>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we name this sandcastle "Manually Controlled Animation" instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do

CHANGES.md Outdated
@@ -10,6 +10,10 @@

- Fixed the inaccurate computation of bounding spheres for `ModelExperimental`. [#10339](https://github.com/CesiumGS/cesium/pull/10339/)

##### Additions :tada:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We usually put Additions above the Fixes section

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do.

On an unrelated point, I just noticed the fix right above my additions:

- Fixed the inaccurate computation of bounding spheres for `ModelExperimental`. [#10339](https://github.com/CesiumGS/cesium/pull/10339/)

I noticed with my Elliptigo model that parts of it (using Model, not ModelExperimental) would randomly disappear as I zoomed in and out, and eventually decided it was down to incorrect bounding spheres (this was a year or so ago, I've forgotten the details), and found that setting "cull" to false fixed the problem.

I was never quite sure if it was a problem with my gltf, or a problem with Model itself... but did note that other gltf renderers didn't seem to have the problem (although, they may have been doing the equivalent of cull=false by default).

Looking at that pull request though, it suggests that the problem was only with ModelExperimental, and not with Model... are there any known bounding sphere issues with Model?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I'm not sure if there are any bounding sphere issues with Model. Does the same bug happen when you render the model with ModelExperimental?

Source/Scene/ModelAnimationCollection.js Show resolved Hide resolved
* If this is defined, it will be used to compute the local animation time
* instead of the scene's time.
*
* @type {ModelAnimation.AnimationTimeCallback}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make this ModelExperimentalAnimation.AnimationTimeCallback because it's ModelExperimental

Source/Scene/ModelAnimationCollection.js Outdated Show resolved Hide resolved
new Date("January 1, 2014 12:00:00 UTC")
);
const animationCollection = model.activeAnimations;
animationCollection.animateWhilePaused = false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can remove this line for the ModelExperimental spec. The models get deleted after every test here (which is not true for Model) so after creation, this will already default to false.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This made me realize that I should set animateWhilePaused to false in ModelSpec.js at the end of "animates while paused with an explicit animation time".

So I did, and it broke "animates with a multiplier". The reason being that the first render in "animates with a multiplier" uses the same scene time as all the renders in "animates while paused with an explicit animation time", so the first render relied on having animateWhilePaused set, and after fixing that it took the _previousTime shortcut. So I also changed the render time for "animates while paused with an explicit animation time".

So yes, making each test start fresh is a much better design decision...

CHANGES.md Outdated
@@ -10,6 +10,10 @@

- Fixed the inaccurate computation of bounding spheres for `ModelExperimental`. [#10339](https://github.com/CesiumGS/cesium/pull/10339/)

##### Additions :tada:

- Added `ModelAnimationCollection.animateWhilePaused` and `ModelAnimation.animationTime` to allow explicit control over a model's animations.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also can we put the pull request number after the text, similar to other entries in the changelog?

@j9liu
Copy link
Contributor

j9liu commented May 10, 2022

@markw65 let me know when this is ready for a final review!

@markw65
Copy link
Contributor Author

markw65 commented May 10, 2022

I think it's ready.

Copy link
Contributor

@j9liu j9liu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@markw65

Looking good! 😄 Just a few tweaks. Once these and the merge conflict in CHANGES.md are resolved, I'll go ahead with the merge to main.

markw65 added 4 commits May 10, 2022 13:55
Cesium currently only supports time based animation. This can
be inconvenient if the phase of the animation is related to
something other than time (eg distance along a path of an
object moving at a variable speed).

This came up before in CesiumGS#7361, but the author was persuaded that
it was better to use nodeTransformations to explicitly control
the model. That was (just) doable with that example, because
there were just 3 pairs of wheels, all of which needed the
exact same, relatively trivial, transformations. The proposed
solution was also cumbersome, relying on modifying `multiplier`
on the fly, with the downside that modifying multiplier also
reset the phase of the animation.

For more complex models, with less uniform animations, this
approach isn't really doable - especially if you want the same
code to work for multiple models.

This adds an animationTime function to ModelAnimation. If set,
it's used by ModelAnimationCollection.update to compute the
localAnimationTime, rather than using the current clock time.

I also added an animateWhilePaused property to
ModelAnimationCollection. When false (the default), we continue
to do the short circuit exit from ModelAnimationCollection.update
when the scene time hasn't changed. When true, a suitable
animationTime function can continue to animate the model, even when
scene time is paused.

The new sandcastle example is just a clone of Time Dynamic Wheels,
rewritten to use Cesium_Man.glb, and the new functionality.
- rename _prevAnimationTime to _prevAnimationDelta
- rewrite the _prevAnimationDelta bailout
- add a test that we animate while paused if animateWhilePaused is true, and update the existing test to check that we don't animate while paused when animateWhilePaused is false
Use `defined(scheduledAnimation.animationTime)`
@markw65
Copy link
Contributor Author

markw65 commented May 10, 2022

@j9liu Once I'm done, should I squash it down to a single commit, or leave it as is?

@j9liu
Copy link
Contributor

j9liu commented May 10, 2022

@markw65 Leave it as is. Squashing is only important when we need to remove files that were added in the same PR (e.g. the new model and sandcastle).

@markw65
Copy link
Contributor Author

markw65 commented May 10, 2022

@j9liu Ok, I've applied your suggestions, and rebased to fix CHANGES.md.

My thought on squashing is that it makes the git history cleaner.

@j9liu
Copy link
Contributor

j9liu commented May 10, 2022

Merging now -- thanks @markw65 !

@j9liu j9liu merged commit 8f4eca1 into CesiumGS:main May 10, 2022
@markw65
Copy link
Contributor Author

markw65 commented May 10, 2022

@j9liu Thank you!

@markw65 markw65 deleted the manual-animation branch August 10, 2022 15:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Issue/PR closed
Development

Successfully merging this pull request may close these issues.

4 participants