|
@@ -1,13 +1,24 @@
|
|
-import { ISceneLoaderPlugin, ISceneLoaderPluginAsync, AnimationGroup, Animatable, AbstractMesh, Tools, Scene, SceneLoader, Observable, SceneLoaderProgressEvent, Tags, ParticleSystem, Skeleton, IDisposable, Nullable, Animation } from "babylonjs";
|
|
|
|
|
|
+import { ISceneLoaderPlugin, ISceneLoaderPluginAsync, AnimationGroup, Animatable, AbstractMesh, Tools, Scene, SceneLoader, Observable, SceneLoaderProgressEvent, Tags, ParticleSystem, Skeleton, IDisposable, Nullable, Animation, GLTFFileLoader, Quaternion } from "babylonjs";
|
|
import { IModelConfiguration } from "../configuration/configuration";
|
|
import { IModelConfiguration } from "../configuration/configuration";
|
|
import { IModelAnimation, GroupModelAnimation, AnimationPlayMode } from "./modelAnimation";
|
|
import { IModelAnimation, GroupModelAnimation, AnimationPlayMode } from "./modelAnimation";
|
|
|
|
|
|
|
|
+import * as deepmerge from '../../assets/deepmerge.min.js';
|
|
|
|
+
|
|
|
|
+export enum ModelState {
|
|
|
|
+ INIT,
|
|
|
|
+ LOADING,
|
|
|
|
+ LOADED,
|
|
|
|
+ CANCELED,
|
|
|
|
+ ERROR
|
|
|
|
+}
|
|
|
|
+
|
|
export class ViewerModel implements IDisposable {
|
|
export class ViewerModel implements IDisposable {
|
|
|
|
|
|
public loader: ISceneLoaderPlugin | ISceneLoaderPluginAsync;
|
|
public loader: ISceneLoaderPlugin | ISceneLoaderPluginAsync;
|
|
|
|
|
|
private _animations: Array<IModelAnimation>;
|
|
private _animations: Array<IModelAnimation>;
|
|
public meshes: Array<AbstractMesh>;
|
|
public meshes: Array<AbstractMesh>;
|
|
|
|
+ public rootMesh: AbstractMesh;
|
|
public particleSystems: Array<ParticleSystem>;
|
|
public particleSystems: Array<ParticleSystem>;
|
|
public skeletons: Array<Skeleton>;
|
|
public skeletons: Array<Skeleton>;
|
|
public currentAnimation: IModelAnimation;
|
|
public currentAnimation: IModelAnimation;
|
|
@@ -16,10 +27,21 @@ export class ViewerModel implements IDisposable {
|
|
public onLoadProgressObservable: Observable<SceneLoaderProgressEvent>;
|
|
public onLoadProgressObservable: Observable<SceneLoaderProgressEvent>;
|
|
public onLoadErrorObservable: Observable<{ message: string; exception: any }>;
|
|
public onLoadErrorObservable: Observable<{ message: string; exception: any }>;
|
|
|
|
|
|
- constructor(private _scene: Scene, private _modelConfiguration?: IModelConfiguration, disableAutoLoad = false) {
|
|
|
|
|
|
+ public onAfterConfigure: Observable<ViewerModel>;
|
|
|
|
+
|
|
|
|
+ public state: ModelState;
|
|
|
|
+ public loadId: number;
|
|
|
|
+
|
|
|
|
+ private _loaderDisposed: boolean = false;
|
|
|
|
+ private _loadedUrl: string;
|
|
|
|
+
|
|
|
|
+ constructor(private _scene: Scene, private _modelConfiguration: IModelConfiguration, disableAutoLoad = false) {
|
|
this.onLoadedObservable = new Observable();
|
|
this.onLoadedObservable = new Observable();
|
|
this.onLoadErrorObservable = new Observable();
|
|
this.onLoadErrorObservable = new Observable();
|
|
this.onLoadProgressObservable = new Observable();
|
|
this.onLoadProgressObservable = new Observable();
|
|
|
|
+ this.onAfterConfigure = new Observable();
|
|
|
|
+
|
|
|
|
+ this.state = ModelState.INIT;
|
|
|
|
|
|
this._animations = [];
|
|
this._animations = [];
|
|
|
|
|
|
@@ -36,6 +58,29 @@ export class ViewerModel implements IDisposable {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ public cancelLoad() {
|
|
|
|
+ // ATM only available in the GLTF Loader
|
|
|
|
+ if (this.loader && this.loader.name === "gltf") {
|
|
|
|
+ let gltfLoader = (<GLTFFileLoader>this.loader);
|
|
|
|
+ gltfLoader.dispose();
|
|
|
|
+ this.state = ModelState.CANCELED;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public get configuration(): IModelConfiguration {
|
|
|
|
+ return this.configuration;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public set configuration(newConfiguration: IModelConfiguration) {
|
|
|
|
+ this._modelConfiguration = newConfiguration;
|
|
|
|
+ this._configureModel();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public updateConfiguration(newConfiguration: Partial<IModelConfiguration>) {
|
|
|
|
+ this._modelConfiguration = deepmerge(this._modelConfiguration, newConfiguration);
|
|
|
|
+ this._configureModel();
|
|
|
|
+ }
|
|
|
|
+
|
|
public getAnimations() {
|
|
public getAnimations() {
|
|
return this._animations;
|
|
return this._animations;
|
|
}
|
|
}
|
|
@@ -69,15 +114,115 @@ export class ViewerModel implements IDisposable {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ private _configureModel() {
|
|
|
|
+ let meshesWithNoParent: Array<AbstractMesh> = this.meshes.filter(m => !m.parent);
|
|
|
|
+ let updateMeshesWithNoParent = (variable: string, value: any, param?: string) => {
|
|
|
|
+ meshesWithNoParent.forEach(mesh => {
|
|
|
|
+ if (param) {
|
|
|
|
+ mesh[variable][param] = value;
|
|
|
|
+ } else {
|
|
|
|
+ mesh[variable] = value;
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+ let updateXYZ = (variable: string, configValues: { x: number, y: number, z: number, w?: number }) => {
|
|
|
|
+ if (configValues.x !== undefined) {
|
|
|
|
+ updateMeshesWithNoParent(variable, configValues.x, 'x');
|
|
|
|
+ }
|
|
|
|
+ if (configValues.y !== undefined) {
|
|
|
|
+ updateMeshesWithNoParent(variable, configValues.y, 'y');
|
|
|
|
+ }
|
|
|
|
+ if (configValues.z !== undefined) {
|
|
|
|
+ updateMeshesWithNoParent(variable, configValues.z, 'z');
|
|
|
|
+ }
|
|
|
|
+ if (configValues.w !== undefined) {
|
|
|
|
+ updateMeshesWithNoParent(variable, configValues.w, 'w');
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ // position?
|
|
|
|
+ if (this._modelConfiguration.position) {
|
|
|
|
+ updateXYZ('position', this._modelConfiguration.position);
|
|
|
|
+ }
|
|
|
|
+ if (this._modelConfiguration.rotation) {
|
|
|
|
+ //quaternion?
|
|
|
|
+ if (this._modelConfiguration.rotation.w) {
|
|
|
|
+ meshesWithNoParent.forEach(mesh => {
|
|
|
|
+ if (!mesh.rotationQuaternion) {
|
|
|
|
+ mesh.rotationQuaternion = new Quaternion();
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+ updateXYZ('rotationQuaternion', this._modelConfiguration.rotation);
|
|
|
|
+ } else {
|
|
|
|
+ updateXYZ('rotation', this._modelConfiguration.rotation);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ if (this._modelConfiguration.scaling) {
|
|
|
|
+ updateXYZ('scaling', this._modelConfiguration.scaling);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (this._modelConfiguration.castShadow) {
|
|
|
|
+ this.meshes.forEach(mesh => {
|
|
|
|
+ Tags.AddTagsTo(mesh, 'castShadow');
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (this._modelConfiguration.normalize) {
|
|
|
|
+ let center = false;
|
|
|
|
+ let unitSize = false;
|
|
|
|
+ let parentIndex;
|
|
|
|
+ if (this._modelConfiguration.normalize === true) {
|
|
|
|
+ center = true;
|
|
|
|
+ unitSize = true;
|
|
|
|
+ parentIndex = 0;
|
|
|
|
+ } else {
|
|
|
|
+ center = !!this._modelConfiguration.normalize.center;
|
|
|
|
+ unitSize = !!this._modelConfiguration.normalize.unitSize;
|
|
|
|
+ parentIndex = this._modelConfiguration.normalize.parentIndex;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ let meshesToNormalize: Array<AbstractMesh> = [];
|
|
|
|
+ if (parentIndex !== undefined) {
|
|
|
|
+ meshesToNormalize.push(this.meshes[parentIndex]);
|
|
|
|
+ } else {
|
|
|
|
+ meshesToNormalize = meshesWithNoParent;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (unitSize) {
|
|
|
|
+ meshesToNormalize.forEach(mesh => {
|
|
|
|
+ mesh.normalizeToUnitCube(true);
|
|
|
|
+ mesh.computeWorldMatrix(true);
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+ if (center) {
|
|
|
|
+ meshesToNormalize.forEach(mesh => {
|
|
|
|
+ const boundingInfo = mesh.getHierarchyBoundingVectors(true);
|
|
|
|
+ const sizeVec = boundingInfo.max.subtract(boundingInfo.min);
|
|
|
|
+ const halfSizeVec = sizeVec.scale(0.5);
|
|
|
|
+ const center = boundingInfo.min.add(halfSizeVec);
|
|
|
|
+ mesh.position = center.scale(-1);
|
|
|
|
+
|
|
|
|
+ // Set on ground.
|
|
|
|
+ mesh.position.y += halfSizeVec.y;
|
|
|
|
+
|
|
|
|
+ // Recompute Info.
|
|
|
|
+ mesh.computeWorldMatrix(true);
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ this.onAfterConfigure.notifyObservers(this);
|
|
|
|
+ }
|
|
|
|
+
|
|
private _initLoad() {
|
|
private _initLoad() {
|
|
- if (!this._modelConfiguration || !this._modelConfiguration.url) {
|
|
|
|
- return Tools.Error("No model URL to load.");
|
|
|
|
|
|
+ if (!this._modelConfiguration.url) {
|
|
|
|
+ this.state = ModelState.ERROR;
|
|
|
|
+ Tools.Error("No URL provided");
|
|
|
|
+ return;
|
|
}
|
|
}
|
|
- let parts = this._modelConfiguration.url.split('/');
|
|
|
|
- let filename = parts.pop() || this._modelConfiguration.url;
|
|
|
|
- let base = parts.length ? parts.join('/') + '/' : './';
|
|
|
|
|
|
|
|
|
|
+ let filename = Tools.GetFilename(this._modelConfiguration.url) || this._modelConfiguration.url;
|
|
|
|
+ let base = this._modelConfiguration.root || Tools.GetFolderPath(this._modelConfiguration.url);
|
|
let plugin = this._modelConfiguration.loader;
|
|
let plugin = this._modelConfiguration.loader;
|
|
|
|
+ this._loadedUrl = this._modelConfiguration.url;
|
|
|
|
|
|
//temp solution for animation group handling
|
|
//temp solution for animation group handling
|
|
let animationsArray = this._scene.animationGroups.slice();
|
|
let animationsArray = this._scene.animationGroups.slice();
|
|
@@ -124,18 +269,29 @@ export class ViewerModel implements IDisposable {
|
|
this.playAnimation(animationName);
|
|
this.playAnimation(animationName);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
-
|
|
|
|
this.onLoadedObservable.notifyObserversWithPromise(this);
|
|
this.onLoadedObservable.notifyObserversWithPromise(this);
|
|
}, (progressEvent) => {
|
|
}, (progressEvent) => {
|
|
this.onLoadProgressObservable.notifyObserversWithPromise(progressEvent);
|
|
this.onLoadProgressObservable.notifyObserversWithPromise(progressEvent);
|
|
}, (e, m, exception) => {
|
|
}, (e, m, exception) => {
|
|
|
|
+ this.state = ModelState.ERROR;
|
|
|
|
+ Tools.Error("Load Error: There was an error loading the model. " + m);
|
|
this.onLoadErrorObservable.notifyObserversWithPromise({ message: m, exception: exception });
|
|
this.onLoadErrorObservable.notifyObserversWithPromise({ message: m, exception: exception });
|
|
}, plugin)!;
|
|
}, plugin)!;
|
|
|
|
|
|
- this.loader['animationStartMode'] = 0;
|
|
|
|
|
|
+ if (this.loader.name === "gltf") {
|
|
|
|
+ let gltfLoader = (<GLTFFileLoader>this.loader);
|
|
|
|
+ gltfLoader.animationStartMode = 0;
|
|
|
|
+ gltfLoader.onDispose = () => {
|
|
|
|
+ this._loaderDisposed = true;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
}
|
|
}
|
|
|
|
|
|
public dispose() {
|
|
public dispose() {
|
|
|
|
+ if (this.loader && this.loader.name === "gltf") {
|
|
|
|
+ (<GLTFFileLoader>this.loader).dispose();
|
|
|
|
+ }
|
|
this.particleSystems.forEach(ps => ps.dispose());
|
|
this.particleSystems.forEach(ps => ps.dispose());
|
|
this.skeletons.forEach(s => s.dispose());
|
|
this.skeletons.forEach(s => s.dispose());
|
|
this._animations.forEach(ag => ag.dispose());
|
|
this._animations.forEach(ag => ag.dispose());
|