viewerModel.ts 27 KB

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