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

Animations #421

Merged
merged 7 commits into from
Mar 19, 2019
Merged

Animations #421

merged 7 commits into from
Mar 19, 2019

Conversation

cdata
Copy link
Contributor

@cdata cdata commented Mar 8, 2019

Animations

This change proposes the addition of new API (and related bug fixes) to configure and present animated glTFs with <model-viewer>. Morph, skeletal and keyframe should all be supported.

New public API introduced by this change:

Attribute Default value Description
autoplay                                        false If true, animations will play. Otherwise, they will be stopped. Playing animations will be reset if animated changes to false.
animation-name undefined If set, <model-viewer> will look for an animation with this name when played. If an animation with that name is found, plays that animation. Otherwise, if an animation with that name is not found, falls back to the first animation (if any).
animation-crossfade-duration 300 The duration of the crossfade between animations when changing them via animation-name.
Property Default value Description
availableAnimations [] After a model finishes loading, if that model has any animations, they will be listed by string name in this property's value.
paused true Readonly, reflects the play/pause state of the <model-viewer>. A <model-viewer> displaying a model with no animations is always considered paused.
currentTime 0 This read/write property represents the current track time (in seconds) of the currently playing animation (if any). If a value is assigned, the animation will seek to the time specified.
Method Description
play() Plays the animation (selected by animation-name, or else falling back to the first animation) if it is available.
pause() Pauses any currently playing animation
Event Description
pause Dispatched when the <model-viewer> is paused. The implicit default state of <model-viewer> is paused, and a pause event will not be dispatched until the <model-viewer> has been played at least once.
play Dispatched when the <model-viewer> begins playing animations.
poster-visibility This event is fired when the visibility of the poster image changes. The current visibility state can be read from the event.detail.visible property

Additional highlights

Examples

autoplay animation-name="Run" Switching animationName on an interval
animated animation-name switch-animation-name
Animated model, in paused state Switch animation-name while paused
pauseswitch

Future work

Fixes #404
Fixes #371

@googlebot
Copy link

So there's good news and bad news.

👍 The good news is that everyone that needs to sign a CLA (the pull request submitter and all commit authors) have done so. Everything is all good there.

😕 The bad news is that it appears that one or more commits were authored or co-authored by someone other than the pull request submitter. We need to confirm that all authors are ok with their commits being contributed to this project. Please have them confirm that here in the pull request.

Note to project maintainer: This is a terminal state, meaning the cla/google commit status will not change from this state. It's up to you to confirm consent of all the commit author(s), set the cla label to yes (if enabled on your project), and then merge this pull request when appropriate.

ℹ️ Googlers: Go here for more info.

@mrdoob mrdoob mentioned this pull request Mar 8, 2019
@cdata cdata force-pushed the animations branch 3 times, most recently from 454cbbf to f5559bf Compare March 14, 2019 05:09
@cdata cdata changed the title (WIP) Animations Animations Mar 14, 2019
@cdata cdata marked this pull request as ready for review March 14, 2019 05:52
@cdata cdata requested review from mrdoob, jsantell and smalls March 14, 2019 18:30
jsantell
jsantell previously approved these changes Mar 14, 2019
Copy link
Contributor

@jsantell jsantell left a comment

Choose a reason for hiding this comment

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

😍 great work!

RobotExpressive by <a href="https://www.patreon.com/quaternius">Tomás Laulhé</a>,
licensed under <a href="https://creativecommons.org/publicdomain/zero/1.0/">CC0</a>
(<a href="https://github.com/mrdoob/three.js/tree/dev/examples/models/gltf/RobotExpressive">source</a>)

## small_hangar_01
Copy link
Contributor

Choose a reason for hiding this comment

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

Horse.glb needs attribution

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 fix

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Attribution added

index.html Outdated
@@ -149,6 +153,27 @@ <h4>src<a href="#required-for-display">*</a></h4>
<h4>alt</h4>
<p>Configures the model with custom text that will be used to describe the model to viewers who use a screen reader or otherwise depend on additional semantic context to understand what they are viewing.</p>
</li>
<li>
<h4>animated</h4>
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 animate might be a better attribute name than animated (present vs past tense), although can't think of good prior art (other than "checked", which is past tense, but seems like a different case?)

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 fix

