viewerModel.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  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. export enum ModelState {
  6. INIT,
  7. LOADING,
  8. LOADED,
  9. CANCELED,
  10. ERROR
  11. }
  12. export class ViewerModel implements IDisposable {
  13. public loader: ISceneLoaderPlugin | ISceneLoaderPluginAsync;
  14. private _animations: Array<IModelAnimation>;
  15. public meshes: Array<AbstractMesh> = [];
  16. public rootMesh: AbstractMesh;
  17. public particleSystems: Array<ParticleSystem> = [];
  18. public skeletons: Array<Skeleton> = [];
  19. public currentAnimation: IModelAnimation;
  20. public onLoadedObservable: Observable<ViewerModel>;
  21. public onLoadProgressObservable: Observable<SceneLoaderProgressEvent>;
  22. public onLoadErrorObservable: Observable<{ message: string; exception: any }>;
  23. public onAfterConfigure: Observable<ViewerModel>;
  24. public state: ModelState;
  25. public loadId: number;
  26. private _loaderDisposed: boolean = false;
  27. private _loadedUrl: string;
  28. constructor(private _scene: Scene, private _modelConfiguration: IModelConfiguration, disableAutoLoad = false) {
  29. this.onLoadedObservable = new Observable();
  30. this.onLoadErrorObservable = new Observable();
  31. this.onLoadProgressObservable = new Observable();
  32. this.onAfterConfigure = new Observable();
  33. this.state = ModelState.INIT;
  34. this._animations = [];
  35. if (!disableAutoLoad) {
  36. this._initLoad();
  37. }
  38. }
  39. public load() {
  40. if (this.loader) {
  41. Tools.Error("Model was already loaded or in the process of loading.");
  42. } else {
  43. this._initLoad();
  44. }
  45. }
  46. public cancelLoad() {
  47. // ATM only available in the GLTF Loader
  48. if (this.loader && this.loader.name === "gltf") {
  49. let gltfLoader = (<GLTFFileLoader>this.loader);
  50. gltfLoader.dispose();
  51. this.state = ModelState.CANCELED;
  52. }
  53. }
  54. public get configuration(): IModelConfiguration {
  55. return this._modelConfiguration;
  56. }
  57. public set configuration(newConfiguration: IModelConfiguration) {
  58. this._modelConfiguration = newConfiguration;
  59. this._configureModel();
  60. }
  61. public updateConfiguration(newConfiguration: Partial<IModelConfiguration>) {
  62. this._modelConfiguration = deepmerge(this._modelConfiguration, newConfiguration);
  63. this._configureModel();
  64. }
  65. public initAnimations() {
  66. this._animations.forEach(a => {
  67. a.dispose();
  68. });
  69. this._animations.length = 0;
  70. // check if this is not a gltf loader and init the animations
  71. if (this.loader.name !== 'gltf') {
  72. this.skeletons.forEach((skeleton, idx) => {
  73. let ag = new AnimationGroup("animation-" + idx, this._scene);
  74. skeleton.getAnimatables().forEach(a => {
  75. if (a.animations[0]) {
  76. ag.addTargetedAnimation(a.animations[0], a);
  77. }
  78. });
  79. this.addAnimationGroup(ag);
  80. });
  81. }
  82. if (!this._modelConfiguration) return;
  83. if (this._modelConfiguration.animation) {
  84. if (this._modelConfiguration.animation.playOnce) {
  85. this._animations.forEach(a => {
  86. a.playMode = AnimationPlayMode.ONCE;
  87. });
  88. }
  89. if (this._modelConfiguration.animation.autoStart && this._animations.length) {
  90. let animationName = this._modelConfiguration.animation.autoStart === true ?
  91. this._animations[0].name : this._modelConfiguration.animation.autoStart;
  92. this.playAnimation(animationName);
  93. }
  94. }
  95. }
  96. public addAnimationGroup(animationGroup: AnimationGroup) {
  97. this._animations.push(new GroupModelAnimation(animationGroup));
  98. }
  99. public getAnimations() {
  100. return this._animations;
  101. }
  102. public getAnimationNames() {
  103. return this._animations.map(a => a.name);
  104. }
  105. protected _getAnimationByName(name: string): Nullable<IModelAnimation> {
  106. // can't use .find, noe available on IE
  107. let filtered = this._animations.filter(a => a.name === name);
  108. // what the next line means - if two animations have the same name, they will not be returned!
  109. if (filtered.length === 1) {
  110. return filtered[0];
  111. } else {
  112. return null;
  113. }
  114. }
  115. public playAnimation(name: string): IModelAnimation {
  116. let animation = this._getAnimationByName(name);
  117. if (animation) {
  118. if (this.currentAnimation) {
  119. this.currentAnimation.stop();
  120. }
  121. this.currentAnimation = animation;
  122. animation.start();
  123. return animation;
  124. } else {
  125. throw new Error("animation not found - " + name);
  126. }
  127. }
  128. private _configureModel() {
  129. let meshesWithNoParent: Array<AbstractMesh> = this.meshes.filter(m => !m.parent);
  130. let updateMeshesWithNoParent = (variable: string, value: any, param?: string) => {
  131. meshesWithNoParent.forEach(mesh => {
  132. if (param) {
  133. mesh[variable][param] = value;
  134. } else {
  135. mesh[variable] = value;
  136. }
  137. });
  138. }
  139. let updateXYZ = (variable: string, configValues: { x: number, y: number, z: number, w?: number }) => {
  140. if (configValues.x !== undefined) {
  141. updateMeshesWithNoParent(variable, configValues.x, 'x');
  142. }
  143. if (configValues.y !== undefined) {
  144. updateMeshesWithNoParent(variable, configValues.y, 'y');
  145. }
  146. if (configValues.z !== undefined) {
  147. updateMeshesWithNoParent(variable, configValues.z, 'z');
  148. }
  149. if (configValues.w !== undefined) {
  150. updateMeshesWithNoParent(variable, configValues.w, 'w');
  151. }
  152. }
  153. // position?
  154. if (this._modelConfiguration.position) {
  155. updateXYZ('position', this._modelConfiguration.position);
  156. }
  157. if (this._modelConfiguration.rotation) {
  158. //quaternion?
  159. if (this._modelConfiguration.rotation.w) {
  160. meshesWithNoParent.forEach(mesh => {
  161. if (!mesh.rotationQuaternion) {
  162. mesh.rotationQuaternion = new Quaternion();
  163. }
  164. })
  165. updateXYZ('rotationQuaternion', this._modelConfiguration.rotation);
  166. } else {
  167. updateXYZ('rotation', this._modelConfiguration.rotation);
  168. }
  169. }
  170. if (this._modelConfiguration.scaling) {
  171. updateXYZ('scaling', this._modelConfiguration.scaling);
  172. }
  173. if (this._modelConfiguration.castShadow) {
  174. this.meshes.forEach(mesh => {
  175. Tags.AddTagsTo(mesh, 'castShadow');
  176. });
  177. }
  178. if (this._modelConfiguration.normalize) {
  179. let center = false;
  180. let unitSize = false;
  181. let parentIndex;
  182. if (this._modelConfiguration.normalize === true) {
  183. center = true;
  184. unitSize = true;
  185. parentIndex = 0;
  186. } else {
  187. center = !!this._modelConfiguration.normalize.center;
  188. unitSize = !!this._modelConfiguration.normalize.unitSize;
  189. parentIndex = this._modelConfiguration.normalize.parentIndex;
  190. }
  191. let meshesToNormalize: Array<AbstractMesh> = [];
  192. if (parentIndex !== undefined) {
  193. meshesToNormalize.push(this.meshes[parentIndex]);
  194. } else {
  195. meshesToNormalize = meshesWithNoParent;
  196. }
  197. if (unitSize) {
  198. meshesToNormalize.forEach(mesh => {
  199. mesh.normalizeToUnitCube(true);
  200. mesh.computeWorldMatrix(true);
  201. });
  202. }
  203. if (center) {
  204. meshesToNormalize.forEach(mesh => {
  205. const boundingInfo = mesh.getHierarchyBoundingVectors(true);
  206. const sizeVec = boundingInfo.max.subtract(boundingInfo.min);
  207. const halfSizeVec = sizeVec.scale(0.5);
  208. const center = boundingInfo.min.add(halfSizeVec);
  209. mesh.position = center.scale(-1);
  210. // Set on ground.
  211. mesh.position.y += halfSizeVec.y;
  212. // Recompute Info.
  213. mesh.computeWorldMatrix(true);
  214. });
  215. }
  216. }
  217. this.onAfterConfigure.notifyObservers(this);
  218. }
  219. private _initLoad() {
  220. if (!this._modelConfiguration.url) {
  221. this.state = ModelState.ERROR;
  222. Tools.Error("No URL provided");
  223. return;
  224. }
  225. let filename = Tools.GetFilename(this._modelConfiguration.url) || this._modelConfiguration.url;
  226. let base = this._modelConfiguration.root || Tools.GetFolderPath(this._modelConfiguration.url);
  227. let plugin = this._modelConfiguration.loader;
  228. this._loadedUrl = this._modelConfiguration.url;
  229. this.loader = SceneLoader.ImportMesh(undefined, base, filename, this._scene, (meshes, particleSystems, skeletons) => {
  230. meshes.forEach(mesh => {
  231. Tags.AddTagsTo(mesh, "viewerMesh");
  232. });
  233. this.meshes = meshes;
  234. this.particleSystems = particleSystems;
  235. this.skeletons = skeletons;
  236. this.initAnimations();
  237. this.onLoadedObservable.notifyObserversWithPromise(this);
  238. }, (progressEvent) => {
  239. this.onLoadProgressObservable.notifyObserversWithPromise(progressEvent);
  240. }, (e, m, exception) => {
  241. this.state = ModelState.ERROR;
  242. Tools.Error("Load Error: There was an error loading the model. " + m);
  243. this.onLoadErrorObservable.notifyObserversWithPromise({ message: m, exception: exception });
  244. }, plugin)!;
  245. if (this.loader.name === "gltf") {
  246. let gltfLoader = (<GLTFFileLoader>this.loader);
  247. gltfLoader.animationStartMode = 0;
  248. gltfLoader.onDispose = () => {
  249. this._loaderDisposed = true;
  250. }
  251. gltfLoader.onAnimationGroupLoaded = ag => {
  252. this.addAnimationGroup(ag);
  253. }
  254. }
  255. }
  256. public dispose() {
  257. this.onAfterConfigure.clear();
  258. this.onLoadedObservable.clear();
  259. this.onLoadErrorObservable.clear();
  260. this.onLoadProgressObservable.clear();
  261. if (this.loader && this.loader.name === "gltf") {
  262. (<GLTFFileLoader>this.loader).dispose();
  263. }
  264. this.particleSystems.forEach(ps => ps.dispose());
  265. this.particleSystems.length = 0;
  266. this.skeletons.forEach(s => s.dispose());
  267. this.skeletons.length = 0;
  268. this._animations.forEach(ag => ag.dispose());
  269. this._animations.length = 0;
  270. this.meshes.forEach(m => m.dispose());
  271. this.meshes.length = 0;
  272. }
  273. }