import * as React from "react"; import { Observable } from "babylonjs/Misc/observable"; import { PropertyChangedEvent } from "../../../../propertyChangedEvent"; import { LineContainerComponent } from "../../../lineContainerComponent"; import { TextLineComponent } from "../../../lines/textLineComponent"; import { LockObject } from "../lockObject"; import { GlobalState } from '../../../../globalState'; import { CustomPropertyGridComponent } from '../customPropertyGridComponent'; import { IParticleSystem } from 'babylonjs/Particles/IParticleSystem'; import { FloatLineComponent } from '../../../lines/floatLineComponent'; import { ButtonLineComponent } from '../../../lines/buttonLineComponent'; import { TextureLinkLineComponent } from '../../../lines/textureLinkLineComponent'; import { OptionsLineComponent } from '../../../lines/optionsLineComponent'; import { ParticleSystem } from 'babylonjs/Particles/particleSystem'; import { Color4LineComponent } from '../../../lines/color4LineComponent'; import { Vector3LineComponent } from '../../../lines/vector3LineComponent'; import { CheckBoxLineComponent } from '../../../lines/checkBoxLineComponent'; import { SliderLineComponent } from '../../../lines/sliderLineComponent'; import { BoxParticleEmitter } from 'babylonjs/Particles/EmitterTypes/boxParticleEmitter'; import { ConeParticleEmitter } from 'babylonjs/Particles/EmitterTypes/coneParticleEmitter'; import { CylinderParticleEmitter } from 'babylonjs/Particles/EmitterTypes/cylinderParticleEmitter'; import { HemisphericParticleEmitter } from 'babylonjs/Particles/EmitterTypes/hemisphericParticleEmitter'; import { PointParticleEmitter } from 'babylonjs/Particles/EmitterTypes/pointParticleEmitter'; import { SphereParticleEmitter } from 'babylonjs/Particles/EmitterTypes/sphereParticleEmitter'; import { BoxEmitterGridComponent } from './boxEmitterGridComponent'; import { ConeEmitterGridComponent } from './coneEmitterGridComponent'; import { CylinderEmitterGridComponent } from './cylinderEmitterGridComponent'; import { HemisphericEmitterGridComponent } from './hemisphericEmitterGridComponent'; import { PointEmitterGridComponent } from './pointEmitterGridComponent'; import { SphereEmitterGridComponent } from './sphereEmitterGridComponent'; import { Vector3 } from 'babylonjs/Maths/math.vector'; import { AbstractMesh } from 'babylonjs/Meshes/abstractMesh'; import { MeshParticleEmitter } from 'babylonjs/Particles/EmitterTypes/meshParticleEmitter'; import { MeshEmitterGridComponent } from './meshEmitterGridComponent'; import { ValueGradientGridComponent, GradientGridMode } from './valueGradientGridComponent'; import { Color3, Color4 } from 'babylonjs/Maths/math.color'; import { GPUParticleSystem } from 'babylonjs/Particles/gpuParticleSystem'; import { Tools } from 'babylonjs/Misc/tools'; import { FileButtonLineComponent } from '../../../lines/fileButtonLineComponent'; import { TextInputLineComponent } from '../../../lines/textInputLineComponent'; interface IParticleSystemPropertyGridComponentProps { globalState: GlobalState; system: IParticleSystem, lockObject: LockObject, onSelectionChangedObservable?: Observable, onPropertyChangedObservable?: Observable } export class ParticleSystemPropertyGridComponent extends React.Component { private _snippetUrl = "https://snippet.babylonjs.com"; constructor(props: IParticleSystemPropertyGridComponentProps) { super(props); } renderEmitter() { const system = this.props.system; const replaySource = "particlesystem.particleEmitterType"; switch(system.particleEmitterType?.getClassName()) { case "BoxParticleEmitter": return ( ); case "ConeParticleEmitter": return ( ); case "CylinderParticleEmitter": return ( ); case "HemisphericParticleEmitter": return ( ); case "MeshParticleEmitter": return ( ); case "PointParticleEmitter": return ( ); case "SphereParticleEmitter": return ( ); } return null; } raiseOnPropertyChanged(property: string, newValue: any, previousValue: any) { if (!this.props.onPropertyChangedObservable) { return; } const system = this.props.system; this.props.onPropertyChangedObservable.notifyObservers({ object: system, property: property, value: newValue, initialValue: previousValue }); } renderControls() { const system = this.props.system; if (system instanceof GPUParticleSystem) { let isStarted = system.isStarted() && !system.isStopped(); return ( { if (isStarted) { system.stop(); system.reset(); } else { system.start(); } this.forceUpdate(); }} /> ); } let isStarted = system.isStarted(); return ( <> { !system.isStopping() && { if (isStarted) { system.stop(); } else { system.start(); } this.forceUpdate(); }} /> } { system.isStopping() && } ) } saveToFile() { const system = this.props.system; let content = JSON.stringify(system.serialize()); Tools.Download(new Blob([content]), "particleSystem.json"); } loadFromFile(file: File) { const system = this.props.system; const scene = system.getScene(); Tools.ReadFile(file, (data) => { let decoder = new TextDecoder("utf-8"); let jsonObject = JSON.parse(decoder.decode(data)); let isGpu = system instanceof GPUParticleSystem system.dispose(); this.props.globalState.onSelectionChangedObservable.notifyObservers(null); let newSystem = isGpu ? GPUParticleSystem.Parse(jsonObject, scene, "") : ParticleSystem.Parse(jsonObject, scene, ""); this.props.globalState.onSelectionChangedObservable.notifyObservers(newSystem); }, undefined, true); } saveToSnippet() { const system = this.props.system; let content = JSON.stringify(system.serialize()); var xmlHttp = new XMLHttpRequest(); xmlHttp.onreadystatechange = () => { if (xmlHttp.readyState == 4) { if (xmlHttp.status == 200) { var baseUrl = location.href.replace(location.hash, "").replace(location.search, ""); var snippet = JSON.parse(xmlHttp.responseText); var newUrl = baseUrl + "#" + snippet.id; system.snippetId = snippet.id; if (snippet.version && snippet.version != "0") { newUrl += "#" + snippet.version; } this.forceUpdate(); } else { alert("Unable to save your particle system"); } } } xmlHttp.open("POST", this._snippetUrl + (system.snippetId ? "/" + system.snippetId : ""), true); xmlHttp.setRequestHeader("Content-Type", "application/json"); var dataToSend = { payload : JSON.stringify({ particleSystem: content }), name: "", description: "", tags: "" }; xmlHttp.send(JSON.stringify(dataToSend)); } render() { const system = this.props.system; var blendModeOptions = [ { label: "Add", value: ParticleSystem.BLENDMODE_ADD }, { label: "Multiply", value: ParticleSystem.BLENDMODE_MULTIPLY }, { label: "Multiply Add", value: ParticleSystem.BLENDMODE_MULTIPLYADD }, { label: "OneOne", value: ParticleSystem.BLENDMODE_ONEONE }, { label: "Standard", value: ParticleSystem.BLENDMODE_STANDARD }, ]; var particleEmitterTypeOptions = [ { label: "Box", value: 0 }, { label: "Cone", value: 1 }, { label: "Cylinder", value: 2 }, { label: "Hemispheric", value: 3 }, { label: "Mesh", value: 4 }, { label: "Point", value: 5 }, { label: "Sphere", value: 6 }, ]; var meshEmitters = this.props.system.getScene().meshes.filter(m => !!m.name); var emitterOptions = [ { label: "None", value: -1 }, { label: "Vector3", value: 0 } ]; meshEmitters.sort((a, b) => a.name.localeCompare(b.name)); emitterOptions.push(...meshEmitters.map((v, i) => { return {label: v.name, value: i + 1} })); return (
{ system.snippetId && } {this.renderControls()} { this.props.globalState.onSelectionChangedObservable.notifyObservers(null); system.dispose(); }} /> this.loadFromFile(file)} accept=".json" /> this.saveToFile()} /> this.saveToSnippet()} /> { switch(value) { case -1: this.raiseOnPropertyChanged("emitter", null, system.emitter); system.emitter = null; break; case 0: this.raiseOnPropertyChanged("emitter", Vector3.Zero(), system.emitter); system.emitter = Vector3.Zero(); break; default: this.raiseOnPropertyChanged("emitter", meshEmitters[value - 1], system.emitter); system.emitter = meshEmitters[value - 1]; } this.forceUpdate(); }} extractValue={() => { if (!system.emitter) { return -1; } if ((system.emitter as Vector3).x !== undefined) { return 0; } let meshIndex = meshEmitters.indexOf(system.emitter as AbstractMesh) if (meshIndex > -1) { return meshIndex + 1; } return -1; }} /> { system.emitter && ((system.emitter as Vector3).x === undefined) && this.props.globalState.onSelectionChangedObservable.notifyObservers(system.emitter)}/> } { system.emitter && ((system.emitter as Vector3).x !== undefined) && } { const currentType = system.particleEmitterType; switch(value) { case 0: system.particleEmitterType = new BoxParticleEmitter(); break; case 1: system.particleEmitterType = new ConeParticleEmitter(); break; case 2: system.particleEmitterType = new CylinderParticleEmitter(); break; case 3: system.particleEmitterType = new HemisphericParticleEmitter(); break; case 4: system.particleEmitterType = new MeshParticleEmitter(); break; case 5: system.particleEmitterType = new PointParticleEmitter(); break; case 6: system.particleEmitterType = new SphereParticleEmitter(); break; } this.raiseOnPropertyChanged("particleEmitterType", system.particleEmitterType, currentType) this.forceUpdate(); }} extractValue={() => { switch(system.particleEmitterType?.getClassName()) { case "BoxParticleEmitter": return 0; case "ConeParticleEmitter": return 1; case "CylinderParticleEmitter": return 2; case "HemisphericParticleEmitter": return 3; case "MeshParticleEmitter": return 4; case "PointParticleEmitter": return 5; case "SphereParticleEmitter": return 6; } return 0; }}/> { this.renderEmitter() } { system instanceof ParticleSystem && { system.addEmitRateGradient(0, 50, 50); this.props.globalState.onCodeChangedObservable.notifyObservers({ object: system, code: `TARGET.addEmitRateGradient(0, 50, 50);` }); }} mode={GradientGridMode.Factor} host={system} codeRecorderPropertyName="getEmitRateGradients()" lockObject={this.props.lockObject}/> } { system.addVelocityGradient(0, 0.1, 0.1); this.props.globalState.onCodeChangedObservable.notifyObservers({ object: system, code: `TARGET.addVelocityGradient(0, 0.1, 0.1);` }); }} mode={GradientGridMode.Factor} host={system} codeRecorderPropertyName="getVelocityGradients()" lockObject={this.props.lockObject}/> { system.addLimitVelocityGradient(0, 0.1, 0.1); this.props.globalState.onCodeChangedObservable.notifyObservers({ object: system, code: `TARGET.addLimitVelocityGradient(0, 0.1, 0.1);` }); }} mode={GradientGridMode.Factor} host={system} codeRecorderPropertyName="getLimitVelocityGradients()" lockObject={this.props.lockObject}/> { system.addDragGradient(0, 0.1, 0.1); this.props.globalState.onCodeChangedObservable.notifyObservers({ object: system, code: `TARGET.addDragGradient(0, 0.1, 0.1);` }); }} host={system} codeRecorderPropertyName="getDragGradients()" mode={GradientGridMode.Factor} lockObject={this.props.lockObject}/> { system instanceof ParticleSystem && { system.addStartSizeGradient(0, 1, 1); this.props.globalState.onCodeChangedObservable.notifyObservers({ object: system, code: `TARGET.addStartSizeGradient(0, 1, 1);` }); }} host={system} codeRecorderPropertyName="getStartSizeGradients()" mode={GradientGridMode.Factor} lockObject={this.props.lockObject}/> } { system.addSizeGradient(0, 1, 1); this.props.globalState.onCodeChangedObservable.notifyObservers({ object: system, code: `TARGET.addSizeGradient(0, 1, 1);` }); }} host={system} codeRecorderPropertyName="getSizeGradients()" mode={GradientGridMode.Factor} lockObject={this.props.lockObject}/> { system instanceof ParticleSystem && { system.addLifeTimeGradient(0, 1, 1); this.props.globalState.onCodeChangedObservable.notifyObservers({ object: system, code: `TARGET.addLifeTimeGradient(0, 1, 1);` }); }} host={system} codeRecorderPropertyName="getLifeTimeGradients()" mode={GradientGridMode.Factor} lockObject={this.props.lockObject}/> } { system.addColorGradient(0, new Color4(0, 0, 0, 1), new Color4(1, 1, 1, 1)); this.props.globalState.onCodeChangedObservable.notifyObservers({ object: system, code: `TARGET.addColorGradient(0, new BABYLON.Color4(0, 0, 0, 1), new BABYLON.Color4(1, 1, 1, 1));` }); }} host={system} codeRecorderPropertyName="getColorGradients()" mode={GradientGridMode.Color4} lockObject={this.props.lockObject}/> { system instanceof ParticleSystem && <> { system.addRampGradient(0, Color3.White()); this.props.globalState.onCodeChangedObservable.notifyObservers({ object: system, code: `TARGET.addRampGradient(0, BABYLON.Color3.White());` }); }} mode={GradientGridMode.Color3} host={system} codeRecorderPropertyName="getRampGradients()" lockObject={this.props.lockObject}/> { system.addColorRemapGradient(0, 1, 1); this.props.globalState.onCodeChangedObservable.notifyObservers({ object: system, code: `TARGET.addColorRemapGradient(0, 1, 1);` }); }} host={system} codeRecorderPropertyName="getColorRemapGradients()" mode={GradientGridMode.Factor} lockObject={this.props.lockObject}/> { system.addAlphaRemapGradient(0, 1, 1); this.props.globalState.onCodeChangedObservable.notifyObservers({ object: system, code: `TARGET.addAlphaRemapGradient(0, 1, 1);` }); }} host={system} codeRecorderPropertyName="getAlphaRemapGradients()" mode={GradientGridMode.Factor} lockObject={this.props.lockObject}/> } { system.addAngularSpeedGradient(0, 0.1, 0.1); this.props.globalState.onCodeChangedObservable.notifyObservers({ object: system, code: `TARGET.addAngularSpeedGradient(0, 0.1, 0.1);` }); }} host={system} codeRecorderPropertyName="getAngularSpeedGradients()" mode={GradientGridMode.Factor} lockObject={this.props.lockObject}/>
); } }