viewerModel.ts 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761
  1. import { ISceneLoaderPlugin, ISceneLoaderPluginAsync, AnimationGroup, Animatable, AbstractMesh, Tools, Scene, SceneLoader, Observable, SceneLoaderProgressEvent, Tags, ParticleSystem, Skeleton, IDisposable, Nullable, Animation, Quaternion, Material, Vector3, AnimationPropertiesOverride, QuinticEase, SineEase, CircleEase, BackEase, BounceEase, CubicEase, ElasticEase, ExponentialEase, PowerEase, QuadraticEase, QuarticEase, PBRMaterial, MultiMaterial } from "babylonjs";
  2. import { GLTFFileLoader, GLTF2 } from "babylonjs-loaders";
  3. import { IModelConfiguration } from "../configuration/interfaces/modelConfiguration";
  4. import { IModelAnimationConfiguration } from "../configuration/interfaces/modelAnimationConfiguration";
  5. import { IModelAnimation, GroupModelAnimation, AnimationPlayMode, ModelAnimationConfiguration, EasingFunction, AnimationState } from "./modelAnimation";
  6. import { deepmerge, extendClassWithConfig } from '../helper/';
  7. import { ObservablesManager } from "../managers/observablesManager";
  8. import { ConfigurationContainer } from "../configuration/configurationContainer";
  9. /**
  10. * The current state of the model
  11. */
  12. export enum ModelState {
  13. INIT,
  14. LOADING,
  15. LOADED,
  16. ENTRY,
  17. ENTRYDONE,
  18. COMPLETE,
  19. CANCELED,
  20. ERROR
  21. }
  22. /**
  23. * The viewer model is a container for all assets representing a sngle loaded model.
  24. */
  25. export class ViewerModel implements IDisposable {
  26. /**
  27. * The loader used to load this model.
  28. */
  29. public loader: ISceneLoaderPlugin | ISceneLoaderPluginAsync;
  30. private _animations: Array<IModelAnimation>;
  31. /**
  32. * the list of meshes that are a part of this model
  33. */
  34. private _meshes: Array<AbstractMesh> = [];
  35. /**
  36. * This model's root mesh (the parent of all other meshes).
  37. * This mesh does not(!) exist in the meshes array.
  38. */
  39. public rootMesh: AbstractMesh;
  40. private _pivotMesh: AbstractMesh;
  41. /**
  42. * ParticleSystems connected to this model
  43. */
  44. public particleSystems: Array<ParticleSystem> = [];
  45. /**
  46. * Skeletons defined in this model
  47. */
  48. public skeletons: Array<Skeleton> = [];
  49. /**
  50. * The current model animation.
  51. * On init, this will be undefined.
  52. */
  53. public currentAnimation: IModelAnimation;
  54. /**
  55. * Observers registered here will be executed when the model is done loading
  56. */
  57. public onLoadedObservable: Observable<ViewerModel>;
  58. /**
  59. * Observers registered here will be executed when the loader notified of a progress event
  60. */
  61. public onLoadProgressObservable: Observable<SceneLoaderProgressEvent>;
  62. /**
  63. * Observers registered here will be executed when the loader notified of an error.
  64. */
  65. public onLoadErrorObservable: Observable<{ message: string; exception: any }>;
  66. /**
  67. * Will be executed after the model finished loading and complete, including entry animation and lod
  68. */
  69. public onCompleteObservable: Observable<ViewerModel>;
  70. /**
  71. * Observers registered here will be executed every time the model is being configured.
  72. * This can be used to extend the model's configuration without extending the class itself
  73. */
  74. public onAfterConfigure: Observable<ViewerModel>;
  75. /**
  76. * The current model state (loaded, error, etc)
  77. */
  78. public state: ModelState;
  79. /**
  80. * A loadID provided by the modelLoader, unique to ths (Abstract)Viewer instance.
  81. */
  82. public loadId: number;
  83. public loadInfo: GLTF2.IAsset;
  84. private _loadedUrl: string;
  85. private _modelConfiguration: IModelConfiguration;
  86. private _loaderDone: boolean = false;
  87. private _entryAnimation: ModelAnimationConfiguration;
  88. private _exitAnimation: ModelAnimationConfiguration;
  89. private _scaleTransition: Animation;
  90. private _animatables: Array<Animatable> = [];
  91. private _frameRate: number = 60;
  92. private _shadowsRenderedAfterLoad: boolean = false;
  93. constructor(private _observablesManager: ObservablesManager, modelConfiguration: IModelConfiguration, private _configurationContainer?: ConfigurationContainer) {
  94. this.onLoadedObservable = new Observable();
  95. this.onLoadErrorObservable = new Observable();
  96. this.onLoadProgressObservable = new Observable();
  97. this.onCompleteObservable = new Observable();
  98. this.onAfterConfigure = new Observable();
  99. this.state = ModelState.INIT;
  100. this.rootMesh = new AbstractMesh("modelRootMesh");
  101. this._pivotMesh = new AbstractMesh("pivotMesh");
  102. this._pivotMesh.parent = this.rootMesh;
  103. // rotate 180, gltf fun
  104. this._pivotMesh.rotation.y += Math.PI;
  105. this._scaleTransition = new Animation("scaleAnimation", "scaling", this._frameRate, Animation.ANIMATIONTYPE_VECTOR3, Animation.ANIMATIONLOOPMODE_CONSTANT);
  106. this._animations = [];
  107. //create a copy of the configuration to make sure it doesn't change even after it is changed in the viewer
  108. this._modelConfiguration = deepmerge((this._configurationContainer && this._configurationContainer.configuration.model) || {}, modelConfiguration);
  109. if (this._observablesManager) { this._observablesManager.onModelAddedObservable.notifyObservers(this); }
  110. if (this._modelConfiguration.entryAnimation) {
  111. this.rootMesh.setEnabled(false);
  112. }
  113. this.onLoadedObservable.add(() => {
  114. this.updateConfiguration(this._modelConfiguration);
  115. if (this._observablesManager) { this._observablesManager.onModelLoadedObservable.notifyObservers(this); }
  116. this._initAnimations();
  117. });
  118. this.onCompleteObservable.add(() => {
  119. this.state = ModelState.COMPLETE;
  120. });
  121. }
  122. public get shadowsRenderedAfterLoad() {
  123. return this._shadowsRenderedAfterLoad;
  124. }
  125. public set shadowsRenderedAfterLoad(rendered: boolean) {
  126. if (!rendered) {
  127. throw new Error("can only be enabled");
  128. } else {
  129. this._shadowsRenderedAfterLoad = rendered;
  130. }
  131. }
  132. public getViewerId() {
  133. return this._configurationContainer && this._configurationContainer.viewerId;
  134. }
  135. /**
  136. * Is this model enabled?
  137. */
  138. public get enabled() {
  139. return this.rootMesh.isEnabled();
  140. }
  141. /**
  142. * Set whether this model is enabled or not.
  143. */
  144. public set enabled(enable: boolean) {
  145. this.rootMesh.setEnabled(enable);
  146. }
  147. public set loaderDone(done: boolean) {
  148. this._loaderDone = done;
  149. this._checkCompleteState();
  150. }
  151. private _checkCompleteState() {
  152. if (this._loaderDone && (this.state === ModelState.ENTRYDONE)) {
  153. this._modelComplete();
  154. }
  155. }
  156. /**
  157. * Add a mesh to this model.
  158. * Any mesh that has no parent will be provided with the root mesh as its new parent.
  159. *
  160. * @param mesh the new mesh to add
  161. * @param triggerLoaded should this mesh trigger the onLoaded observable. Used when adding meshes manually.
  162. */
  163. public addMesh(mesh: AbstractMesh, triggerLoaded?: boolean) {
  164. if (!mesh.parent) {
  165. mesh.parent = this._pivotMesh;
  166. }
  167. mesh.receiveShadows = !!this.configuration.receiveShadows;
  168. this._meshes.push(mesh);
  169. if (triggerLoaded) {
  170. return this.onLoadedObservable.notifyObserversWithPromise(this);
  171. }
  172. }
  173. /**
  174. * get the list of meshes (excluding the root mesh)
  175. */
  176. public get meshes() {
  177. return this._meshes;
  178. }
  179. /**
  180. * Get the model's configuration
  181. */
  182. public get configuration(): IModelConfiguration {
  183. return this._modelConfiguration;
  184. }
  185. /**
  186. * (Re-)set the model's entire configuration
  187. * @param newConfiguration the new configuration to replace the new one
  188. */
  189. public set configuration(newConfiguration: IModelConfiguration) {
  190. this._modelConfiguration = newConfiguration;
  191. this._configureModel();
  192. }
  193. /**
  194. * Update the current configuration with new values.
  195. * Configuration will not be overwritten, but merged with the new configuration.
  196. * Priority is to the new configuration
  197. * @param newConfiguration the configuration to be merged into the current configuration;
  198. */
  199. public updateConfiguration(newConfiguration: Partial<IModelConfiguration>) {
  200. this._modelConfiguration = deepmerge(this._modelConfiguration, newConfiguration);
  201. this._configureModel();
  202. }
  203. private _initAnimations() {
  204. // check if this is not a gltf loader and init the animations
  205. if (this.skeletons.length) {
  206. this.skeletons.forEach((skeleton, idx) => {
  207. let ag = new AnimationGroup("animation-" + idx);
  208. skeleton.getAnimatables().forEach(a => {
  209. if (a.animations[0]) {
  210. ag.addTargetedAnimation(a.animations[0], a);
  211. }
  212. });
  213. this.addAnimationGroup(ag);
  214. });
  215. }
  216. let completeCallback = () => {
  217. }
  218. if (this._modelConfiguration.animation) {
  219. if (this._modelConfiguration.animation.playOnce) {
  220. this._animations.forEach(a => {
  221. a.playMode = AnimationPlayMode.ONCE;
  222. });
  223. }
  224. if (this._modelConfiguration.animation.autoStart && this._animations.length) {
  225. let animationName = this._modelConfiguration.animation.autoStart === true ?
  226. this._animations[0].name : this._modelConfiguration.animation.autoStart;
  227. completeCallback = () => {
  228. this.playAnimation(animationName);
  229. }
  230. }
  231. }
  232. this._enterScene(completeCallback);
  233. }
  234. /**
  235. * Animates the model from the current position to the default position
  236. * @param completeCallback A function to call when the animation has completed
  237. */
  238. private _enterScene(completeCallback?: () => void): void {
  239. const scene = this.rootMesh.getScene();
  240. let previousValue = scene.animationPropertiesOverride!.enableBlending;
  241. let callback = () => {
  242. this.state = ModelState.ENTRYDONE;
  243. scene.animationPropertiesOverride!.enableBlending = previousValue;
  244. this._checkCompleteState();
  245. if (completeCallback) completeCallback();
  246. }
  247. if (!this._entryAnimation) {
  248. callback();
  249. return;
  250. }
  251. this.rootMesh.setEnabled(true);
  252. // disable blending for the sake of the entry animation;
  253. scene.animationPropertiesOverride!.enableBlending = false;
  254. this._applyAnimation(this._entryAnimation, true, callback);
  255. }
  256. /**
  257. * Animates the model from the current position to the exit-screen position
  258. * @param completeCallback A function to call when the animation has completed
  259. */
  260. private _exitScene(completeCallback: () => void): void {
  261. if (!this._exitAnimation) {
  262. completeCallback();
  263. return;
  264. }
  265. this._applyAnimation(this._exitAnimation, false, completeCallback);
  266. }
  267. private _modelComplete() {
  268. //reapply material defines to be sure:
  269. let meshes = this._pivotMesh.getChildMeshes(false);
  270. meshes.filter(m => m.material).forEach((mesh) => {
  271. this._applyModelMaterialConfiguration(mesh.material!);
  272. });
  273. this.state = ModelState.COMPLETE;
  274. this.onCompleteObservable.notifyObservers(this);
  275. }
  276. /**
  277. * Add a new animation group to this model.
  278. * @param animationGroup the new animation group to be added
  279. */
  280. public addAnimationGroup(animationGroup: AnimationGroup) {
  281. this._animations.push(new GroupModelAnimation(animationGroup));
  282. }
  283. /**
  284. * Get the ModelAnimation array
  285. */
  286. public getAnimations(): Array<IModelAnimation> {
  287. return this._animations;
  288. }
  289. /**
  290. * Get the animations' names. Using the names you can play a specific animation.
  291. */
  292. public getAnimationNames(): Array<string> {
  293. return this._animations.map(a => a.name);
  294. }
  295. /**
  296. * Get an animation by the provided name. Used mainly when playing n animation.
  297. * @param name the name of the animation to find
  298. */
  299. protected _getAnimationByName(name: string): Nullable<IModelAnimation> {
  300. // can't use .find, noe available on IE
  301. let filtered = this._animations.filter(a => a.name === name);
  302. // what the next line means - if two animations have the same name, they will not be returned!
  303. if (filtered.length === 1) {
  304. return filtered[0];
  305. } else {
  306. return null;
  307. }
  308. }
  309. /**
  310. * Choose an initialized animation using its name and start playing it
  311. * @param name the name of the animation to play
  312. * @returns The model aniamtion to be played.
  313. */
  314. public playAnimation(name: string): IModelAnimation {
  315. let animation = this.setCurrentAnimationByName(name);
  316. if (animation) {
  317. animation.start();
  318. }
  319. return animation;
  320. }
  321. public setCurrentAnimationByName(name: string) {
  322. let animation = this._getAnimationByName(name);
  323. if (animation) {
  324. if (this.currentAnimation && this.currentAnimation.state !== AnimationState.STOPPED) {
  325. this.currentAnimation.stop();
  326. }
  327. this.currentAnimation = animation;
  328. return animation;
  329. } else {
  330. throw new Error("animation not found - " + name);
  331. }
  332. }
  333. private _configureModel() {
  334. // this can be changed to the meshes that have rootMesh a parent without breaking anything.
  335. let meshesWithNoParent: Array<AbstractMesh> = [this.rootMesh] //this._meshes.filter(m => m.parent === this.rootMesh);
  336. let updateMeshesWithNoParent = (variable: string, value: any, param?: string) => {
  337. meshesWithNoParent.forEach(mesh => {
  338. if (param) {
  339. mesh[variable][param] = value;
  340. } else {
  341. mesh[variable] = value;
  342. }
  343. });
  344. }
  345. let updateXYZ = (variable: string, configValues: { x: number, y: number, z: number, w?: number }) => {
  346. if (configValues.x !== undefined) {
  347. updateMeshesWithNoParent(variable, configValues.x, 'x');
  348. }
  349. if (configValues.y !== undefined) {
  350. updateMeshesWithNoParent(variable, configValues.y, 'y');
  351. }
  352. if (configValues.z !== undefined) {
  353. updateMeshesWithNoParent(variable, configValues.z, 'z');
  354. }
  355. if (configValues.w !== undefined) {
  356. updateMeshesWithNoParent(variable, configValues.w, 'w');
  357. }
  358. }
  359. if (this._modelConfiguration.normalize) {
  360. let center = false;
  361. let unitSize = false;
  362. let parentIndex;
  363. if (this._modelConfiguration.normalize === true) {
  364. center = true;
  365. unitSize = true;
  366. } else {
  367. center = !!this._modelConfiguration.normalize.center;
  368. unitSize = !!this._modelConfiguration.normalize.unitSize;
  369. parentIndex = this._modelConfiguration.normalize.parentIndex;
  370. }
  371. let meshesToNormalize: Array<AbstractMesh> = [];
  372. if (parentIndex !== undefined) {
  373. meshesToNormalize.push(this._meshes[parentIndex]);
  374. } else {
  375. meshesToNormalize = this._pivotMesh.getChildMeshes(true).length === 1 ? [this._pivotMesh] : meshesWithNoParent;
  376. }
  377. if (unitSize) {
  378. meshesToNormalize.forEach(mesh => {
  379. mesh.normalizeToUnitCube(true);
  380. mesh.computeWorldMatrix(true);
  381. });
  382. }
  383. if (center) {
  384. meshesToNormalize.forEach(mesh => {
  385. const boundingInfo = mesh.getHierarchyBoundingVectors(true);
  386. const sizeVec = boundingInfo.max.subtract(boundingInfo.min);
  387. const halfSizeVec = sizeVec.scale(0.5);
  388. const center = boundingInfo.min.add(halfSizeVec);
  389. mesh.position = center.scale(-1);
  390. mesh.position.y += halfSizeVec.y;
  391. // Recompute Info.
  392. mesh.computeWorldMatrix(true);
  393. });
  394. }
  395. } else {
  396. // if centered, should be done here
  397. }
  398. // position?
  399. if (this._modelConfiguration.position) {
  400. updateXYZ('position', this._modelConfiguration.position);
  401. }
  402. if (this._modelConfiguration.rotation) {
  403. //quaternion?
  404. if (this._modelConfiguration.rotation.w) {
  405. meshesWithNoParent.forEach(mesh => {
  406. if (!mesh.rotationQuaternion) {
  407. mesh.rotationQuaternion = new Quaternion();
  408. }
  409. })
  410. updateXYZ('rotationQuaternion', this._modelConfiguration.rotation);
  411. } else {
  412. updateXYZ('rotation', this._modelConfiguration.rotation);
  413. }
  414. }
  415. if (this._modelConfiguration.rotationOffsetAxis) {
  416. let rotationAxis = new Vector3(0, 0, 0).copyFrom(this._modelConfiguration.rotationOffsetAxis as Vector3);
  417. meshesWithNoParent.forEach(m => {
  418. if (this._modelConfiguration.rotationOffsetAngle) {
  419. m.rotate(rotationAxis, this._modelConfiguration.rotationOffsetAngle);
  420. }
  421. });
  422. }
  423. if (this._modelConfiguration.scaling) {
  424. updateXYZ('scaling', this._modelConfiguration.scaling);
  425. }
  426. if (this._modelConfiguration.castShadow) {
  427. this._meshes.forEach(mesh => {
  428. Tags.AddTagsTo(mesh, 'castShadow');
  429. });
  430. }
  431. let meshes = this._pivotMesh.getChildMeshes(false);
  432. meshes.filter(m => m.material).forEach((mesh) => {
  433. this._applyModelMaterialConfiguration(mesh.material!);
  434. });
  435. if (this._modelConfiguration.entryAnimation) {
  436. this._entryAnimation = this._modelAnimationConfigurationToObject(this._modelConfiguration.entryAnimation);
  437. }
  438. if (this._modelConfiguration.exitAnimation) {
  439. this._exitAnimation = this._modelAnimationConfigurationToObject(this._modelConfiguration.exitAnimation);
  440. }
  441. this.onAfterConfigure.notifyObservers(this);
  442. }
  443. private _modelAnimationConfigurationToObject(animConfig: IModelAnimationConfiguration): ModelAnimationConfiguration {
  444. let anim: ModelAnimationConfiguration = {
  445. time: 0.5
  446. };
  447. if (animConfig.scaling) {
  448. anim.scaling = Vector3.Zero();
  449. }
  450. if (animConfig.easingFunction !== undefined) {
  451. anim.easingFunction = animConfig.easingFunction;
  452. }
  453. if (animConfig.easingMode !== undefined) {
  454. anim.easingMode = animConfig.easingMode;
  455. }
  456. extendClassWithConfig(anim, animConfig);
  457. return anim;
  458. }
  459. /**
  460. * Apply a material configuration to a material
  461. * @param material Material to apply configuration to
  462. */
  463. public _applyModelMaterialConfiguration(material: Material) {
  464. if (!this._modelConfiguration.material) return;
  465. extendClassWithConfig(material, this._modelConfiguration.material);
  466. if (material instanceof PBRMaterial) {
  467. if (this._modelConfiguration.material.directIntensity !== undefined) {
  468. material.directIntensity = this._modelConfiguration.material.directIntensity;
  469. }
  470. if (this._modelConfiguration.material.emissiveIntensity !== undefined) {
  471. material.emissiveIntensity = this._modelConfiguration.material.emissiveIntensity;
  472. }
  473. if (this._modelConfiguration.material.environmentIntensity !== undefined) {
  474. material.environmentIntensity = this._modelConfiguration.material.environmentIntensity;
  475. }
  476. if (this._modelConfiguration.material.directEnabled !== undefined) {
  477. material.disableLighting = !this._modelConfiguration.material.directEnabled;
  478. }
  479. if (this._configurationContainer && this._configurationContainer.reflectionColor) {
  480. material.reflectionColor = this._configurationContainer.reflectionColor;
  481. }
  482. }
  483. else if (material instanceof MultiMaterial) {
  484. for (let i = 0; i < material.subMaterials.length; i++) {
  485. const subMaterial = material.subMaterials[i];
  486. if (subMaterial) {
  487. this._applyModelMaterialConfiguration(subMaterial);
  488. }
  489. }
  490. }
  491. }
  492. /**
  493. * Start entry/exit animation given an animation configuration
  494. * @param animationConfiguration Entry/Exit animation configuration
  495. * @param isEntry Pass true if the animation is an entry animation
  496. * @param completeCallback Callback to execute when the animation completes
  497. */
  498. private _applyAnimation(animationConfiguration: ModelAnimationConfiguration, isEntry: boolean, completeCallback?: () => void) {
  499. let animations: Animation[] = [];
  500. //scale
  501. if (animationConfiguration.scaling) {
  502. let scaleStart: Vector3 = isEntry ? animationConfiguration.scaling : new Vector3(1, 1, 1);
  503. let scaleEnd: Vector3 = isEntry ? new Vector3(1, 1, 1) : animationConfiguration.scaling;
  504. if (!scaleStart.equals(scaleEnd)) {
  505. this.rootMesh.scaling = scaleStart;
  506. this._setLinearKeys(
  507. this._scaleTransition,
  508. this.rootMesh.scaling,
  509. scaleEnd,
  510. animationConfiguration.time
  511. );
  512. animations.push(this._scaleTransition);
  513. }
  514. }
  515. //Start the animation(s)
  516. this.transitionTo(
  517. animations,
  518. animationConfiguration.time,
  519. this._createEasingFunction(animationConfiguration.easingFunction),
  520. animationConfiguration.easingMode,
  521. () => { if (completeCallback) completeCallback(); }
  522. );
  523. }
  524. /**
  525. * Begin @animations with the specified @easingFunction
  526. * @param animations The BABYLON Animations to begin
  527. * @param duration of transition, in seconds
  528. * @param easingFunction An easing function to apply
  529. * @param easingMode A easing mode to apply to the easingFunction
  530. * @param onAnimationEnd Call back trigger at the end of the animation.
  531. */
  532. public transitionTo(
  533. animations: Animation[],
  534. duration: number,
  535. easingFunction: any,
  536. easingMode: number = BABYLON.EasingFunction.EASINGMODE_EASEINOUT,
  537. onAnimationEnd: () => void): void {
  538. if (easingFunction) {
  539. for (let animation of animations) {
  540. easingFunction.setEasingMode(easingMode);
  541. animation.setEasingFunction(easingFunction);
  542. }
  543. }
  544. //Stop any current animations before starting the new one - merging not yet supported.
  545. this.stopAllAnimations();
  546. this.rootMesh.animations = animations;
  547. if (this.rootMesh.getScene().beginAnimation) {
  548. let animatable: Animatable = this.rootMesh.getScene().beginAnimation(this.rootMesh, 0, this._frameRate * duration, false, 1, () => {
  549. if (onAnimationEnd) {
  550. onAnimationEnd();
  551. }
  552. });
  553. this._animatables.push(animatable);
  554. }
  555. }
  556. /**
  557. * Sets key values on an Animation from first to last frame.
  558. * @param animation The Babylon animation object to set keys on
  559. * @param startValue The value of the first key
  560. * @param endValue The value of the last key
  561. * @param duration The duration of the animation, used to determine the end frame
  562. */
  563. private _setLinearKeys(animation: Animation, startValue: any, endValue: any, duration: number) {
  564. animation.setKeys([
  565. {
  566. frame: 0,
  567. value: startValue
  568. },
  569. {
  570. frame: this._frameRate * duration,
  571. value: endValue
  572. }
  573. ]);
  574. }
  575. /**
  576. * Creates and returns a Babylon easing funtion object based on a string representing the Easing function
  577. * @param easingFunctionID The enum of the easing funtion to create
  578. * @return The newly created Babylon easing function object
  579. */
  580. private _createEasingFunction(easingFunctionID?: number): any {
  581. let easingFunction;
  582. switch (easingFunctionID) {
  583. case EasingFunction.CircleEase:
  584. easingFunction = new CircleEase();
  585. break;
  586. case EasingFunction.BackEase:
  587. easingFunction = new BackEase(0.3);
  588. break;
  589. case EasingFunction.BounceEase:
  590. easingFunction = new BounceEase();
  591. break;
  592. case EasingFunction.CubicEase:
  593. easingFunction = new CubicEase();
  594. break;
  595. case EasingFunction.ElasticEase:
  596. easingFunction = new ElasticEase();
  597. break;
  598. case EasingFunction.ExponentialEase:
  599. easingFunction = new ExponentialEase();
  600. break;
  601. case EasingFunction.PowerEase:
  602. easingFunction = new PowerEase();
  603. break;
  604. case EasingFunction.QuadraticEase:
  605. easingFunction = new QuadraticEase();
  606. break;
  607. case EasingFunction.QuarticEase:
  608. easingFunction = new QuarticEase();
  609. break;
  610. case EasingFunction.QuinticEase:
  611. easingFunction = new QuinticEase();
  612. break;
  613. case EasingFunction.SineEase:
  614. easingFunction = new SineEase();
  615. break;
  616. default:
  617. Tools.Log("No ease function found");
  618. break;
  619. }
  620. return easingFunction;
  621. }
  622. /**
  623. * Stops and removes all animations that have been applied to the model
  624. */
  625. public stopAllAnimations(): void {
  626. if (this.rootMesh) {
  627. this.rootMesh.animations = [];
  628. }
  629. if (this.currentAnimation) {
  630. this.currentAnimation.stop();
  631. }
  632. while (this._animatables.length) {
  633. this._animatables[0].onAnimationEnd = null;
  634. this._animatables[0].stop();
  635. this._animatables.shift();
  636. }
  637. }
  638. /**
  639. * Will remove this model from the viewer (but NOT dispose it).
  640. */
  641. public remove() {
  642. this.stopAllAnimations();
  643. // hide it
  644. this.rootMesh.isVisible = false;
  645. if (this._observablesManager) { this._observablesManager.onModelRemovedObservable.notifyObservers(this); }
  646. }
  647. /**
  648. * Dispose this model, including all of its associated assets.
  649. */
  650. public dispose() {
  651. this.remove();
  652. this.onAfterConfigure.clear();
  653. this.onLoadedObservable.clear();
  654. this.onLoadErrorObservable.clear();
  655. this.onLoadProgressObservable.clear();
  656. if (this.loader && this.loader.name === "gltf") {
  657. (<GLTFFileLoader>this.loader).dispose();
  658. }
  659. this.particleSystems.forEach(ps => ps.dispose());
  660. this.particleSystems.length = 0;
  661. this.skeletons.forEach(s => s.dispose());
  662. this.skeletons.length = 0;
  663. this._animations.forEach(ag => ag.dispose());
  664. this._animations.length = 0;
  665. this.rootMesh.dispose(false, true);
  666. }
  667. }