Copy link
Contributor Author

@cdata cdata Mar 14, 2019

Choose a reason for hiding this comment

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

Ugh, I forgot: I originally used animate, but it's a built-in method of HTMLElement (part of the Web Animations API).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

(and yah that's definitely not going to be confusing for folks at all)

😩

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 has been left as is, but if there is another name we like that doesn't overlap with a built-in API, I am open to changing it.

class AnimationModelViewerElement extends ModelViewerElement {
@property({type: Boolean}) animated: boolean = false;
@property({type: Boolean, attribute: 'pause-animation'})
pauseAnimation: boolean = false;
Copy link
Contributor

Choose a reason for hiding this comment

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

pause-animation seems unused/undocumented (other than in the scenario where it's checked with !animated), is this necessary?

Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of pause-animation, there does seem like there would be a need to reset back to the default/T-Pose, so there is a difference between 'pausing' the animation (freezing in the middle of an animation) as opposed to 'removing' the animation -- of course, I don't think it necessary to solve this in this PR

Copy link
Contributor Author

@cdata cdata Mar 14, 2019

Choose a reason for hiding this comment

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

Yes, it is different from stopping the animation. Pause animation is documented, and will be necessary in order to implement @yuinchien 's mocks to spec in #425

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think the effect you are describing is achieved when setting animate to false. Did you have something else in mind?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

pause-animation was not adequately documented, sorry. I added docs for it.

Copy link
Contributor

Choose a reason for hiding this comment

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

@cdata yes, with pause-animation and !animate, both 'freeze' and 'reset' flows are supported

Copy link
Contributor Author

Choose a reason for hiding this comment

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

After API refactor, to "pause" you must invoke pause() (there is no corresponding attribute/property). And, to "stop" you subsequently set .currentTime = 0.

* model. The promise rejects if the currently loaded model is
* changed to something new due to the src attribute changing.
*/
[$updateModelLoadsPromise]() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Environments has a similar need/way of doing this -- wonder if there's a way to generalize this across all features?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

After offline discussion, I'm looking into whether this is necessary or not.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I reduced the complexity of this implementation substantially, and I think it should be fine. Thanks for calling this out!

// NOTE(cdata): If a property changes from values A -> B -> A in the space
// of a microtask, LitElement/UpdatingElement will notify of a change even
// though the value has effectively not changed, so we need to check to make
// sure that the value has actually changed before changing the loaded flag.
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems like it'd be valuable for all properties, not just src

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Possibly, although the consequences of src change side-effects occurring unnecessarily seem particularly severe as they could affect all other features.

@@ -53,18 +56,19 @@ export const LoadingMixin = (ModelViewerElement) => {
}

get loaded() {
return super.loaded || this[$preloaded];
return super.loaded ||
(this.src && CachingGLTFLoader.hasFinishedLoading(this.src));
Copy link
Contributor

Choose a reason for hiding this comment

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

This method, while probably valuable for tests at least, seems that it'd be more appropriate to live on this.src, our Model class (which could just wrap this); CachingGLTFLoader is an implementation detail that would benefit from continuing being hidden inside of Model, or at least out of features/*

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't know that I agree. CachingGLTFLoader was in fact the abstraction that enabled preloading to work in parallel with normal loading via setting src. Please offer a more concrete proposal for how you would change this!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Keeping this as-is, but remain open to suggestions. I don't think it's the right answer to conflate preloading and the implementation in the Model class, because untangling that is what begot CachingGLTFLoader in the first place.

.catch(() => {}); // Silently ignore exceptions here, they should be
// caught by the invoking function
cache.set(url, loadAttempt);
// window.loadAttempt = loadAttempt;
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: comments

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 fix

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed

if (preloaded) {
this.dispatchEvent(new CustomEvent('preload', {detail}));
} else {
loader.preload(this.src)
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: prefer await

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll re-evaluate if this depends on asynchronous code flow

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I factored this out so that it's wrapped in an async function.

.then(() => {
preloaded.set(url, true);
})
.catch(() => {}); // Silently ignore exceptions here, they should be
Copy link
Contributor

Choose a reason for hiding this comment

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

Any reason using .then instead of await? This will swallow exceptions as indicated, but how would invoking function catch that if they're not thrown?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

When you invoke then or catch in a promise, it does not modify the state of a promise in place. The methods return new promise instances that are resolved differently depending on how you invoked them.

We don't cache the "caught" promise. So, try/catch blocks outside of this scope will still work as expected.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

And yeah, the reason for using then is that the asynchronous behavior is preferred.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As mentioned in #421 (comment) I'll re-evaluate if this is necessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Rewritten with await.

const scene = await this.loader.load(url);
// If we have pending work due to a previous source change in progress,
// cancel it so that we do not incur a race condition:
if (this[$cancelPendingSourceChange] != null) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Similar concept to other features ensuring that the changes are still valid; can that be abstracted so that model can handle it everywhere? No good ideas currently, but this seems like a potential growing source of race conditions throughout features

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I disagree, this problem is fairly unique to the consequences of changing the src. Let's discuss offline.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

After reviewing, I remain convinced that changing the src requires special considerations. I'm open to concrete suggestions for refactoring, though.

@cdata
Copy link
Contributor Author

cdata commented Mar 15, 2019

Tests ended in IE11 timeout, flagging for #433

@mrdoob
Copy link
Collaborator

mrdoob commented Mar 15, 2019

What about autoplay instead of animated? Mimicking audio and video tags.

@cdata
Copy link
Contributor Author

cdata commented Mar 15, 2019

autoplay is a good idea, I like that.

Although conceptually, we would probably have to introduce an explicit API for pausing + resetting the animation.

For example, to "stop" a <video> you have to do something along the lines of:

video.pause();
video.currentTime = 0;

WDYT? Should we go for something more like that?

I'm also interested to hear what others think of autoplay as a name @jsantell @smalls

@smalls
Copy link
Contributor

smalls commented Mar 15, 2019

I'm good with autoplay - in some ways it's not as descriptive (there's no association with an animation), but I think the developer intent is clearer (with animated, for instance, it's not clear if it should be animated our could be animated).

Chris and I chatted about pause() && currentTime=0 vs stop(); I'm fine with the former, and I think that'll give developers extra flexibility. We can always add stop() on top of that as a convenience method if needed.

Overall using <video> as inspiration for the API makes sense - they've probably thought through some of these issues already.

@cdata
Copy link
Contributor Author

cdata commented Mar 15, 2019

This all SGTM. I'm going to change the current proposal so that the API tracks closely to <video>. The bill of changes looks like this in my mind, so please call out if you think any of these changes aren't right:

  • animated will become autoplay
  • A play method will be added, and associated play event dispatched when appropriate
  • A pause method and a paused property will be added, and associated pause event dispatched when appropriate
    • pause-animations attribute/property will be removed
  • currentTime will be exposed as a read/write property
  • animation-name, availableAnimations and animation-crossfade-duration will remain as-is

@cdata
Copy link
Contributor Author

cdata commented Mar 18, 2019

  • API has been overhauled to be more like <video> (PR description updated)
  • Additional tests / demo added
  • Docs updated to reflect API change

PTAL at your leisure!

@cdata cdata requested a review from jsantell March 18, 2019 16:08
// preloaded.set(url, true);
//})
//.catch(() => {}); // Silently ignore exceptions here, they should be
//// caught by the invoking function
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Need to remove

smalls
smalls previously approved these changes Mar 19, 2019
Copy link
Contributor

@smalls smalls left a comment

Choose a reason for hiding this comment

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

Thanks, the API's looking great!

@cdata cdata merged commit 86f704a into master Mar 19, 2019
@cdata cdata deleted the animations branch March 19, 2019 22:34
@mrdoob
Copy link
Collaborator

mrdoob commented Mar 19, 2019

🎉🎉🎉

@sowmyy
Copy link

sowmyy commented Jun 12, 2020

Is there a way to import the array from the gltf file and use it in the component? Because, for me only one of the multiple animations play. rest doesn't! Is this bug already being resolved or yet to ? Also, which is the recommended format for rendering animated 3D models - GLTF or GLB ?? @cdata

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Enable user to configure currently active animation Failure to render glTFs with animations
6 participants