@google/model-viewer
Version:
Easily display interactive 3D models on the web and in AR!
162 lines (128 loc) • 4.54 kB
text/typescript
/* @license
* Copyright 2019 Google LLC. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {property} from 'lit-element';
import ModelViewerElementBase, {$hasTransitioned, $needsRender, $onModelLoad, $renderer, $scene, $tick, $updateSource} from '../model-viewer-base.js';
import {Constructor} from '../utilities.js';
const MILLISECONDS_PER_SECOND = 1000.0
const $changeAnimation = Symbol('changeAnimation');
const $paused = Symbol('paused');
export declare interface AnimationInterface {
autoplay: boolean;
animationName: string|void;
animationCrossfadeDuration: number;
readonly availableAnimations: Array<string>;
readonly paused: boolean;
readonly duration: number;
currentTime: number;
pause(): void;
play(): void;
}
export const AnimationMixin = <T extends Constructor<ModelViewerElementBase>>(
ModelViewerElement: T): Constructor<AnimationInterface>&T => {
class AnimationModelViewerElement extends ModelViewerElement {
autoplay: boolean = false;
animationName: string|undefined = undefined;
animationCrossfadeDuration: number = 300;
protected[$paused]: boolean = true;
/**
* Returns an array
*/
get availableAnimations(): Array<string> {
if (this.loaded) {
return this[$scene].animationNames;
}
return [];
}
get duration(): number {
return this[$scene].duration;
}
get paused(): boolean {
return this[$paused];
}
get currentTime(): number {
return this[$scene].animationTime;
}
set currentTime(value: number) {
this[$scene].animationTime = value;
this[$renderer].threeRenderer.shadowMap.needsUpdate = true;
this[$needsRender]();
}
pause() {
if (this[$paused]) {
return;
}
this[$paused] = true;
this[$renderer].threeRenderer.shadowMap.autoUpdate = false;
this.dispatchEvent(new CustomEvent('pause'));
}
play() {
if (this[$paused] && this.availableAnimations.length > 0) {
this[$paused] = false;
this[$renderer].threeRenderer.shadowMap.autoUpdate = true;
if (!this[$scene].hasActiveAnimation) {
this[$changeAnimation]();
}
this.dispatchEvent(new CustomEvent('play'));
}
}
[$onModelLoad]() {
super[$onModelLoad]();
this[$paused] = true;
if (this.autoplay) {
this[$changeAnimation]();
this.play();
}
}
[$tick](_time: number, delta: number) {
super[$tick](_time, delta);
if (this[$paused] || !this[$hasTransitioned]()) {
return;
}
this[$scene].updateAnimation(delta / MILLISECONDS_PER_SECOND);
this[$needsRender]();
}
updated(changedProperties: Map<string, any>) {
super.updated(changedProperties);
if (changedProperties.has('autoplay') && this.autoplay) {
this.play();
}
if (changedProperties.has('animationName')) {
this[$changeAnimation]();
}
}
async[$updateSource]() {
// If we are loading a new model, we need to stop the animation of
// the current one (if any is playing). Otherwise, we might lose
// the reference to the scene root and running actions start to
// throw exceptions and/or behave in unexpected ways:
this[$scene].stopAnimation();
return super[$updateSource]();
}
[$changeAnimation]() {
this[$scene].playAnimation(
this.animationName,
this.animationCrossfadeDuration / MILLISECONDS_PER_SECOND);
// If we are currently paused, we need to force a render so that
// the scene updates to the first frame of the new animation
if (this[$paused]) {
this[$scene].updateAnimation(0);
this[$needsRender]();
}
}
}
return AnimationModelViewerElement;
};