|
@@ -1,8 +1,11 @@
|
|
|
import { viewerManager } from './viewerManager';
|
|
|
import { TemplateManager } from './../templateManager';
|
|
|
import configurationLoader from './../configuration/loader';
|
|
|
-import { CubeTexture, Color3, IEnvironmentHelperOptions, EnvironmentHelper, Effect, SceneOptimizer, SceneOptimizerOptions, Observable, Engine, Scene, ArcRotateCamera, Vector3, SceneLoader, AbstractMesh, Mesh, HemisphericLight, Database, SceneLoaderProgressEvent, ISceneLoaderPlugin, ISceneLoaderPluginAsync } from 'babylonjs';
|
|
|
-import { ViewerConfiguration } from '../configuration/configuration';
|
|
|
+import { CubeTexture, Color3, IEnvironmentHelperOptions, EnvironmentHelper, Effect, SceneOptimizer, SceneOptimizerOptions, Observable, Engine, Scene, ArcRotateCamera, Vector3, SceneLoader, AbstractMesh, Mesh, HemisphericLight, Database, SceneLoaderProgressEvent, ISceneLoaderPlugin, ISceneLoaderPluginAsync, Quaternion, Light, ShadowLight, ShadowGenerator, Tags, AutoRotationBehavior, BouncingBehavior, FramingBehavior, Behavior } from 'babylonjs';
|
|
|
+import { ViewerConfiguration, ISceneConfiguration, ISceneOptimizerConfiguration, IObserversConfiguration, IModelConfiguration, ISkyboxConfiguration, IGroundConfiguration, ILightConfiguration, ICameraConfiguration } from '../configuration/configuration';
|
|
|
+
|
|
|
+import * as deepmerge from '../../assets/deepmerge.min.js';
|
|
|
+import { CameraBehavior } from 'src/interfaces';
|
|
|
|
|
|
export abstract class AbstractViewer {
|
|
|
|
|
@@ -10,6 +13,7 @@ export abstract class AbstractViewer {
|
|
|
|
|
|
public engine: Engine;
|
|
|
public scene: Scene;
|
|
|
+ public camera: ArcRotateCamera;
|
|
|
public sceneOptimizer: SceneOptimizer;
|
|
|
public baseId: string;
|
|
|
|
|
@@ -35,6 +39,7 @@ export abstract class AbstractViewer {
|
|
|
public onEngineInitObservable: Observable<Engine>;
|
|
|
public onModelLoadedObservable: Observable<AbstractMesh[]>;
|
|
|
public onModelLoadProgressObservable: Observable<SceneLoaderProgressEvent>;
|
|
|
+ public onModelLoadErrorObservable: Observable<{ message: string; exception: any }>;
|
|
|
public onLoaderInitObservable: Observable<ISceneLoaderPlugin | ISceneLoaderPluginAsync>;
|
|
|
public onInitDoneObservable: Observable<AbstractViewer>;
|
|
|
|
|
@@ -54,6 +59,7 @@ export abstract class AbstractViewer {
|
|
|
this.onEngineInitObservable = new Observable();
|
|
|
this.onModelLoadedObservable = new Observable();
|
|
|
this.onModelLoadProgressObservable = new Observable();
|
|
|
+ this.onModelLoadErrorObservable = new Observable();
|
|
|
this.onInitDoneObservable = new Observable();
|
|
|
this.onLoaderInitObservable = new Observable();
|
|
|
|
|
@@ -70,19 +76,10 @@ export abstract class AbstractViewer {
|
|
|
// extend the configuration
|
|
|
configurationLoader.loadConfiguration(initialConfiguration).then((configuration) => {
|
|
|
this.configuration = configuration;
|
|
|
-
|
|
|
- // adding preconfigured functions
|
|
|
if (this.configuration.observers) {
|
|
|
- if (this.configuration.observers.onEngineInit) {
|
|
|
- this.onEngineInitObservable.add(window[this.configuration.observers.onEngineInit]);
|
|
|
- }
|
|
|
- if (this.configuration.observers.onSceneInit) {
|
|
|
- this.onSceneInitObservable.add(window[this.configuration.observers.onSceneInit]);
|
|
|
- }
|
|
|
- if (this.configuration.observers.onModelLoaded) {
|
|
|
- this.onModelLoadedObservable.add(window[this.configuration.observers.onModelLoaded]);
|
|
|
- }
|
|
|
+ this.configureObservers(this.configuration.observers);
|
|
|
}
|
|
|
+ //this.updateConfiguration(configuration);
|
|
|
|
|
|
// initialize the templates
|
|
|
let templateConfiguration = this.configuration.templates || {};
|
|
@@ -123,7 +120,415 @@ export abstract class AbstractViewer {
|
|
|
}
|
|
|
|
|
|
protected render = (): void => {
|
|
|
- this.scene && this.scene.render();
|
|
|
+ this.scene && this.scene.activeCamera && this.scene.render();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Update the current viewer configuration with new values.
|
|
|
+ * Only provided information will be updated, old configuration values will be kept.
|
|
|
+ * If this.configuration was manually changed, you can trigger this function with no parameters,
|
|
|
+ * and the entire configuration will be updated.
|
|
|
+ * @param newConfiguration
|
|
|
+ */
|
|
|
+ public updateConfiguration(newConfiguration: Partial<ViewerConfiguration> = this.configuration) {
|
|
|
+ // update scene configuration
|
|
|
+ if (newConfiguration.scene) {
|
|
|
+ this.configureScene(newConfiguration.scene);
|
|
|
+ }
|
|
|
+ // optimizer
|
|
|
+ if (newConfiguration.optimizer) {
|
|
|
+ this.configureOptimizer(newConfiguration.optimizer);
|
|
|
+ }
|
|
|
+
|
|
|
+ // observers in configuration
|
|
|
+ if (newConfiguration.observers) {
|
|
|
+ this.configureObservers(newConfiguration.observers);
|
|
|
+ }
|
|
|
+
|
|
|
+ // configure model
|
|
|
+ if (newConfiguration.model && typeof newConfiguration.model === 'object') {
|
|
|
+ this.configureModel(newConfiguration.model);
|
|
|
+ }
|
|
|
+
|
|
|
+ // lights
|
|
|
+ if (newConfiguration.lights) {
|
|
|
+ this.configureLights(newConfiguration.lights);
|
|
|
+ }
|
|
|
+
|
|
|
+ // environment
|
|
|
+ if (newConfiguration.skybox !== undefined || newConfiguration.ground !== undefined) {
|
|
|
+ this.configureEnvironment(newConfiguration.skybox, newConfiguration.ground);
|
|
|
+ }
|
|
|
+
|
|
|
+ // update this.configuration with the new data
|
|
|
+ this.configuration = deepmerge(this.configuration || {}, newConfiguration);
|
|
|
+ }
|
|
|
+
|
|
|
+ protected configureEnvironment(skyboxConifguration?: ISkyboxConfiguration | boolean, groundConfiguration?: IGroundConfiguration | boolean) {
|
|
|
+ if (!skyboxConifguration && !groundConfiguration) {
|
|
|
+ if (this.environmentHelper) {
|
|
|
+ this.environmentHelper.dispose();
|
|
|
+ };
|
|
|
+ return Promise.resolve(this.scene);
|
|
|
+ }
|
|
|
+
|
|
|
+ const options: Partial<IEnvironmentHelperOptions> = {
|
|
|
+ createGround: !!groundConfiguration,
|
|
|
+ createSkybox: !!skyboxConifguration,
|
|
|
+ setupImageProcessing: false // will be done at the scene level!
|
|
|
+ };
|
|
|
+
|
|
|
+ if (groundConfiguration) {
|
|
|
+ let groundConfig = (typeof groundConfiguration === 'boolean') ? {} : groundConfiguration;
|
|
|
+
|
|
|
+ let groundSize = groundConfig.size || (typeof skyboxConifguration === 'object' && skyboxConifguration.scale);
|
|
|
+ if (groundSize) {
|
|
|
+ options.groundSize = groundSize;
|
|
|
+ }
|
|
|
+
|
|
|
+ options.enableGroundShadow = groundConfig === true || groundConfig.receiveShadows;
|
|
|
+ if (groundConfig.shadowLevel) {
|
|
|
+ options.groundShadowLevel = groundConfig.shadowLevel;
|
|
|
+ }
|
|
|
+ options.enableGroundMirror = !!groundConfig.mirror;
|
|
|
+ if (groundConfig.texture) {
|
|
|
+ options.groundTexture = groundConfig.texture;
|
|
|
+ }
|
|
|
+ if (groundConfig.color) {
|
|
|
+ options.groundColor = new Color3(groundConfig.color.r, groundConfig.color.g, groundConfig.color.b)
|
|
|
+ }
|
|
|
+
|
|
|
+ if (groundConfig.mirror) {
|
|
|
+ options.enableGroundMirror = true;
|
|
|
+ // to prevent undefines
|
|
|
+ if (typeof groundConfig.mirror === "object") {
|
|
|
+ if (groundConfig.mirror.amount)
|
|
|
+ options.groundMirrorAmount = groundConfig.mirror.amount;
|
|
|
+ if (groundConfig.mirror.sizeRatio)
|
|
|
+ options.groundMirrorSizeRatio = groundConfig.mirror.sizeRatio;
|
|
|
+ if (groundConfig.mirror.blurKernel)
|
|
|
+ options.groundMirrorBlurKernel = groundConfig.mirror.blurKernel;
|
|
|
+ if (groundConfig.mirror.fresnelWeight)
|
|
|
+ options.groundMirrorFresnelWeight = groundConfig.mirror.fresnelWeight;
|
|
|
+ if (groundConfig.mirror.fallOffDistance)
|
|
|
+ options.groundMirrorFallOffDistance = groundConfig.mirror.fallOffDistance;
|
|
|
+ if (this.defaultHighpTextureType !== undefined)
|
|
|
+ options.groundMirrorTextureType = this.defaultHighpTextureType;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ let postInitSkyboxMaterial = false;
|
|
|
+ if (skyboxConifguration) {
|
|
|
+ let conf = skyboxConifguration === true ? {} : skyboxConifguration;
|
|
|
+ if (conf.material && conf.material.imageProcessingConfiguration) {
|
|
|
+ options.setupImageProcessing = false; // will be configured later manually.
|
|
|
+ }
|
|
|
+ let skyboxSize = conf.scale;
|
|
|
+ if (skyboxSize) {
|
|
|
+ options.skyboxSize = skyboxSize;
|
|
|
+ }
|
|
|
+ options.sizeAuto = !options.skyboxSize;
|
|
|
+ if (conf.color) {
|
|
|
+ options.skyboxColor = new Color3(conf.color.r, conf.color.g, conf.color.b)
|
|
|
+ }
|
|
|
+ if (conf.cubeTexture && conf.cubeTexture.url) {
|
|
|
+ if (typeof conf.cubeTexture.url === "string") {
|
|
|
+ options.skyboxTexture = conf.cubeTexture.url;
|
|
|
+ } else {
|
|
|
+ // init later!
|
|
|
+ postInitSkyboxMaterial = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (conf.material && conf.material.imageProcessingConfiguration) {
|
|
|
+ postInitSkyboxMaterial = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!this.environmentHelper) {
|
|
|
+ this.environmentHelper = this.scene.createDefaultEnvironment(options)!;
|
|
|
+ } else {
|
|
|
+ // there might be a new scene! we need to dispose.
|
|
|
+
|
|
|
+ // get the scene used by the envHelper
|
|
|
+ let scene: Scene = this.environmentHelper.rootMesh.getScene();
|
|
|
+ // is it a different scene? Oh no!
|
|
|
+ if (scene !== this.scene) {
|
|
|
+ this.environmentHelper.dispose();
|
|
|
+ this.environmentHelper = this.scene.createDefaultEnvironment(options)!;
|
|
|
+ } else {
|
|
|
+ this.environmentHelper.updateOptions(options)!;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (postInitSkyboxMaterial) {
|
|
|
+ let skyboxMaterial = this.environmentHelper.skyboxMaterial;
|
|
|
+ if (skyboxMaterial) {
|
|
|
+ if (typeof skyboxConifguration === 'object' && skyboxConifguration.material && skyboxConifguration.material.imageProcessingConfiguration) {
|
|
|
+ this.extendClassWithConfig(skyboxMaterial.imageProcessingConfiguration, skyboxConifguration.material.imageProcessingConfiguration);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ protected configureScene(sceneConfig: ISceneConfiguration, optimizerConfig?: ISceneOptimizerConfiguration) {
|
|
|
+ // sanity check!
|
|
|
+ if (!this.scene) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (sceneConfig.debug) {
|
|
|
+ this.scene.debugLayer.show();
|
|
|
+ } else {
|
|
|
+ if (this.scene.debugLayer.isVisible()) {
|
|
|
+ this.scene.debugLayer.hide();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (sceneConfig.clearColor) {
|
|
|
+ let cc = sceneConfig.clearColor;
|
|
|
+ let oldcc = this.scene.clearColor;
|
|
|
+ if (cc.r !== undefined) {
|
|
|
+ oldcc.r = cc.r;
|
|
|
+ }
|
|
|
+ if (cc.g !== undefined) {
|
|
|
+ oldcc.g = cc.g
|
|
|
+ }
|
|
|
+ if (cc.b !== undefined) {
|
|
|
+ oldcc.b = cc.b
|
|
|
+ }
|
|
|
+ if (cc.a !== undefined) {
|
|
|
+ oldcc.a = cc.a
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // image processing configuration - optional.
|
|
|
+ if (sceneConfig.imageProcessingConfiguration) {
|
|
|
+ this.extendClassWithConfig(this.scene.imageProcessingConfiguration, sceneConfig.imageProcessingConfiguration);
|
|
|
+ }
|
|
|
+ if (sceneConfig.environmentTexture) {
|
|
|
+ if (this.scene.environmentTexture) {
|
|
|
+ this.scene.environmentTexture.dispose();
|
|
|
+ }
|
|
|
+ const environmentTexture = CubeTexture.CreateFromPrefilteredData(sceneConfig.environmentTexture, this.scene);
|
|
|
+ this.scene.environmentTexture = environmentTexture;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (sceneConfig.autoRotate) {
|
|
|
+ this.camera.useAutoRotationBehavior = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ protected configureOptimizer(optimizerConfig: ISceneOptimizerConfiguration | boolean) {
|
|
|
+ if (typeof optimizerConfig === 'boolean') {
|
|
|
+ if (this.sceneOptimizer) {
|
|
|
+ this.sceneOptimizer.stop();
|
|
|
+ this.sceneOptimizer.dispose();
|
|
|
+ delete this.sceneOptimizer;
|
|
|
+ }
|
|
|
+ if (optimizerConfig) {
|
|
|
+ this.sceneOptimizer = new SceneOptimizer(this.scene);
|
|
|
+ this.sceneOptimizer.start();
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ let optimizerOptions: SceneOptimizerOptions = new SceneOptimizerOptions(optimizerConfig.targetFrameRate, optimizerConfig.trackerDuration);
|
|
|
+ // check for degradation
|
|
|
+ if (optimizerConfig.degradation) {
|
|
|
+ switch (optimizerConfig.degradation) {
|
|
|
+ case "low":
|
|
|
+ optimizerOptions = SceneOptimizerOptions.LowDegradationAllowed(optimizerConfig.targetFrameRate);
|
|
|
+ break;
|
|
|
+ case "moderate":
|
|
|
+ optimizerOptions = SceneOptimizerOptions.ModerateDegradationAllowed(optimizerConfig.targetFrameRate);
|
|
|
+ break;
|
|
|
+ case "hight":
|
|
|
+ optimizerOptions = SceneOptimizerOptions.HighDegradationAllowed(optimizerConfig.targetFrameRate);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (this.sceneOptimizer) {
|
|
|
+ this.sceneOptimizer.stop();
|
|
|
+ this.sceneOptimizer.dispose()
|
|
|
+ }
|
|
|
+ this.sceneOptimizer = new SceneOptimizer(this.scene, optimizerOptions, optimizerConfig.autoGeneratePriorities, optimizerConfig.improvementMode);
|
|
|
+ this.sceneOptimizer.start();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ protected configureObservers(observersConfiguration: IObserversConfiguration) {
|
|
|
+ if (observersConfiguration.onEngineInit) {
|
|
|
+ this.onEngineInitObservable.add(window[observersConfiguration.onEngineInit]);
|
|
|
+ } else {
|
|
|
+ if (observersConfiguration.onEngineInit === '' && this.configuration.observers && this.configuration.observers!.onEngineInit) {
|
|
|
+ this.onEngineInitObservable.removeCallback(window[this.configuration.observers!.onEngineInit!]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (observersConfiguration.onSceneInit) {
|
|
|
+ this.onSceneInitObservable.add(window[observersConfiguration.onSceneInit]);
|
|
|
+ } else {
|
|
|
+ if (observersConfiguration.onSceneInit === '' && this.configuration.observers && this.configuration.observers!.onSceneInit) {
|
|
|
+ this.onSceneInitObservable.removeCallback(window[this.configuration.observers!.onSceneInit!]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (observersConfiguration.onModelLoaded) {
|
|
|
+ this.onModelLoadedObservable.add(window[observersConfiguration.onModelLoaded]);
|
|
|
+ } else {
|
|
|
+ if (observersConfiguration.onModelLoaded === '' && this.configuration.observers && this.configuration.observers!.onModelLoaded) {
|
|
|
+ this.onModelLoadedObservable.removeCallback(window[this.configuration.observers!.onModelLoaded!]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ protected configureCamera(cameraConfig: ICameraConfiguration, focusMeshes: Array<AbstractMesh> = this.scene.meshes) {
|
|
|
+ if (!this.scene.activeCamera) {
|
|
|
+ this.scene.createDefaultCamera(true, true, true);
|
|
|
+ this.camera = <ArcRotateCamera>this.scene.activeCamera!;
|
|
|
+ }
|
|
|
+ if (cameraConfig.position) {
|
|
|
+ this.camera.position.copyFromFloats(cameraConfig.position.x || 0, cameraConfig.position.y || 0, cameraConfig.position.z || 0);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (cameraConfig.rotation) {
|
|
|
+ this.camera.rotationQuaternion = new Quaternion(cameraConfig.rotation.x || 0, cameraConfig.rotation.y || 0, cameraConfig.rotation.z || 0, cameraConfig.rotation.w || 0)
|
|
|
+ }
|
|
|
+
|
|
|
+ this.camera.minZ = cameraConfig.minZ || this.camera.minZ;
|
|
|
+ this.camera.maxZ = cameraConfig.maxZ || this.camera.maxZ;
|
|
|
+
|
|
|
+ if (cameraConfig.behaviors) {
|
|
|
+ for (let name in cameraConfig.behaviors) {
|
|
|
+ this.setCameraBehavior(cameraConfig.behaviors[name], focusMeshes);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const sceneExtends = this.scene.getWorldExtends();
|
|
|
+ const sceneDiagonal = sceneExtends.max.subtract(sceneExtends.min);
|
|
|
+ const sceneDiagonalLenght = sceneDiagonal.length();
|
|
|
+ this.camera.upperRadiusLimit = sceneDiagonalLenght * 3;
|
|
|
+ }
|
|
|
+
|
|
|
+ protected configureLights(lightsConfiguration: { [name: string]: ILightConfiguration | boolean } = {}, focusMeshes: Array<AbstractMesh> = this.scene.meshes) {
|
|
|
+ // sanity check!
|
|
|
+ if (!Object.keys(lightsConfiguration).length) return;
|
|
|
+
|
|
|
+ let lightsAvailable: Array<string> = this.scene.lights.map(light => light.name);
|
|
|
+
|
|
|
+ Object.keys(lightsConfiguration).forEach((name, idx) => {
|
|
|
+ let lightConfig: ILightConfiguration = { type: 0 };
|
|
|
+ if (typeof lightsConfiguration[name] === 'object') {
|
|
|
+ lightConfig = <ILightConfiguration>lightsConfiguration[name];
|
|
|
+ }
|
|
|
+
|
|
|
+ lightConfig.name = name;
|
|
|
+
|
|
|
+ let light;
|
|
|
+ // light is not already available
|
|
|
+ if (lightsAvailable.indexOf(name) === -1) {
|
|
|
+ let constructor = Light.GetConstructorFromName(lightConfig.type, lightConfig.name, this.scene);
|
|
|
+ if (!constructor) return;
|
|
|
+ light = constructor();
|
|
|
+ } else {
|
|
|
+ // available? get it from the scene
|
|
|
+ light = this.scene.getLightByName(name);
|
|
|
+ lightsAvailable = lightsAvailable.filter(ln => ln !== name);
|
|
|
+ }
|
|
|
+
|
|
|
+ // if config set the light to false, dispose it.
|
|
|
+ if (lightsConfiguration[name] === false) {
|
|
|
+ light.dispose();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ //enabled
|
|
|
+ if (light.isEnabled() !== !lightConfig.disabled) {
|
|
|
+ light.setEnabled(!lightConfig.disabled);
|
|
|
+ }
|
|
|
+
|
|
|
+ this.extendClassWithConfig(light, lightConfig);
|
|
|
+
|
|
|
+ //position. Some lights don't support shadows
|
|
|
+ if (light instanceof ShadowLight) {
|
|
|
+ let shadowGenerator = light.getShadowGenerator();
|
|
|
+ if (lightConfig.shadowEnabled && this.maxShadows) {
|
|
|
+ if (!shadowGenerator) {
|
|
|
+ shadowGenerator = new ShadowGenerator(512, light);
|
|
|
+ }
|
|
|
+ this.extendClassWithConfig(shadowGenerator, lightConfig.shadowConfig || {});
|
|
|
+ // add the focues meshes to the shadow list
|
|
|
+ let shadownMap = shadowGenerator.getShadowMap();
|
|
|
+ if (!shadownMap) return;
|
|
|
+ let renderList = shadownMap.renderList;
|
|
|
+ for (var index = 0; index < focusMeshes.length; index++) {
|
|
|
+ if (Tags.MatchesQuery(focusMeshes[index], 'castShadow')) {
|
|
|
+ // renderList && renderList.push(focusMeshes[index]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else if (shadowGenerator) {
|
|
|
+ shadowGenerator.dispose();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // remove the unneeded lights
|
|
|
+ /*lightsAvailable.forEach(name => {
|
|
|
+ let light = this.scene.getLightByName(name);
|
|
|
+ if (light) {
|
|
|
+ light.dispose();
|
|
|
+ }
|
|
|
+ });*/
|
|
|
+ }
|
|
|
+
|
|
|
+ protected configureModel(modelConfiguration: Partial<IModelConfiguration>, focusMeshes: Array<AbstractMesh> = this.scene.meshes) {
|
|
|
+ let meshesWithNoParent: Array<AbstractMesh> = focusMeshes.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 (modelConfiguration.position) {
|
|
|
+ updateXYZ('position', modelConfiguration.position);
|
|
|
+ }
|
|
|
+ if (modelConfiguration.rotation) {
|
|
|
+ if (modelConfiguration.rotation.w) {
|
|
|
+ meshesWithNoParent.forEach(mesh => {
|
|
|
+ if (!mesh.rotationQuaternion) {
|
|
|
+ mesh.rotationQuaternion = new Quaternion();
|
|
|
+ }
|
|
|
+ })
|
|
|
+ updateXYZ('rotationQuaternion', modelConfiguration.rotation);
|
|
|
+ } else {
|
|
|
+ updateXYZ('rotation', modelConfiguration.rotation);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (modelConfiguration.scaling) {
|
|
|
+ updateXYZ('scaling', modelConfiguration.scaling);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (modelConfiguration.castShadow) {
|
|
|
+ focusMeshes.forEach(mesh => {
|
|
|
+ Tags.AddTagsTo(mesh, 'castShadow');
|
|
|
+ });
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
public dispose() {
|
|
@@ -233,48 +638,17 @@ export abstract class AbstractViewer {
|
|
|
// create a new scene
|
|
|
this.scene = new Scene(this.engine);
|
|
|
// make sure there is a default camera and light.
|
|
|
- this.scene.createDefaultCameraOrLight(true, true, true);
|
|
|
+ this.scene.createDefaultLight(true);
|
|
|
+
|
|
|
if (this.configuration.scene) {
|
|
|
- if (this.configuration.scene.debug) {
|
|
|
- this.scene.debugLayer.show();
|
|
|
- }
|
|
|
+ this.configureScene(this.configuration.scene);
|
|
|
|
|
|
// Scene optimizer
|
|
|
if (this.configuration.optimizer) {
|
|
|
-
|
|
|
- let optimizerConfig = this.configuration.optimizer;
|
|
|
- let optimizerOptions: SceneOptimizerOptions = new SceneOptimizerOptions(optimizerConfig.targetFrameRate, optimizerConfig.trackerDuration);
|
|
|
- // check for degradation
|
|
|
- if (optimizerConfig.degradation) {
|
|
|
- switch (optimizerConfig.degradation) {
|
|
|
- case "low":
|
|
|
- optimizerOptions = SceneOptimizerOptions.LowDegradationAllowed(optimizerConfig.targetFrameRate);
|
|
|
- break;
|
|
|
- case "moderate":
|
|
|
- optimizerOptions = SceneOptimizerOptions.ModerateDegradationAllowed(optimizerConfig.targetFrameRate);
|
|
|
- break;
|
|
|
- case "hight":
|
|
|
- optimizerOptions = SceneOptimizerOptions.HighDegradationAllowed(optimizerConfig.targetFrameRate);
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- this.sceneOptimizer = new SceneOptimizer(this.scene, optimizerOptions, optimizerConfig.autoGeneratePriorities, optimizerConfig.improvementMode);
|
|
|
- this.sceneOptimizer.start();
|
|
|
- }
|
|
|
-
|
|
|
- // image processing configuration - optional.
|
|
|
- if (this.configuration.scene.imageProcessingConfiguration) {
|
|
|
- this.extendClassWithConfig(this.scene.imageProcessingConfiguration, this.configuration.scene.imageProcessingConfiguration);
|
|
|
- }
|
|
|
- if (this.configuration.scene.environmentTexture) {
|
|
|
- const environmentTexture = CubeTexture.CreateFromPrefilteredData(this.configuration.scene.environmentTexture, this.scene);
|
|
|
- this.scene.environmentTexture = environmentTexture;
|
|
|
+ this.configureOptimizer(this.configuration.optimizer);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-
|
|
|
-
|
|
|
return Promise.resolve(this.scene);
|
|
|
}
|
|
|
|
|
@@ -287,8 +661,14 @@ export abstract class AbstractViewer {
|
|
|
let plugin = (typeof model === 'string') ? undefined : model.loader;
|
|
|
|
|
|
return Promise.resolve(this.scene).then((scene) => {
|
|
|
- if (!scene || clearScene) return this.initScene();
|
|
|
- else return this.scene!;
|
|
|
+ if (!scene) return this.initScene();
|
|
|
+
|
|
|
+ if (clearScene) {
|
|
|
+ scene.meshes.forEach(mesh => {
|
|
|
+ mesh.dispose();
|
|
|
+ });
|
|
|
+ }
|
|
|
+ return scene!;
|
|
|
}).then(() => {
|
|
|
return new Promise<Array<AbstractMesh>>((resolve, reject) => {
|
|
|
this.lastUsedLoader = SceneLoader.ImportMesh(undefined, base, filename, this.scene, (meshes) => {
|
|
@@ -297,14 +677,23 @@ export abstract class AbstractViewer {
|
|
|
this.onModelLoadProgressObservable.notifyObserversWithPromise(progressEvent);
|
|
|
}, (e, m, exception) => {
|
|
|
// console.log(m, exception);
|
|
|
- reject(m);
|
|
|
+ this.onModelLoadErrorObservable.notifyObserversWithPromise({ message: m, exception: exception }).then(() => {
|
|
|
+ reject(exception);
|
|
|
+ });
|
|
|
}, plugin)!;
|
|
|
this.onLoaderInitObservable.notifyObserversWithPromise(this.lastUsedLoader);
|
|
|
});
|
|
|
}).then((meshes: Array<AbstractMesh>) => {
|
|
|
return this.onModelLoadedObservable.notifyObserversWithPromise(meshes)
|
|
|
.then(() => {
|
|
|
- this.initEnvironment();
|
|
|
+ // update the models' configuration
|
|
|
+ this.configureModel(model, meshes);
|
|
|
+ this.configureLights(this.configuration.lights);
|
|
|
+
|
|
|
+ if (this.configuration.camera) {
|
|
|
+ this.configureCamera(this.configuration.camera, meshes);
|
|
|
+ }
|
|
|
+ return this.initEnvironment(meshes);
|
|
|
}).then(() => {
|
|
|
return this.scene;
|
|
|
});
|
|
@@ -312,108 +701,7 @@ export abstract class AbstractViewer {
|
|
|
}
|
|
|
|
|
|
protected initEnvironment(focusMeshes: Array<AbstractMesh> = []): Promise<Scene> {
|
|
|
- if (!this.configuration.skybox && !this.configuration.ground) {
|
|
|
- if (this.environmentHelper) {
|
|
|
- this.environmentHelper.dispose();
|
|
|
- };
|
|
|
- return Promise.resolve(this.scene);
|
|
|
- }
|
|
|
-
|
|
|
- const options: Partial<IEnvironmentHelperOptions> = {
|
|
|
- createGround: !!this.configuration.ground,
|
|
|
- createSkybox: !!this.configuration.skybox,
|
|
|
- setupImageProcessing: false // will be done at the scene level!
|
|
|
- };
|
|
|
-
|
|
|
- if (this.configuration.ground) {
|
|
|
- let groundConfig = (typeof this.configuration.ground === 'boolean') ? {} : this.configuration.ground;
|
|
|
-
|
|
|
- let groundSize = groundConfig.size || (this.configuration.skybox && this.configuration.skybox.scale);
|
|
|
- if (groundSize) {
|
|
|
- options.groundSize = groundSize;
|
|
|
- }
|
|
|
-
|
|
|
- options.enableGroundShadow = this.configuration.ground === true || groundConfig.receiveShadows;
|
|
|
- if (groundConfig.shadowLevel) {
|
|
|
- options.groundShadowLevel = groundConfig.shadowLevel;
|
|
|
- }
|
|
|
- options.enableGroundMirror = !!groundConfig.mirror;
|
|
|
- if (groundConfig.texture) {
|
|
|
- options.groundTexture = groundConfig.texture;
|
|
|
- }
|
|
|
- if (groundConfig.color) {
|
|
|
- options.groundColor = new Color3(groundConfig.color.r, groundConfig.color.g, groundConfig.color.b)
|
|
|
- }
|
|
|
-
|
|
|
- if (groundConfig.mirror) {
|
|
|
- options.enableGroundMirror = true;
|
|
|
- // to prevent undefines
|
|
|
- if (typeof groundConfig.mirror === "object") {
|
|
|
- if (groundConfig.mirror.amount)
|
|
|
- options.groundMirrorAmount = groundConfig.mirror.amount;
|
|
|
- if (groundConfig.mirror.sizeRatio)
|
|
|
- options.groundMirrorSizeRatio = groundConfig.mirror.sizeRatio;
|
|
|
- if (groundConfig.mirror.blurKernel)
|
|
|
- options.groundMirrorBlurKernel = groundConfig.mirror.blurKernel;
|
|
|
- if (groundConfig.mirror.fresnelWeight)
|
|
|
- options.groundMirrorFresnelWeight = groundConfig.mirror.fresnelWeight;
|
|
|
- if (groundConfig.mirror.fallOffDistance)
|
|
|
- options.groundMirrorFallOffDistance = groundConfig.mirror.fallOffDistance;
|
|
|
- if (this.defaultHighpTextureType !== undefined)
|
|
|
- options.groundMirrorTextureType = this.defaultHighpTextureType;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- let postInitSkyboxMaterial = false;
|
|
|
- if (this.configuration.skybox) {
|
|
|
- let conf = this.configuration.skybox;
|
|
|
- if (conf.material && conf.material.imageProcessingConfiguration) {
|
|
|
- options.setupImageProcessing = false; // will be configured later manually.
|
|
|
- }
|
|
|
- let skyboxSize = this.configuration.skybox.scale;
|
|
|
- if (skyboxSize) {
|
|
|
- options.skyboxSize = skyboxSize;
|
|
|
- }
|
|
|
- options.sizeAuto = !options.skyboxSize;
|
|
|
- if (conf.color) {
|
|
|
- options.skyboxColor = new Color3(conf.color.r, conf.color.g, conf.color.b)
|
|
|
- }
|
|
|
- if (conf.cubeTexture && conf.cubeTexture.url) {
|
|
|
- if (typeof conf.cubeTexture.url === "string") {
|
|
|
- options.skyboxTexture = conf.cubeTexture.url;
|
|
|
- } else {
|
|
|
- // init later!
|
|
|
- postInitSkyboxMaterial = true;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (conf.material && conf.material.imageProcessingConfiguration) {
|
|
|
- postInitSkyboxMaterial = true;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (!this.environmentHelper) {
|
|
|
- this.environmentHelper = this.scene.createDefaultEnvironment(options)!;
|
|
|
- }
|
|
|
- else {
|
|
|
- // there might be a new scene! we need to dispose.
|
|
|
- // Need to decide if a scene should stay or be disposed.
|
|
|
- this.environmentHelper.dispose();
|
|
|
- //this.environmentHelper.updateOptions(options);
|
|
|
- this.environmentHelper = this.scene.createDefaultEnvironment(options)!;
|
|
|
- }
|
|
|
- console.log(options);
|
|
|
-
|
|
|
- if (postInitSkyboxMaterial) {
|
|
|
- let skyboxMaterial = this.environmentHelper.skyboxMaterial;
|
|
|
- if (skyboxMaterial) {
|
|
|
- if (this.configuration.skybox && this.configuration.skybox.material && this.configuration.skybox.material.imageProcessingConfiguration) {
|
|
|
- this.extendClassWithConfig(skyboxMaterial.imageProcessingConfiguration, this.configuration.skybox.material.imageProcessingConfiguration);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+ this.configureEnvironment(this.configuration.skybox, this.configuration.ground);
|
|
|
|
|
|
return Promise.resolve(this.scene);
|
|
|
}
|
|
@@ -492,4 +780,54 @@ export abstract class AbstractViewer {
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
+
|
|
|
+ private setCameraBehavior(behaviorConfig: number | {
|
|
|
+ type: number;
|
|
|
+ [propName: string]: any;
|
|
|
+ }, payload: any) {
|
|
|
+
|
|
|
+ let behavior: Behavior<ArcRotateCamera> | null;
|
|
|
+ let type = (typeof behaviorConfig !== "object") ? behaviorConfig : behaviorConfig.type;
|
|
|
+
|
|
|
+ let config: { [propName: string]: any } = (typeof behaviorConfig === "object") ? behaviorConfig : {};
|
|
|
+
|
|
|
+ // constructing behavior
|
|
|
+ switch (type) {
|
|
|
+ case CameraBehavior.AUTOROTATION:
|
|
|
+ behavior = new AutoRotationBehavior();
|
|
|
+ break;
|
|
|
+ case CameraBehavior.BOUNCING:
|
|
|
+ behavior = new BouncingBehavior();
|
|
|
+ break;
|
|
|
+ case CameraBehavior.FRAMING:
|
|
|
+ behavior = new FramingBehavior();
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ behavior = null;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (behavior) {
|
|
|
+ if (typeof behaviorConfig === "object") {
|
|
|
+ this.extendClassWithConfig(behavior, behaviorConfig);
|
|
|
+ }
|
|
|
+ this.camera.addBehavior(behavior);
|
|
|
+ }
|
|
|
+
|
|
|
+ // post attach configuration. Some functionalities require the attached camera.
|
|
|
+ switch (type) {
|
|
|
+ case CameraBehavior.AUTOROTATION:
|
|
|
+ break;
|
|
|
+ case CameraBehavior.BOUNCING:
|
|
|
+ break;
|
|
|
+ case CameraBehavior.FRAMING:
|
|
|
+ if (config.zoomOnBoundingInfo) {
|
|
|
+ //payload is an array of meshes
|
|
|
+ let meshes = <Array<AbstractMesh>>payload;
|
|
|
+ let bounding = meshes[0].getHierarchyBoundingVectors();
|
|
|
+ (<FramingBehavior>behavior).zoomOnBoundingInfo(bounding.min, bounding.max);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|