|
@@ -8,7 +8,7 @@ import { Mesh } from 'babylonjs/Meshes/mesh';
|
|
|
import { Vector3 } from 'babylonjs/Maths/math.vector';
|
|
|
import { HemisphericLight } from 'babylonjs/Lights/hemisphericLight';
|
|
|
import { ArcRotateCamera } from 'babylonjs/Cameras/arcRotateCamera';
|
|
|
-import { PreviewMeshType } from './previewMeshType';
|
|
|
+import { PreviewType } from './previewType';
|
|
|
import { Animation } from 'babylonjs/Animations/animation';
|
|
|
import { SceneLoader } from 'babylonjs/Loading/sceneLoader';
|
|
|
import { TransformNode } from 'babylonjs/Meshes/transformNode';
|
|
@@ -17,11 +17,17 @@ import { FramingBehavior } from 'babylonjs/Behaviors/Cameras/framingBehavior';
|
|
|
import { DirectionalLight } from 'babylonjs/Lights/directionalLight';
|
|
|
import { LogEntry } from '../log/logComponent';
|
|
|
import { PointerEventTypes } from 'babylonjs/Events/pointerEvents';
|
|
|
-import { Color3 } from 'babylonjs/Maths/math.color';
|
|
|
+import { Color3, Color4 } from 'babylonjs/Maths/math.color';
|
|
|
import { PostProcess } from 'babylonjs/PostProcesses/postProcess';
|
|
|
import { Constants } from 'babylonjs/Engines/constants';
|
|
|
import { CurrentScreenBlock } from 'babylonjs/Materials/Node/Blocks/Dual/currentScreenBlock';
|
|
|
import { NodeMaterialModes } from 'babylonjs/Materials/Node/Enums/nodeMaterialModes';
|
|
|
+import { ParticleSystem } from 'babylonjs/Particles/particleSystem';
|
|
|
+import { IParticleSystem } from 'babylonjs/Particles/IParticleSystem';
|
|
|
+import { ParticleHelper } from 'babylonjs/Particles/particleHelper';
|
|
|
+import { Texture } from 'babylonjs/Materials/Textures/texture';
|
|
|
+import { ParticleTextureBlock } from 'babylonjs/Materials/Node/Blocks/Particle/particleTextureBlock';
|
|
|
+import { FileTools } from 'babylonjs/Misc/fileTools';
|
|
|
|
|
|
export class PreviewManager {
|
|
|
private _nodeMaterial: NodeMaterial;
|
|
@@ -42,6 +48,7 @@ export class PreviewManager {
|
|
|
private _currentType: number;
|
|
|
private _lightParent: TransformNode;
|
|
|
private _postprocess: Nullable<PostProcess>;
|
|
|
+ private _particleSystem: Nullable<IParticleSystem>;
|
|
|
|
|
|
public constructor(targetCanvas: HTMLCanvasElement, globalState: GlobalState) {
|
|
|
this._nodeMaterial = globalState.nodeMaterial;
|
|
@@ -104,9 +111,7 @@ export class PreviewManager {
|
|
|
this._scene.render();
|
|
|
});
|
|
|
|
|
|
- // let cameraLastRotation = 0;
|
|
|
let lastOffsetX: number | undefined = undefined;
|
|
|
- // const lightRotationParallaxSpeed = 0.5;
|
|
|
const lightRotationSpeed = 0.01;
|
|
|
|
|
|
this._scene.onPointerObservable.add((evt) => {
|
|
@@ -193,35 +198,47 @@ export class PreviewManager {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private _prepareMeshes() {
|
|
|
- if (this._globalState.mode !== NodeMaterialModes.PostProcess) {
|
|
|
- this._prepareLights();
|
|
|
+ private _prepareScene() {
|
|
|
+ this._camera.useFramingBehavior = this._globalState.mode === NodeMaterialModes.Material;
|
|
|
|
|
|
- // Framing
|
|
|
- this._camera.useFramingBehavior = true;
|
|
|
+ switch (this._globalState.mode) {
|
|
|
+ case NodeMaterialModes.Material: {
|
|
|
+ this._prepareLights();
|
|
|
|
|
|
- var framingBehavior = this._camera.getBehaviorByName("Framing") as FramingBehavior;
|
|
|
+ var framingBehavior = this._camera.getBehaviorByName("Framing") as FramingBehavior;
|
|
|
|
|
|
- setTimeout(() => { // Let the behavior activate first
|
|
|
- framingBehavior.framingTime = 0;
|
|
|
- framingBehavior.elevationReturnTime = -1;
|
|
|
+ setTimeout(() => { // Let the behavior activate first
|
|
|
+ framingBehavior.framingTime = 0;
|
|
|
+ framingBehavior.elevationReturnTime = -1;
|
|
|
|
|
|
- if (this._scene.meshes.length) {
|
|
|
- var worldExtends = this._scene.getWorldExtends();
|
|
|
- this._camera.lowerRadiusLimit = null;
|
|
|
- this._camera.upperRadiusLimit = null;
|
|
|
- framingBehavior.zoomOnBoundingInfo(worldExtends.min, worldExtends.max);
|
|
|
- }
|
|
|
+ if (this._scene.meshes.length) {
|
|
|
+ var worldExtends = this._scene.getWorldExtends();
|
|
|
+ this._camera.lowerRadiusLimit = null;
|
|
|
+ this._camera.upperRadiusLimit = null;
|
|
|
+ framingBehavior.zoomOnBoundingInfo(worldExtends.min, worldExtends.max);
|
|
|
+ }
|
|
|
|
|
|
- this._camera.pinchPrecision = 200 / this._camera.radius;
|
|
|
- this._camera.upperRadiusLimit = 5 * this._camera.radius;
|
|
|
- });
|
|
|
+ this._camera.pinchPrecision = 200 / this._camera.radius;
|
|
|
+ this._camera.upperRadiusLimit = 5 * this._camera.radius;
|
|
|
+ });
|
|
|
|
|
|
- this._camera.wheelDeltaPercentage = 0.01;
|
|
|
- this._camera.pinchDeltaPercentage = 0.01;
|
|
|
+ this._camera.wheelDeltaPercentage = 0.01;
|
|
|
+ this._camera.pinchDeltaPercentage = 0.01;
|
|
|
|
|
|
- // Animations
|
|
|
- this._handleAnimations();
|
|
|
+ // Animations
|
|
|
+ this._handleAnimations();
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ case NodeMaterialModes.PostProcess: {
|
|
|
+ this._camera.radius = 4;
|
|
|
+ this._camera.upperRadiusLimit = 10;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ case NodeMaterialModes.Particle: {
|
|
|
+ this._camera.radius = this._globalState.previewType === PreviewType.Explosion ? 50 : this._globalState.previewType === PreviewType.DefaultParticleSystem ? 6 : 20;
|
|
|
+ this._camera.upperRadiusLimit = 5000;
|
|
|
+ break;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
// Material
|
|
@@ -230,9 +247,8 @@ export class PreviewManager {
|
|
|
}
|
|
|
|
|
|
private _refreshPreviewMesh() {
|
|
|
-
|
|
|
- if (this._currentType !== this._globalState.previewMeshType || this._currentType === PreviewMeshType.Custom) {
|
|
|
- this._currentType = this._globalState.previewMeshType;
|
|
|
+ if (this._currentType !== this._globalState.previewType || this._currentType === PreviewType.Custom) {
|
|
|
+ this._currentType = this._globalState.previewType;
|
|
|
if (this._meshes && this._meshes.length) {
|
|
|
for (var mesh of this._meshes) {
|
|
|
mesh.dispose();
|
|
@@ -247,55 +263,144 @@ export class PreviewManager {
|
|
|
|
|
|
this._engine.releaseEffects();
|
|
|
|
|
|
+ if (this._particleSystem) {
|
|
|
+ this._particleSystem.onBeforeDrawParticlesObservable.clear();
|
|
|
+ this._particleSystem.onDisposeObservable.clear();
|
|
|
+ this._particleSystem.stop();
|
|
|
+ this._particleSystem.dispose();
|
|
|
+ this._particleSystem = null;
|
|
|
+ }
|
|
|
+
|
|
|
SceneLoader.ShowLoadingScreen = false;
|
|
|
|
|
|
this._globalState.onIsLoadingChanged.notifyObservers(true);
|
|
|
|
|
|
- if (this._globalState.mode !== NodeMaterialModes.PostProcess) {
|
|
|
- switch (this._globalState.previewMeshType) {
|
|
|
- case PreviewMeshType.Box:
|
|
|
+ if (this._globalState.mode === NodeMaterialModes.Material) {
|
|
|
+ switch (this._globalState.previewType) {
|
|
|
+ case PreviewType.Box:
|
|
|
SceneLoader.AppendAsync("https://models.babylonjs.com/", "roundedCube.glb", this._scene).then(() => {
|
|
|
this._meshes.push(...this._scene.meshes);
|
|
|
- this._prepareMeshes();
|
|
|
+ this._prepareScene();
|
|
|
});
|
|
|
return;
|
|
|
- case PreviewMeshType.Sphere:
|
|
|
+ case PreviewType.Sphere:
|
|
|
this._meshes.push(Mesh.CreateSphere("dummy-sphere", 32, 2, this._scene));
|
|
|
break;
|
|
|
- case PreviewMeshType.Torus:
|
|
|
+ case PreviewType.Torus:
|
|
|
this._meshes.push(Mesh.CreateTorus("dummy-torus", 2, 0.5, 32, this._scene));
|
|
|
break;
|
|
|
- case PreviewMeshType.Cylinder:
|
|
|
+ case PreviewType.Cylinder:
|
|
|
SceneLoader.AppendAsync("https://models.babylonjs.com/", "roundedCylinder.glb", this._scene).then(() => {
|
|
|
this._meshes.push(...this._scene.meshes);
|
|
|
- this._prepareMeshes();
|
|
|
+ this._prepareScene();
|
|
|
});
|
|
|
return;
|
|
|
- case PreviewMeshType.Plane:
|
|
|
+ case PreviewType.Plane:
|
|
|
let plane = Mesh.CreateGround("dummy-plane", 2, 2, 128, this._scene);
|
|
|
plane.scaling.y = -1;
|
|
|
plane.rotation.x = Math.PI;
|
|
|
this._meshes.push(plane);
|
|
|
break;
|
|
|
- case PreviewMeshType.ShaderBall:
|
|
|
+ case PreviewType.ShaderBall:
|
|
|
SceneLoader.AppendAsync("https://models.babylonjs.com/", "shaderBall.glb", this._scene).then(() => {
|
|
|
this._meshes.push(...this._scene.meshes);
|
|
|
- this._prepareMeshes();
|
|
|
+ this._prepareScene();
|
|
|
});
|
|
|
return;
|
|
|
- case PreviewMeshType.Custom:
|
|
|
- SceneLoader.AppendAsync("file:", this._globalState.previewMeshFile, this._scene).then(() => {
|
|
|
+ case PreviewType.Custom:
|
|
|
+ SceneLoader.AppendAsync("file:", this._globalState.previewFile, this._scene).then(() => {
|
|
|
this._meshes.push(...this._scene.meshes);
|
|
|
- this._prepareMeshes();
|
|
|
+ this._prepareScene();
|
|
|
+ });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ } else if (this._globalState.mode === NodeMaterialModes.Particle) {
|
|
|
+ switch (this._globalState.previewType) {
|
|
|
+ case PreviewType.DefaultParticleSystem:
|
|
|
+ this._particleSystem = ParticleHelper.CreateDefault(new Vector3(0, 0, 0), 500, this._scene);
|
|
|
+ this._particleSystem.start();
|
|
|
+ break;
|
|
|
+ case PreviewType.Bubbles:
|
|
|
+ this._particleSystem = new ParticleSystem("particles", 4000, this._scene);
|
|
|
+ this._particleSystem.particleTexture = new Texture("https://assets.babylonjs.com/particles/textures/explosion/Flare.png", this._scene);
|
|
|
+ this._particleSystem.minSize = 0.1;
|
|
|
+ this._particleSystem.maxSize = 1.0;
|
|
|
+ this._particleSystem.minLifeTime = 0.5;
|
|
|
+ this._particleSystem.maxLifeTime = 5.0;
|
|
|
+ this._particleSystem.minEmitPower = 0.5;
|
|
|
+ this._particleSystem.maxEmitPower = 3.0;
|
|
|
+ this._particleSystem.createBoxEmitter(new Vector3(-1, 1, -1), new Vector3(1, 1, 1), new Vector3(-0.1, -0.1, -0.1), new Vector3(0.1, 0.1, 0.1));
|
|
|
+ this._particleSystem.emitRate = 100;
|
|
|
+ this._particleSystem.blendMode = ParticleSystem.BLENDMODE_ONEONE;
|
|
|
+ this._particleSystem.color1 = new Color4(1, 1, 0, 1);
|
|
|
+ this._particleSystem.color2 = new Color4(1, 0.5, 0, 1);
|
|
|
+ this._particleSystem.gravity = new Vector3(0, -1.0, 0);
|
|
|
+ this._particleSystem.start();
|
|
|
+ break;
|
|
|
+ case PreviewType.Explosion:
|
|
|
+ this._loadParticleSystem(this._globalState.previewType, 1);
|
|
|
+ return;
|
|
|
+ case PreviewType.Fire:
|
|
|
+ case PreviewType.Rain:
|
|
|
+ case PreviewType.Smoke:
|
|
|
+ this._loadParticleSystem(this._globalState.previewType);
|
|
|
+ return;
|
|
|
+ case PreviewType.Custom:
|
|
|
+ FileTools.ReadFile(this._globalState.previewFile, (json) => {
|
|
|
+ this._particleSystem = ParticleSystem.Parse(JSON.parse(json), this._scene, "");
|
|
|
+ this._particleSystem.start();
|
|
|
+ this._prepareScene();
|
|
|
+ }, undefined, false, (error) => {
|
|
|
+ console.log(error);
|
|
|
});
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- this._prepareMeshes();
|
|
|
+ this._prepareScene();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ private _loadParticleSystem(particleNumber: number, systemIndex = 0, prepareScene = true) {
|
|
|
+ let name = "";
|
|
|
+
|
|
|
+ switch (particleNumber) {
|
|
|
+ case PreviewType.Explosion:
|
|
|
+ name = "explosion";
|
|
|
+ break;
|
|
|
+ case PreviewType.Fire:
|
|
|
+ name = "fire";
|
|
|
+ break;
|
|
|
+ case PreviewType.Rain:
|
|
|
+ name = "rain";
|
|
|
+ break;
|
|
|
+ case PreviewType.Smoke:
|
|
|
+ name = "smoke";
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ ParticleHelper.CreateAsync(name, this._scene).then((set) => {
|
|
|
+ for (let i = 0; i < set.systems.length; ++i) {
|
|
|
+ if (i == systemIndex) {
|
|
|
+ this._particleSystem = set.systems[i];
|
|
|
+ this._particleSystem.disposeOnStop = true;
|
|
|
+ this._particleSystem.onDisposeObservable.add(() => {
|
|
|
+ this._loadParticleSystem(particleNumber, systemIndex, false);
|
|
|
+ });
|
|
|
+ this._particleSystem.start();
|
|
|
+ } else {
|
|
|
+ set.systems[i].dispose();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (prepareScene) {
|
|
|
+ this._prepareScene();
|
|
|
+ } else {
|
|
|
+ let serializationObject = this._nodeMaterial.serialize();
|
|
|
+ this._updatePreview(serializationObject);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
private _forceCompilationAsync(material: NodeMaterial, mesh: AbstractMesh): Promise<void> {
|
|
|
return material.forceCompilationAsync(mesh);
|
|
|
}
|
|
@@ -312,42 +417,65 @@ export class PreviewManager {
|
|
|
this._postprocess = null;
|
|
|
}
|
|
|
|
|
|
- if (this._globalState.mode === NodeMaterialModes.PostProcess) {
|
|
|
- this._globalState.onIsLoadingChanged.notifyObservers(false);
|
|
|
+ switch (this._globalState.mode) {
|
|
|
+ case NodeMaterialModes.PostProcess: {
|
|
|
+ this._globalState.onIsLoadingChanged.notifyObservers(false);
|
|
|
|
|
|
- this._postprocess = tempMaterial.createPostProcess(this._camera, 1.0, Constants.TEXTURE_NEAREST_SAMPLINGMODE, this._engine);
|
|
|
+ this._postprocess = tempMaterial.createPostProcess(this._camera, 1.0, Constants.TEXTURE_NEAREST_SAMPLINGMODE, this._engine);
|
|
|
|
|
|
- const currentScreen = tempMaterial.getBlockByPredicate((block) => block instanceof CurrentScreenBlock);
|
|
|
- if (currentScreen) {
|
|
|
- this._postprocess!.onApplyObservable.add((effect) => {
|
|
|
- effect.setTexture("textureSampler", (currentScreen as CurrentScreenBlock).texture);
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- if (this._material) {
|
|
|
- this._material.dispose();
|
|
|
- }
|
|
|
- this._material = tempMaterial;
|
|
|
- } else if (this._meshes.length) {
|
|
|
- let tasks = this._meshes.map((m) => this._forceCompilationAsync(tempMaterial, m));
|
|
|
-
|
|
|
- Promise.all(tasks).then(() => {
|
|
|
- for (var mesh of this._meshes) {
|
|
|
- mesh.material = tempMaterial;
|
|
|
+ const currentScreen = tempMaterial.getBlockByPredicate((block) => block instanceof CurrentScreenBlock);
|
|
|
+ if (currentScreen) {
|
|
|
+ this._postprocess!.onApplyObservable.add((effect) => {
|
|
|
+ effect.setTexture("textureSampler", (currentScreen as CurrentScreenBlock).texture);
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
if (this._material) {
|
|
|
this._material.dispose();
|
|
|
}
|
|
|
-
|
|
|
this._material = tempMaterial;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ case NodeMaterialModes.Particle: {
|
|
|
this._globalState.onIsLoadingChanged.notifyObservers(false);
|
|
|
- }).catch((reason) => {
|
|
|
- this._globalState.onLogRequiredObservable.notifyObservers(new LogEntry("Shader compilation error:\r\n" + reason, true));
|
|
|
- this._globalState.onIsLoadingChanged.notifyObservers(false);
|
|
|
- });
|
|
|
- } else {
|
|
|
- this._material = tempMaterial;
|
|
|
+
|
|
|
+ this._particleSystem!.onBeforeDrawParticlesObservable.clear();
|
|
|
+
|
|
|
+ this._particleSystem!.onBeforeDrawParticlesObservable.add((effect) => {
|
|
|
+ const textureBlock = tempMaterial.getBlockByPredicate((block) => block instanceof ParticleTextureBlock);
|
|
|
+ if (textureBlock && (textureBlock as ParticleTextureBlock).texture && effect) {
|
|
|
+ effect.setTexture("diffuseSampler", (textureBlock as ParticleTextureBlock).texture);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ tempMaterial.createEffectForParticles(this._particleSystem!);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ default: {
|
|
|
+ if (this._meshes.length) {
|
|
|
+ let tasks = this._meshes.map((m) => this._forceCompilationAsync(tempMaterial, m));
|
|
|
+
|
|
|
+ Promise.all(tasks).then(() => {
|
|
|
+ for (var mesh of this._meshes) {
|
|
|
+ mesh.material = tempMaterial;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this._material) {
|
|
|
+ this._material.dispose();
|
|
|
+ }
|
|
|
+
|
|
|
+ this._material = tempMaterial;
|
|
|
+ this._globalState.onIsLoadingChanged.notifyObservers(false);
|
|
|
+ }).catch((reason) => {
|
|
|
+ this._globalState.onLogRequiredObservable.notifyObservers(new LogEntry("Shader compilation error:\r\n" + reason, true));
|
|
|
+ this._globalState.onIsLoadingChanged.notifyObservers(false);
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ this._material = tempMaterial;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
}
|
|
|
} catch (err) {
|
|
|
// Ignore the error
|