viewerModel.ts 28 KB

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