viewerModel.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. import { ISceneLoaderPlugin, ISceneLoaderPluginAsync, AnimationGroup, Animatable, AbstractMesh, Tools, Scene, SceneLoader, Observable, SceneLoaderProgressEvent, Tags, ParticleSystem, Skeleton, IDisposable, Nullable, Animation, GLTFFileLoader, Quaternion } from "babylonjs";
  2. import { IModelConfiguration } from "../configuration/configuration";
  3. import { IModelAnimation, GroupModelAnimation, AnimationPlayMode } from "./modelAnimation";
  4. import * as deepmerge from '../../assets/deepmerge.min.js';
  5. import { AbstractViewer } from "..";
  6. export enum ModelState {
  7. INIT,
  8. LOADING,
  9. LOADED,
  10. CANCELED,
  11. ERROR
  12. }
  13. /**
  14. * The viewer model is a container for all assets representing a sngle loaded model.
  15. */
  16. export class ViewerModel implements IDisposable {
  17. /**
  18. * The loader used to load this model.
  19. */
  20. public loader: ISceneLoaderPlugin | ISceneLoaderPluginAsync;
  21. private _animations: Array<IModelAnimation>;
  22. /**
  23. * the list of meshes that are a part of this model
  24. */
  25. public meshes: Array<AbstractMesh> = [];
  26. /**
  27. * This model's root mesh (the parent of all other meshes).
  28. * This mesh also exist in the meshes array.
  29. */
  30. public rootMesh: AbstractMesh;
  31. /**
  32. * ParticleSystems connected to this model
  33. */
  34. public particleSystems: Array<ParticleSystem> = [];
  35. /**
  36. * Skeletons defined in this model
  37. */
  38. public skeletons: Array<Skeleton> = [];
  39. /**
  40. * The current model animation.
  41. * On init, this will be undefined.
  42. */
  43. public currentAnimation: IModelAnimation;
  44. /**
  45. * Observers registered here will be executed when the model is done loading
  46. */
  47. public onLoadedObservable: Observable<ViewerModel>;
  48. /**
  49. * Observers registered here will be executed when the loader notified of a progress event
  50. */
  51. public onLoadProgressObservable: Observable<SceneLoaderProgressEvent>;
  52. /**
  53. * Observers registered here will be executed when the loader notified of an error.
  54. */
  55. public onLoadErrorObservable: Observable<{ message: string; exception: any }>;
  56. /**
  57. * Observers registered here will be executed every time the model is being configured.
  58. * This can be used to extend the model's configuration without extending the class itself
  59. */
  60. public onAfterConfigure: Observable<ViewerModel>;
  61. /**
  62. * The current model state (loaded, error, etc)
  63. */
  64. public state: ModelState;
  65. /**
  66. * A loadID provided by the modelLoader, unique to ths (Abstract)Viewer instance.
  67. */
  68. public loadId: number;
  69. private _loadedUrl: string;
  70. private _modelConfiguration: IModelConfiguration;
  71. constructor(private _viewer: AbstractViewer, modelConfiguration: IModelConfiguration) {
  72. this.onLoadedObservable = new Observable();
  73. this.onLoadErrorObservable = new Observable();
  74. this.onLoadProgressObservable = new Observable();
  75. this.onAfterConfigure = new Observable();
  76. this.state = ModelState.INIT;
  77. this._animations = [];
  78. //create a copy of the configuration to make sure it doesn't change even after it is changed in the viewer
  79. this._modelConfiguration = deepmerge({}, modelConfiguration);
  80. this._viewer.models.push(this);
  81. }
  82. /**
  83. * Get the model's configuration
  84. */
  85. public get configuration(): IModelConfiguration {
  86. return this._modelConfiguration;
  87. }
  88. /**
  89. * (Re-)set the model's entire configuration
  90. * @param newConfiguration the new configuration to replace the new one
  91. */
  92. public set configuration(newConfiguration: IModelConfiguration) {
  93. this._modelConfiguration = newConfiguration;
  94. this._configureModel();
  95. }
  96. /**
  97. * Update the current configuration with new values.
  98. * Configuration will not be overwritten, but merged with the new configuration.
  99. * Priority is to the new configuration
  100. * @param newConfiguration the configuration to be merged into the current configuration;
  101. */
  102. public updateConfiguration(newConfiguration: Partial<IModelConfiguration>) {
  103. this._modelConfiguration = deepmerge(this._modelConfiguration, newConfiguration);
  104. this._configureModel();
  105. }
  106. public initAnimations() {
  107. this._animations.forEach(a => {
  108. a.dispose();
  109. });
  110. this._animations.length = 0;
  111. // check if this is not a gltf loader and init the animations
  112. if (this.loader.name !== 'gltf') {
  113. this.skeletons.forEach((skeleton, idx) => {
  114. let ag = new AnimationGroup("animation-" + idx, this._viewer.scene);
  115. skeleton.getAnimatables().forEach(a => {
  116. if (a.animations[0]) {
  117. ag.addTargetedAnimation(a.animations[0], a);
  118. }
  119. });
  120. this.addAnimationGroup(ag);
  121. });
  122. }
  123. if (!this._modelConfiguration) return;
  124. if (this._modelConfiguration.animation) {
  125. if (this._modelConfiguration.animation.playOnce) {
  126. this._animations.forEach(a => {
  127. a.playMode = AnimationPlayMode.ONCE;
  128. });
  129. }
  130. if (this._modelConfiguration.animation.autoStart && this._animations.length) {
  131. let animationName = this._modelConfiguration.animation.autoStart === true ?
  132. this._animations[0].name : this._modelConfiguration.animation.autoStart;
  133. this.playAnimation(animationName);
  134. }
  135. }
  136. }
  137. /**
  138. * Add a new animation group to this model.
  139. * @param animationGroup the new animation group to be added
  140. */
  141. public addAnimationGroup(animationGroup: AnimationGroup) {
  142. this._animations.push(new GroupModelAnimation(animationGroup));
  143. }
  144. /**
  145. * Get the ModelAnimation array
  146. */
  147. public getAnimations(): Array<IModelAnimation> {
  148. return this._animations;
  149. }
  150. /**
  151. * Get the animations' names. Using the names you can play a specific animation.
  152. */
  153. public getAnimationNames(): Array<string> {
  154. return this._animations.map(a => a.name);
  155. }
  156. /**
  157. * Get an animation by the provided name. Used mainly when playing n animation.
  158. * @param name the name of the animation to find
  159. */
  160. protected _getAnimationByName(name: string): Nullable<IModelAnimation> {
  161. // can't use .find, noe available on IE
  162. let filtered = this._animations.filter(a => a.name === name);
  163. // what the next line means - if two animations have the same name, they will not be returned!
  164. if (filtered.length === 1) {
  165. return filtered[0];
  166. } else {
  167. return null;
  168. }
  169. }
  170. /**
  171. * Choose an initialized animation using its name and start playing it
  172. * @param name the name of the animation to play
  173. * @returns The model aniamtion to be played.
  174. */
  175. public playAnimation(name: string): IModelAnimation {
  176. let animation = this._getAnimationByName(name);
  177. if (animation) {
  178. if (this.currentAnimation) {
  179. this.currentAnimation.stop();
  180. }
  181. this.currentAnimation = animation;
  182. animation.start();
  183. return animation;
  184. } else {
  185. throw new Error("animation not found - " + name);
  186. }
  187. }
  188. private _configureModel() {
  189. let meshesWithNoParent: Array<AbstractMesh> = this.meshes.filter(m => !m.parent);
  190. let updateMeshesWithNoParent = (variable: string, value: any, param?: string) => {
  191. meshesWithNoParent.forEach(mesh => {
  192. if (param) {
  193. mesh[variable][param] = value;
  194. } else {
  195. mesh[variable] = value;
  196. }
  197. });
  198. }
  199. let updateXYZ = (variable: string, configValues: { x: number, y: number, z: number, w?: number }) => {
  200. if (configValues.x !== undefined) {
  201. updateMeshesWithNoParent(variable, configValues.x, 'x');
  202. }
  203. if (configValues.y !== undefined) {
  204. updateMeshesWithNoParent(variable, configValues.y, 'y');
  205. }
  206. if (configValues.z !== undefined) {
  207. updateMeshesWithNoParent(variable, configValues.z, 'z');
  208. }
  209. if (configValues.w !== undefined) {
  210. updateMeshesWithNoParent(variable, configValues.w, 'w');
  211. }
  212. }
  213. // position?
  214. if (this._modelConfiguration.position) {
  215. updateXYZ('position', this._modelConfiguration.position);
  216. }
  217. if (this._modelConfiguration.rotation) {
  218. //quaternion?
  219. if (this._modelConfiguration.rotation.w) {
  220. meshesWithNoParent.forEach(mesh => {
  221. if (!mesh.rotationQuaternion) {
  222. mesh.rotationQuaternion = new Quaternion();
  223. }
  224. })
  225. updateXYZ('rotationQuaternion', this._modelConfiguration.rotation);
  226. } else {
  227. updateXYZ('rotation', this._modelConfiguration.rotation);
  228. }
  229. }
  230. if (this._modelConfiguration.scaling) {
  231. updateXYZ('scaling', this._modelConfiguration.scaling);
  232. }
  233. if (this._modelConfiguration.castShadow) {
  234. this.meshes.forEach(mesh => {
  235. Tags.AddTagsTo(mesh, 'castShadow');
  236. });
  237. }
  238. if (this._modelConfiguration.normalize) {
  239. let center = false;
  240. let unitSize = false;
  241. let parentIndex;
  242. if (this._modelConfiguration.normalize === true) {
  243. center = true;
  244. unitSize = true;
  245. parentIndex = 0;
  246. } else {
  247. center = !!this._modelConfiguration.normalize.center;
  248. unitSize = !!this._modelConfiguration.normalize.unitSize;
  249. parentIndex = this._modelConfiguration.normalize.parentIndex;
  250. }
  251. let meshesToNormalize: Array<AbstractMesh> = [];
  252. if (parentIndex !== undefined) {
  253. meshesToNormalize.push(this.meshes[parentIndex]);
  254. } else {
  255. meshesToNormalize = meshesWithNoParent;
  256. }
  257. if (unitSize) {
  258. meshesToNormalize.forEach(mesh => {
  259. mesh.normalizeToUnitCube(true);
  260. mesh.computeWorldMatrix(true);
  261. });
  262. }
  263. if (center) {
  264. meshesToNormalize.forEach(mesh => {
  265. const boundingInfo = mesh.getHierarchyBoundingVectors(true);
  266. const sizeVec = boundingInfo.max.subtract(boundingInfo.min);
  267. const halfSizeVec = sizeVec.scale(0.5);
  268. const center = boundingInfo.min.add(halfSizeVec);
  269. mesh.position = center.scale(-1);
  270. // Set on ground.
  271. mesh.position.y += halfSizeVec.y;
  272. // Recompute Info.
  273. mesh.computeWorldMatrix(true);
  274. });
  275. }
  276. }
  277. this.onAfterConfigure.notifyObservers(this);
  278. }
  279. /**
  280. * Dispose this model, including all of its associated assets.
  281. */
  282. public dispose() {
  283. this.onAfterConfigure.clear();
  284. this.onLoadedObservable.clear();
  285. this.onLoadErrorObservable.clear();
  286. this.onLoadProgressObservable.clear();
  287. if (this.loader && this.loader.name === "gltf") {
  288. (<GLTFFileLoader>this.loader).dispose();
  289. }
  290. this.particleSystems.forEach(ps => ps.dispose());
  291. this.particleSystems.length = 0;
  292. this.skeletons.forEach(s => s.dispose());
  293. this.skeletons.length = 0;
  294. this._animations.forEach(ag => ag.dispose());
  295. this._animations.length = 0;
  296. this.meshes.forEach(m => m.dispose());
  297. this.meshes.length = 0;
  298. this._viewer.models.splice(this._viewer.models.indexOf(this), 1);
  299. }
  300. }