diff --git a/assets/templates/code/tools.d.ts b/assets/templates/code/tools.d.ts index 3e50745da..ad0dd149a 100644 --- a/assets/templates/code/tools.d.ts +++ b/assets/templates/code/tools.d.ts @@ -74,6 +74,11 @@ interface BehaviorCodeTools { * @param name the name of the prefab to instantiate */ instantiatePrefab (name: string): T; + /** + * Instantiates a particle system set identified by the given name + * @param name the name of the particle system set to instantiate + */ + instantiateParticleSystemSet (name: string): BABYLON.ParticleSystemSet; /** * Calls the given method with the given parameters on the given object which has scripts providing the given method * @param object the object reference where to send the message by calling the given method name diff --git a/assets/templates/particles-creator/class.js b/assets/templates/particles-creator/class.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/assets/templates/particles-creator/class.ts b/assets/templates/particles-creator/class.ts deleted file mode 100644 index fab8514b7..000000000 --- a/assets/templates/particles-creator/class.ts +++ /dev/null @@ -1,79 +0,0 @@ -class CustomParticles { - // Public members - public finalSize: number = 2; - - /** - * Constructor - */ - constructor () { - // Scope - const scope = this; - - // Custom update function - particleSystem.updateFunction = function (particles: BABYLON.Particle[]) { - for (var index = 0; index < particles.length; index++) { - var particle = particles[index]; - particle.age += this._scaledUpdateSpeed; - - if (particle.age < particle.lifeTime * .35) { - particle.size = scope.finalSize * particle.age / (particle.lifeTime * 0.35); - } - - if (particle.age >= particle.lifeTime) { // Recycle by swapping with last particle - this.recycleParticle(particle); - index--; - continue; - } - else { - var speed = this._scaledUpdateSpeed * 2; - if (particle.age >= particle.lifeTime / 2) { - speed = -speed; - } - - particle.colorStep.scaleToRef(speed, this._scaledColorStep); - particle.color.addInPlace(this._scaledColorStep); - - if (particle.color.a < 0) - particle.color.a = 0; - - particle.angle += particle.angularSpeed * this._scaledUpdateSpeed; - particle.direction.scaleToRef(this._scaledUpdateSpeed, this._scaledDirection); - particle.position.addInPlace(this._scaledDirection); - - this.gravity.scaleToRef(this._scaledUpdateSpeed, this._scaledGravity); - particle.direction.addInPlace(this._scaledGravity); - } - } - } - } - - /** - * Set the uniforms and samplers of the shader - * @param uniforms the shader's uniforms - * @param samplers the shader's samplers - */ - public setUniforms (uniforms: string[], samplers: string[]): void { - - } - - /** - * Set the defines of the shader - * @param defines the defines for the shader - */ - public setDefines (defines: string[]): void { - defines.push('#define CUSTOM_DEFINE'); - } - - /** - * On bind the particle system shader - * @param effect the effect for the particles - */ - public onBind (effect: BABYLON.Effect): void { - - } -} - -return { - ctor: CustomParticles, - finalSize: 2 -}; diff --git a/assets/templates/particles-creator/default-set.json b/assets/templates/particles-creator/default-set.json new file mode 100644 index 000000000..f8200f3c3 --- /dev/null +++ b/assets/templates/particles-creator/default-set.json @@ -0,0 +1,107 @@ +{ + "systems": [ + { + "name": "New Particle System", + "id": "1e0f653a-ef4b-4fae-b5ea-1917e0b4d9f4", + "capacity": 10000, + "emitterId": "aadd662b-8127-454e-905c-2e01b6f9f56c", + "particleEmitterType": { + "type": "BoxParticleEmitter", + "direction1": [ + -1, + 1, + -1 + ], + "direction2": [ + 1, + 1, + 1 + ], + "minEmitBox": [ + 0, + 0, + 0 + ], + "maxEmitBox": [ + 0, + 0, + 0 + ] + }, + "textureName": "data:image/png;base64,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", + "invertY": true, + "animations": [], + "beginAnimationOnStart": false, + "beginAnimationFrom": 0, + "beginAnimationTo": 60, + "beginAnimationLoop": false, + "startDelay": 0, + "renderingGroupId": 0, + "isBillboardBased": true, + "billboardMode": 7, + "minAngularSpeed": -0.5, + "maxAngularSpeed": 0.5, + "minSize": 0.1, + "maxSize": 0.5, + "minScaleX": 1, + "maxScaleX": 1, + "minScaleY": 1, + "maxScaleY": 1, + "minEmitPower": 0.5, + "maxEmitPower": 4, + "minLifeTime": 0.5, + "maxLifeTime": 2, + "emitRate": 400, + "gravity": [ + 0, + -2, + 0 + ], + "noiseStrength": [ + 10, + 10, + 10 + ], + "color1": [ + 1, + 0, + 0, + 1 + ], + "color2": [ + 0, + 1, + 1, + 1 + ], + "colorDead": [ + 0, + 0, + 0, + 1 + ], + "updateSpeed": 0.01, + "targetStopDuration": 0, + "blendMode": 0, + "preWarmCycles": 0, + "preWarmStepOffset": 1, + "minInitialRotation": 0, + "maxInitialRotation": 0, + "startSpriteCellID": 0, + "endSpriteCellID": 0, + "spriteCellChangeSpeed": 1, + "spriteCellWidth": 0, + "spriteCellHeight": 0, + "spriteRandomStartCell": false, + "isAnimationSheetEnabled": false, + "textureMask": [ + 1, + 1, + 1, + 1 + ], + "customShader": null, + "preventAutoStart": false + } + ] +} \ No newline at end of file diff --git a/assets/templates/particles-creator/shader.fragment.fx b/assets/templates/particles-creator/shader.fragment.fx deleted file mode 100644 index a750ebfb6..000000000 --- a/assets/templates/particles-creator/shader.fragment.fx +++ /dev/null @@ -1,22 +0,0 @@ -// Varying -varying vec2 vUV; -varying vec4 vColor; -#ifdef CLIPPLANE -varying float fClipDistance; -#endif - -// Uniforms -uniform sampler2D diffuseSampler; -uniform vec4 textureMask; - -// Main -void main(void) -{ - #ifdef CLIPPLANE - if (fClipDistance > 0.0) - discard; - #endif - - vec4 baseColor = texture2D(diffuseSampler, vUV); - gl_FragColor = (baseColor * textureMask + (vec4(1., 1., 1., 1.) - textureMask)) * vColor; -} \ No newline at end of file diff --git a/assets/templates/particles-creator/shader.vertex.fx b/assets/templates/particles-creator/shader.vertex.fx deleted file mode 100644 index fdd43242f..000000000 --- a/assets/templates/particles-creator/shader.vertex.fx +++ /dev/null @@ -1,55 +0,0 @@ -// Attributes -attribute vec3 position; -attribute vec4 color; -attribute vec3 options; -attribute vec2 size; -attribute float cellIndex; - -// Varyings -varying vec2 vUV; -varying vec4 vColor; - -// Uniforms -uniform mat4 view; -uniform mat4 projection; -uniform vec3 particlesInfos; - -#ifdef CLIPPLANE -uniform vec4 vClipPlane; -uniform mat4 invView; -varying float fClipDistance; -#endif - -// Main -void main(void) -{ - vec3 viewPos = (view * vec4(position, 1.0)).xyz; - vec2 cornerPos; - float angle = options.x; - vec2 offset = options.yz; - cornerPos = vec2(offset.x - 0.5, offset.y - 0.5) * size; - - vec3 rotatedCorner; - rotatedCorner.x = cornerPos.x * cos(angle) - cornerPos.y * sin(angle); - rotatedCorner.y = cornerPos.x * sin(angle) + cornerPos.y * cos(angle); - rotatedCorner.z = 0.; - - viewPos += rotatedCorner; - gl_Position = projection * vec4(viewPos, 1.0); - vColor = color; - -#ifdef ANIMATESHEET - float rowOffset = floor(cellIndex / particlesInfos.z); - float columnOffset = cellIndex - rowOffset * particlesInfos.z; - vec2 uvScale = particlesInfos.xy; - vec2 uvOffset = vec2(offset.x, 1.0 - offset.y); - vUV = (uvOffset + vec2(columnOffset, rowOffset)) * uvScale; -#else - vUV = offset; -#endif - -#ifdef CLIPPLANE - vec4 worldPos = invView * vec4(viewPos, 1.0); - fClipDistance = dot(worldPos, vClipPlane); -#endif -} \ No newline at end of file diff --git a/babylonjs-editor.d.ts b/babylonjs-editor.d.ts index b9b14bb57..1d0c39fc8 100644 --- a/babylonjs-editor.d.ts +++ b/babylonjs-editor.d.ts @@ -13,6 +13,7 @@ declare module 'babylonjs-editor' { import Request from 'babylonjs-editor/editor/tools/request'; import UndoRedo from 'babylonjs-editor/editor/tools/undo-redo'; import ThemeSwitcher, { ThemeType } from 'babylonjs-editor/editor/tools/theme'; + import GraphicsTools from 'babylonjs-editor/editor/tools/graphics-tools'; import Layout from 'babylonjs-editor/editor/gui/layout'; import Toolbar from 'babylonjs-editor/editor/gui/toolbar'; import List from 'babylonjs-editor/editor/gui/list'; @@ -37,9 +38,11 @@ declare module 'babylonjs-editor' { import ScenePreview from 'babylonjs-editor/editor/scene/scene-preview'; import PrefabAssetComponent from 'babylonjs-editor/editor/prefabs/asset-component'; import { Prefab, PrefabNodeType } from 'babylonjs-editor/editor/prefabs/prefab'; + import ParticlesCreatorExtension, { ParticlesCreatorMetadata } from 'babylonjs-editor/editor/particles/asset-component'; + import Storage from 'babylonjs-editor/editor/storage/storage'; import VSCodeSocket from 'babylonjs-editor/editor/vscode/vscode-socket'; export default Editor; - export { Editor, Tools, Request, UndoRedo, ThemeSwitcher, ThemeType, IStringDictionary, INumberDictionary, IDisposable, EditorPlugin, Layout, Toolbar, List, Grid, GridRow, Picker, Graph, GraphNode, Window, CodeEditor, Form, Edition, Tree, TreeContextMenuItem, TreeNode, Dialog, ContextMenu, ContextMenuItem, ResizableLayout, ComponentConfig, ItemConfigType, AbstractEditionTool, ProjectRoot, CodeProjectEditorFactory, SceneManager, SceneFactory, ScenePreview, PrefabAssetComponent, Prefab, PrefabNodeType, VSCodeSocket }; + export { Editor, Tools, Request, UndoRedo, ThemeSwitcher, ThemeType, GraphicsTools, IStringDictionary, INumberDictionary, IDisposable, EditorPlugin, Layout, Toolbar, List, Grid, GridRow, Picker, Graph, GraphNode, Window, CodeEditor, Form, Edition, Tree, TreeContextMenuItem, TreeNode, Dialog, ContextMenu, ContextMenuItem, ResizableLayout, ComponentConfig, ItemConfigType, AbstractEditionTool, ProjectRoot, CodeProjectEditorFactory, SceneManager, SceneFactory, ScenePreview, PrefabAssetComponent, Prefab, PrefabNodeType, ParticlesCreatorExtension, ParticlesCreatorMetadata, Storage, VSCodeSocket }; } declare module 'babylonjs-editor/editor/editor' { @@ -276,6 +279,11 @@ declare module 'babylonjs-editor/editor/tools/tools' { * @param sources One or more source objects from which to copy properties */ static Assign(target: Object, ...sources: Object[]): T; + /** + * Deep clones the given object. Take care of cycling objects! + * @param data the data of the object to clone + */ + static Clone(data: T): T; /** * Reads the given file * @param file the file to read @@ -400,6 +408,22 @@ declare module 'babylonjs-editor/editor/tools/theme' { } } +declare module 'babylonjs-editor/editor/tools/graphics-tools' { + import { BaseTexture } from 'babylonjs'; + export default class GraphicsTools { + /** + * Configures the given texture to retrieve its pixels and create a new file (blob) + * @param tex the texture to transform to a blob + */ + static TextureToFile(tex: BaseTexture): Promise; + /** + * Converts the given canvas data to blob + * @param canvas the canvas to take its data and convert to a blob + */ + static CanvasToBlob(canvas: HTMLCanvasElement): Promise; + } +} + declare module 'babylonjs-editor/editor/gui/layout' { export default class Layout { element: W2UI.W2Layout; @@ -921,7 +945,7 @@ declare module 'babylonjs-editor/editor/gui/form' { } declare module 'babylonjs-editor/editor/gui/edition' { - import { Color3, Color4, Vector2, Vector3, Vector4, BaseTexture } from 'babylonjs'; + import { Color3, Color4, Vector2, Vector3, Vector4, BaseTexture, Scene } from 'babylonjs'; import * as dat from 'dat-gui'; import Editor from 'babylonjs-editor/editor/editor'; export default class Edition { @@ -1002,7 +1026,7 @@ declare module 'babylonjs-editor/editor/gui/edition' { * @param object the object which has a texture * @param callback: called when changed texture */ - addTexture(parent: dat.GUI, editor: Editor, property: string, object: any, allowCubes?: boolean, onlyCubes?: boolean, callback?: (texture: BaseTexture) => void): dat.GUIController; + addTexture(parent: dat.GUI, editor: Editor, scene: Scene, property: string, object: any, allowCubes?: boolean, onlyCubes?: boolean, callback?: (texture: BaseTexture) => void): dat.GUIController; } } @@ -1893,6 +1917,152 @@ declare module 'babylonjs-editor/editor/prefabs/prefab' { } } +declare module 'babylonjs-editor/editor/particles/asset-component' { + import { AbstractMesh, PickingInfo } from 'babylonjs'; + import { IAssetComponent, AssetElement } from 'babylonjs-editor/extensions/typings/asset'; + import Editor from 'babylonjs-editor/editor/editor'; + export interface ParticlesCreatorMetadata { + name: string; + psData: any; + } + export default class ParticlesAssetComponent implements IAssetComponent { + editor: Editor; + id: string; + assetsCaption: string; + datas: AssetElement[]; + /** + * Constructor + * @param scene: the babylonjs scene + */ + constructor(editor: Editor); + /** + * On the user renames the asset + * @param asset the asset being renamed + * @param name the new name of the asset + */ + onRenameAsset(asset: AssetElement, name: string): void; + /** + * On the user wants to remove the asset + * @param asset the asset to remove + */ + onRemoveAsset(asset: AssetElement): void; + /** + * On the user adds an asset + * @param asset the asset to add + */ + onAddAsset(asset: AssetElement): void; + /** + * Creates a new particle systems set asset + */ + onCreateAsset(name: string): Promise>; + /** + * On get all the assets to be drawn in the assets component + */ + onGetAssets(): AssetElement[]; + /** + * On the user double clicks on asset + * @param asset the asset being double-clicked by the user + */ + onDoubleClickAsset(asset: AssetElement): void; + /** + * On the user drops an asset in the scene + * @param targetMesh the mesh under the pointer + * @param asset the asset being dropped + * @param pickInfo the pick info once the user dropped the asset + */ + onDragAndDropAsset(targetMesh: AbstractMesh, asset: AssetElement, pickInfo: PickingInfo): void; + /** + * Called by the editor when serializing the scene + */ + onSerializeAssets(): AssetElement[]; + /** + * On the user loads the editor project + * @param data the previously saved data + */ + onParseAssets(data: AssetElement[]): void; + } +} + +declare module 'babylonjs-editor/editor/storage/storage' { + import Editor from 'babylonjs-editor/editor/editor'; + import Picker from 'babylonjs-editor/editor/gui/picker'; + export type FileType = string | Uint8Array | ArrayBuffer; + export interface CreateFiles { + name: string; + data?: FileType | Promise; + file?: File; + folder?: CreateFiles[]; + doNotOverride?: boolean; + } + export interface GetFiles { + name: string; + folder: any; + } + export default abstract class Storage { + editor: Editor; + picker: Picker; + onCreateFiles: (folder: string) => void; + protected filesCount: number; + protected _uploadedCount: number; + /** + * Returns the appropriate storage (OneDrive, Electron, etc.) + * @param editor the editor reference + */ + static GetStorage(editor: Editor): Promise; + /** + * Constructor + * @param editor: the editor reference + */ + constructor(editor: Editor); + /** + * Opens the folder picker + * @param title the title of the picker + * @param filesToWrite the array of files to write on the HDD + * @param folder the current working directory to browse + * @param overrideFilename if the file browser should override the filename + */ + openPicker(title: string, filesToWrite: CreateFiles[], folder?: string, overrideFilename?: boolean): Promise; + /** + * Uploads the files + * @param folder the target folder + * @param filesToWrite the files to upload + */ + protected uploadFiles(folder: string, filesToWrite: CreateFiles[]): Promise; + /** + * Recursively creates the given files (uncluding folders) + * @param folder: the parent folder of the files + * @param files files to create + */ + protected recursivelyCreateFiles(folder: any, files: CreateFiles[]): Promise; + /** + * Returns the number of files to upload + * @param files the files to count + */ + protected recursivelyGetFilesToUploadCount(files: CreateFiles[]): number; + /** + * Returns the number of uploaded files + */ + protected uploadedCount: number; + /** + * Creates the given folders + * @param folder the parent folder + * @param names the folders names + */ + abstract createFolders(folder: any, names: string[]): Promise; + /** + * Creates the given files + * @param folder the parent folder + * @param files the files to write + */ + abstract createFiles(folder: any, files: CreateFiles[]): Promise; + /** + * Returns the files available in the given folder + * @param folder the parent folder + */ + abstract getFiles(folder?: any): Promise; + } +} + declare module 'babylonjs-editor/editor/vscode/vscode-socket' { import Editor from 'babylonjs-editor/editor/editor'; export default class VSCodeSocket { @@ -1973,13 +2143,8 @@ declare module 'babylonjs-editor/editor/core' { onResize: Observable<{}>; onAddObject: Observable<{}>; onRemoveObject: Observable<{}>; - onGlobalPropertyChange: Observable<{ - baseObject?: any; - object: any; - property: string; - value: any; - initialValue: any; - }>; + onModifyingObject: Observable<{}>; + onModifiedObject: Observable<{}>; onDropFiles: Observable<{ target: HTMLElement; files: FileList; @@ -2300,6 +2465,7 @@ declare module 'babylonjs-editor/editor/components/assets' { import Toolbar from 'babylonjs-editor/editor/gui/toolbar'; import { IAssetComponent, AssetElement } from 'babylonjs-editor/extensions/typings/asset'; import PrefabAssetComponent from 'babylonjs-editor/editor/prefabs/asset-component'; + import ParticlesAssetComponent from 'babylonjs-editor/editor/particles/asset-component'; import { IStringDictionary } from 'babylonjs-editor/editor/typings/typings'; export interface AssetPreviewData { asset: AssetElement; @@ -2314,6 +2480,7 @@ declare module 'babylonjs-editor/editor/components/assets' { toolbar: Toolbar; components: IAssetComponent[]; prefabs: PrefabAssetComponent; + particles: ParticlesAssetComponent; assetPreviewDatas: AssetPreviewData[]; protected currentComponent: IAssetComponent; protected emptyTextNode: HTMLHeadElement; @@ -2542,10 +2709,6 @@ declare module 'babylonjs-editor/editor/scene/scene-icons' { * @param editor: the editor instance */ constructor(editor: Editor); - /** - * On before render the scene - */ - onPreUpdate(): void; /** * On post update the scenes */ diff --git a/css/editor.css b/css/editor.css index 686817360..49232d944 100644 --- a/css/editor.css +++ b/css/editor.css @@ -138,6 +138,10 @@ html, body { z-index: 0; } +.menu { + overflow: hidden !important; +} + /* JSTREE */ .vakata-context li > a { font-size: 12px; diff --git a/src/editor/components/assets.ts b/src/editor/components/assets.ts index 0090093c5..f909feb23 100644 --- a/src/editor/components/assets.ts +++ b/src/editor/components/assets.ts @@ -12,6 +12,7 @@ import Toolbar from '../gui/toolbar'; import { IAssetComponent, AssetElement } from '../../extensions/typings/asset'; import PrefabAssetComponent from '../prefabs/asset-component'; +import ParticlesAssetComponent from '../particles/asset-component'; import { Dialog } from 'babylonjs-editor'; import VSCodeSocket from '../vscode/vscode-socket'; @@ -33,6 +34,8 @@ export default class EditorAssets { public components: IAssetComponent[] = []; public prefabs: PrefabAssetComponent; + public particles: ParticlesAssetComponent; + public assetPreviewDatas: AssetPreviewData[] = []; // Protected members @@ -84,6 +87,7 @@ export default class EditorAssets { // Create components this.prefabs = new PrefabAssetComponent(editor); + this.particles = new ParticlesAssetComponent(editor); // Add components tabs this.addDefaultComponents(); @@ -119,6 +123,7 @@ export default class EditorAssets { */ public addDefaultComponents (): void { this.addTab(this.prefabs); + this.addTab(this.particles); } /** diff --git a/src/editor/components/inspector.ts b/src/editor/components/inspector.ts index 2e834389e..ad0a2c24a 100644 --- a/src/editor/components/inspector.ts +++ b/src/editor/components/inspector.ts @@ -9,7 +9,7 @@ import NodeTool from '../edition-tools/node-tool'; import LightTool from '../edition-tools/light-tool'; import PhysicsTool from '../edition-tools/physics-tool'; import RenderTargetTool from '../edition-tools/render-target-tool'; -import ParticleSystemTool from '../edition-tools/particle-system-tool'; +import ParticleSystemTool from '../edition-tools/particles/particle-system-tool'; import SoundTool from '../edition-tools/sound-tool'; import AnimationTool from '../edition-tools/animation-tool'; @@ -210,12 +210,18 @@ export default class EditorInspector { if (t.divId === this.lastTabName) lastTool = t; + // On change + t.tool.onChange(t.tool.element, (property, result, object, initialValue) => { + this.editor.core.onModifyingObject.notifyObservers(this.currentObject); + }); + // Manage undo / redo t.tool.onFinishChange(t.tool.element, (property, result, object, initialValue) => { UndoRedo.Push({ baseObject: t.object, property: property, to: result, from: initialValue, object: object }); Tags.AddTagsTo(t.object, 'modified'); this.editor.graph.updateObjectMark(t.object); t.onModified && t.onModified(); + this.editor.core.onModifiedObject.notifyObservers(this.currentObject); }); this.currentTools.push(t); diff --git a/src/editor/components/toolbar.ts b/src/editor/components/toolbar.ts index 2a8dfe3a0..38c9ecbfe 100644 --- a/src/editor/components/toolbar.ts +++ b/src/editor/components/toolbar.ts @@ -86,7 +86,7 @@ export default class EditorToolbar { { type: 'break' }, { id: 'material-editor', img: 'icon-shaders', text: 'Material Editor...' }, { id: 'post-process-editor', img: 'icon-shaders', text: 'Post-Process Editor...' }, - // { id: 'particles-creator', img: 'icon-particles', text: 'Particles Creator' }, + { id: 'particles-creator', img: 'icon-particles', text: 'Particles Creator' }, { type: 'break' }, { id: 'path-finder', img: 'icon-graph', text: 'Path Finder...' }, { type: 'break' }, diff --git a/src/editor/core.ts b/src/editor/core.ts index d7db0f238..6442f8a4e 100644 --- a/src/editor/core.ts +++ b/src/editor/core.ts @@ -33,7 +33,8 @@ export default class Core { public onResize: Observable<{ }> = new Observable<{ }>(); public onAddObject: Observable<{ }> = new Observable<{ }>(); public onRemoveObject: Observable<{ }> = new Observable<{ }>(); - public onGlobalPropertyChange = new Observable<{ baseObject?: any; object: any; property: string; value: any; initialValue: any; }>(); + public onModifyingObject: Observable<{ }> = new Observable<{ }>(); + public onModifiedObject: Observable<{ }> = new Observable<{ }>(); public onDropFiles = new Observable<{ target: HTMLElement; files: FileList }>(); public onSceneLoaded = new Observable<{ scene: Scene, file: File, project?: ProjectRoot }>(); diff --git a/src/editor/edition-tools/gui/image.ts b/src/editor/edition-tools/gui/image.ts index f9fe8c1fd..d3e57613b 100644 --- a/src/editor/edition-tools/gui/image.ts +++ b/src/editor/edition-tools/gui/image.ts @@ -54,7 +54,7 @@ export default class GuiImageTool extends AbstractEditionTool { const texture = this.tool.addFolder('Texture'); texture.open(); - this.tool.addTexture(texture, this.editor, '_texture', this, false, false, (tex) => { + this.tool.addTexture(texture, this.editor, this.editor.core.scene, '_texture', this, false, false, (tex) => { let blobURL = ''; try { blobURL = URL.createObjectURL(FilesInputStore.FilesToLoad[tex['url']]); diff --git a/src/editor/edition-tools/materials/cell-tool.ts b/src/editor/edition-tools/materials/cell-tool.ts index 4a64fd7c9..e3e35b3d9 100644 --- a/src/editor/edition-tools/materials/cell-tool.ts +++ b/src/editor/edition-tools/materials/cell-tool.ts @@ -27,7 +27,7 @@ export default class CellMaterialTool extends MaterialTool { diffuse.open(); this.tool.addColor(diffuse, 'Color', this.object.diffuseColor).open(); - this.tool.addTexture(diffuse, this.editor, 'diffuseTexture', this.object, false).name('Texture'); + this.tool.addTexture(diffuse, this.editor, this.editor.core.scene, 'diffuseTexture', this.object, false).name('Texture'); // Cell const cell = this.tool.addFolder('Fire'); diff --git a/src/editor/edition-tools/materials/custom-tool.ts b/src/editor/edition-tools/materials/custom-tool.ts index 03a251ec8..202e2ed97 100644 --- a/src/editor/edition-tools/materials/custom-tool.ts +++ b/src/editor/edition-tools/materials/custom-tool.ts @@ -1,75 +1,75 @@ -import { Material, Vector2, Vector3 } from 'babylonjs'; - -import CustomEditorMaterial from '../../../extensions/material-editor/material'; - -import MaterialTool from './material-tool'; -import Tools from '../../tools/tools'; - -export default class CustomMaterialTool extends MaterialTool { - // Public members - public divId: string = 'CUSTOM-MATERIAL-TOOL'; - public tabName: string = 'Custom Material'; - - /** - * Returns if the object is supported - * @param object the object selected in the graph - */ - public isSupported(object: any): boolean { - return super.isSupported(object) && this.object.getClassName && this.object.getClassName() === 'CustomMaterial'; - } - - /** - * Updates the edition tool - * @param object the object selected in the graph - */ - public update(object: any): void { - super.update(object); - this.setTabName('Custom Material'); - - // Get current config of the post-process - const config = this.object.config; - - // Base Color - this.tool.addColor(this.tool.element, 'Base Color', this.object.baseColor).open(); - - // Floats - const floats = this.tool.addFolder('Floats'); - floats.open(); - - config.floats.forEach(f => { - if (this.object.userConfig[f] === undefined) - this.object.userConfig[f] = 1; - - floats.add(this.object.userConfig, f).step(0.01).name(f).onChange(() => this.object.markAsDirty(Material.MiscDirtyFlag)); - }); - - // Vectors - const vectors = this.tool.addFolder('Vectors'); - vectors.open(); - - config.vectors2.forEach(v => { - if (!this.object.userConfig[v] || !(this.object.userConfig[v] instanceof Vector2)) - this.object.userConfig[v] = Vector2.Zero(); - - this.tool.addVector(vectors, v, this.object.userConfig[v], () => this.object.markAsDirty(Material.MiscDirtyFlag)).open(); - }); - - config.vectors3.forEach(v => { - if (!this.object.userConfig[v] || !(this.object.userConfig[v] instanceof Vector3)) - this.object.userConfig[v] = Vector3.Zero(); - - this.tool.addVector(vectors, v, this.object.userConfig[v], () => this.object.markAsDirty(Material.MiscDirtyFlag)).open(); - }); - - // Samplers - const samplers = this.tool.addFolder('Samplers'); - samplers.open(); - - config.textures.forEach(t => { - this.tool.addTexture(samplers, this.editor, t.name, this.object.userConfig, false, false, () => this.object.markAsDirty(Material.TextureDirtyFlag)).name(t.name); - }); - - // Options - super.addOptions(); - } -} +import { Material, Vector2, Vector3 } from 'babylonjs'; + +import CustomEditorMaterial from '../../../extensions/material-editor/material'; + +import MaterialTool from './material-tool'; +import Tools from '../../tools/tools'; + +export default class CustomMaterialTool extends MaterialTool { + // Public members + public divId: string = 'CUSTOM-MATERIAL-TOOL'; + public tabName: string = 'Custom Material'; + + /** + * Returns if the object is supported + * @param object the object selected in the graph + */ + public isSupported(object: any): boolean { + return super.isSupported(object) && this.object.getClassName && this.object.getClassName() === 'CustomMaterial'; + } + + /** + * Updates the edition tool + * @param object the object selected in the graph + */ + public update(object: any): void { + super.update(object); + this.setTabName('Custom Material'); + + // Get current config of the post-process + const config = this.object.config; + + // Base Color + this.tool.addColor(this.tool.element, 'Base Color', this.object.baseColor).open(); + + // Floats + const floats = this.tool.addFolder('Floats'); + floats.open(); + + config.floats.forEach(f => { + if (this.object.userConfig[f] === undefined) + this.object.userConfig[f] = 1; + + floats.add(this.object.userConfig, f).step(0.01).name(f).onChange(() => this.object.markAsDirty(Material.MiscDirtyFlag)); + }); + + // Vectors + const vectors = this.tool.addFolder('Vectors'); + vectors.open(); + + config.vectors2.forEach(v => { + if (!this.object.userConfig[v] || !(this.object.userConfig[v] instanceof Vector2)) + this.object.userConfig[v] = Vector2.Zero(); + + this.tool.addVector(vectors, v, this.object.userConfig[v], () => this.object.markAsDirty(Material.MiscDirtyFlag)).open(); + }); + + config.vectors3.forEach(v => { + if (!this.object.userConfig[v] || !(this.object.userConfig[v] instanceof Vector3)) + this.object.userConfig[v] = Vector3.Zero(); + + this.tool.addVector(vectors, v, this.object.userConfig[v], () => this.object.markAsDirty(Material.MiscDirtyFlag)).open(); + }); + + // Samplers + const samplers = this.tool.addFolder('Samplers'); + samplers.open(); + + config.textures.forEach(t => { + this.tool.addTexture(samplers, this.editor, this.editor.core.scene, t.name, this.object.userConfig, false, false, () => this.object.markAsDirty(Material.TextureDirtyFlag)).name(t.name); + }); + + // Options + super.addOptions(); + } +} diff --git a/src/editor/edition-tools/materials/fire-tool.ts b/src/editor/edition-tools/materials/fire-tool.ts index c9f7c6dd4..b3f18ba6b 100644 --- a/src/editor/edition-tools/materials/fire-tool.ts +++ b/src/editor/edition-tools/materials/fire-tool.ts @@ -28,15 +28,15 @@ export default class FireMaterialTool extends MaterialTool { diffuse.open(); this.tool.addColor(diffuse, 'Color', this.object.diffuseColor).open(); - this.tool.addTexture(diffuse, this.editor, 'diffuseTexture', this.object, false).name('Texture'); + this.tool.addTexture(diffuse, this.editor, this.editor.core.scene, 'diffuseTexture', this.object, false).name('Texture'); // Fire const fire = this.tool.addFolder('Fire'); fire.open(); fire.add(this.object, 'speed').min(0).step(0.01).name('Speed'); - this.tool.addTexture(fire, this.editor, 'distortionTexture', this.object, false).name('Distortion') - this.tool.addTexture(fire, this.editor, 'opacityTexture', this.object, false).name('Opacity'); + this.tool.addTexture(fire, this.editor, this.editor.core.scene, 'distortionTexture', this.object, false).name('Distortion') + this.tool.addTexture(fire, this.editor, this.editor.core.scene, 'opacityTexture', this.object, false).name('Opacity'); // Options super.addOptions(); diff --git a/src/editor/edition-tools/materials/fur-tool.ts b/src/editor/edition-tools/materials/fur-tool.ts index f3a649c38..77ce8ee75 100644 --- a/src/editor/edition-tools/materials/fur-tool.ts +++ b/src/editor/edition-tools/materials/fur-tool.ts @@ -28,7 +28,7 @@ export default class FurMaterialTool extends MaterialTool { diffuse.open(); this.tool.addColor(diffuse, 'Color', this.object.diffuseColor, () => this.object.updateFur()).open(); - this.tool.addTexture(diffuse, this.editor, 'diffuseTexture', this.object, false, false, () => this.object.updateFur()).name('Texture'); + this.tool.addTexture(diffuse, this.editor, this.editor.core.scene, 'diffuseTexture', this.object, false, false, () => this.object.updateFur()).name('Texture'); // Fur const fur = this.tool.addFolder('Fur'); diff --git a/src/editor/edition-tools/materials/lava-tool.ts b/src/editor/edition-tools/materials/lava-tool.ts index 4882ee8f6..7a1c37bdc 100644 --- a/src/editor/edition-tools/materials/lava-tool.ts +++ b/src/editor/edition-tools/materials/lava-tool.ts @@ -28,13 +28,13 @@ export default class LavaMaterialTool extends MaterialTool { diffuse.open(); this.tool.addColor(diffuse, 'Color', this.object.diffuseColor).open(); - this.tool.addTexture(diffuse, this.editor, 'diffuseTexture', this.object, false).name('Texture'); + this.tool.addTexture(diffuse, this.editor, this.editor.core.scene, 'diffuseTexture', this.object, false).name('Texture'); // Lava const lava = this.tool.addFolder('Lava'); lava.open(); - this.tool.addTexture(lava, this.editor, 'noiseTexture', this.object, false).name('Noise'); + this.tool.addTexture(lava, this.editor, this.editor.core.scene, 'noiseTexture', this.object, false).name('Noise'); lava.add(this.object, 'movingSpeed').min(0).name('Moving Speed'); lava.add(this.object, 'lowFrequencySpeed').min(0).name('Low Frequency Speed'); diff --git a/src/editor/edition-tools/materials/mix-tool.ts b/src/editor/edition-tools/materials/mix-tool.ts index 3fa4c7aa0..9668baad7 100644 --- a/src/editor/edition-tools/materials/mix-tool.ts +++ b/src/editor/edition-tools/materials/mix-tool.ts @@ -26,21 +26,21 @@ export default class MixMaterialTool extends MaterialTool { const mixmap1 = this.tool.addFolder('Mix Map 1'); mixmap1.open(); - this.tool.addTexture(mixmap1, this.editor, 'mixTexture1', this.object, false).name('Mix Texture 1'); - this.tool.addTexture(mixmap1, this.editor, 'diffuseTexture1', this.object, false).name('Diffuse Texture 1'); - this.tool.addTexture(mixmap1, this.editor, 'diffuseTexture2', this.object, false).name('Diffuse Texture 2'); - this.tool.addTexture(mixmap1, this.editor, 'diffuseTexture3', this.object, false).name('Diffuse Texture 3'); - this.tool.addTexture(mixmap1, this.editor, 'diffuseTexture4', this.object, false).name('Diffuse Texture 4'); + this.tool.addTexture(mixmap1, this.editor, this.editor.core.scene, 'mixTexture1', this.object, false).name('Mix Texture 1'); + this.tool.addTexture(mixmap1, this.editor, this.editor.core.scene, 'diffuseTexture1', this.object, false).name('Diffuse Texture 1'); + this.tool.addTexture(mixmap1, this.editor, this.editor.core.scene, 'diffuseTexture2', this.object, false).name('Diffuse Texture 2'); + this.tool.addTexture(mixmap1, this.editor, this.editor.core.scene, 'diffuseTexture3', this.object, false).name('Diffuse Texture 3'); + this.tool.addTexture(mixmap1, this.editor, this.editor.core.scene, 'diffuseTexture4', this.object, false).name('Diffuse Texture 4'); // Mix map 2 const mixmap2 = this.tool.addFolder('Mix Map 2'); mixmap2.open(); - this.tool.addTexture(mixmap2, this.editor, 'mixTexture2', this.object, false).name('Mix Texture 2'); - this.tool.addTexture(mixmap2, this.editor, 'diffuseTexture5', this.object, false).name('Diffuse Texture 5'); - this.tool.addTexture(mixmap2, this.editor, 'diffuseTexture6', this.object, false).name('Diffuse Texture 6'); - this.tool.addTexture(mixmap2, this.editor, 'diffuseTexture7', this.object, false).name('Diffuse Texture 7'); - this.tool.addTexture(mixmap2, this.editor, 'diffuseTexture8', this.object, false).name('Diffuse Texture 8'); + this.tool.addTexture(mixmap2, this.editor, this.editor.core.scene, 'mixTexture2', this.object, false).name('Mix Texture 2'); + this.tool.addTexture(mixmap2, this.editor, this.editor.core.scene, 'diffuseTexture5', this.object, false).name('Diffuse Texture 5'); + this.tool.addTexture(mixmap2, this.editor, this.editor.core.scene, 'diffuseTexture6', this.object, false).name('Diffuse Texture 6'); + this.tool.addTexture(mixmap2, this.editor, this.editor.core.scene, 'diffuseTexture7', this.object, false).name('Diffuse Texture 7'); + this.tool.addTexture(mixmap2, this.editor, this.editor.core.scene, 'diffuseTexture8', this.object, false).name('Diffuse Texture 8'); // Options super.addOptions(); diff --git a/src/editor/edition-tools/materials/pbr-metallic-roughness-tool.ts b/src/editor/edition-tools/materials/pbr-metallic-roughness-tool.ts index cd55f09f0..75e8ded9a 100644 --- a/src/editor/edition-tools/materials/pbr-metallic-roughness-tool.ts +++ b/src/editor/edition-tools/materials/pbr-metallic-roughness-tool.ts @@ -1,73 +1,73 @@ -import { PBRMetallicRoughnessMaterial } from 'babylonjs'; - -import MaterialTool from './material-tool'; -import Tools from '../../tools/tools'; - -export default class PBRMetallicRoughnessMaterialTool extends MaterialTool { - // Public members - public divId: string = 'PBR-METALLIC-ROUGHNESS-MATERIAL-TOOL'; - public tabName: string = 'PBR Metallic Roughness'; - - /** - * Returns if the object is supported - * @param object the object selected in the graph - */ - public isSupported(object: any): boolean { - return super.isSupported(object) && this.object instanceof PBRMetallicRoughnessMaterial; - } - - /** - * Updates the edition tool - * @param object the object selected in the graph - */ - public update(object: any): void { - super.update(object); - - // Base Color - const baseColor = this.tool.addFolder('Base'); - baseColor.open(); - - this.tool.addColor(baseColor, 'Base Color', this.object.baseColor).open(); - this.tool.addTexture(baseColor, this.editor, 'baseTexture', this.object, false).name('Texture'); - - // Bump - const normal = this.tool.addFolder('Normal'); - normal.open(); - this.tool.addTexture(normal, this.editor, 'normalTexture', this.object).name('Normal Texture'); - normal.add(this.object, 'invertNormalMapX').name('Invert Normal Map X'); - normal.add(this.object, 'invertNormalMapY').name('Invert Normal Map Y'); - - // Reflection - const reflection = this.tool.addFolder('Reflection'); - reflection.open(); - this.tool.addTexture(reflection, this.editor, 'environmentTexture', this.object, true, false).name('Environment Texture'); - - // Metallic Roughness - const metallic = this.tool.addFolder('Metallic Roughness'); - metallic.open(); - metallic.add(this.object, 'roughness').step(0.01).name('Roughness'); - metallic.add(this.object, 'metallic').step(0.01).name('Metallic'); - this.tool.addTexture(metallic, this.editor, 'metallicRoughnessTexture', this.object).name('Metallic Roughness Texture'); - - // Emissive - const emissive = this.tool.addFolder('Emissive'); - emissive.open(); - this.tool.addColor(emissive, 'Color', this.object.emissiveColor).open(); - this.tool.addTexture(emissive, this.editor, 'emissiveTexture', this.object).name('Emissive Texture'); - - // Lightmap - const lightmap = this.tool.addFolder('Lightmap'); - lightmap.open(); - lightmap.add(this.object, 'useLightmapAsShadowmap').name('Use Lightmap As Shadowmap'); - this.tool.addTexture(lightmap, this.editor, 'lightmapTexture', this.object).name('Lightmap Texture'); - - // Occlusion - const occlusion = this.tool.addFolder('Occlusion'); - occlusion.open(); - occlusion.add(this.object, 'occlusionStrength').name('Occlusion Strength'); - this.tool.addTexture(occlusion, this.editor, 'occlusionTexture', this.object).name('Occlusion Texture'); - - // Options - super.addOptions(); - } -} +import { PBRMetallicRoughnessMaterial } from 'babylonjs'; + +import MaterialTool from './material-tool'; +import Tools from '../../tools/tools'; + +export default class PBRMetallicRoughnessMaterialTool extends MaterialTool { + // Public members + public divId: string = 'PBR-METALLIC-ROUGHNESS-MATERIAL-TOOL'; + public tabName: string = 'PBR Metallic Roughness'; + + /** + * Returns if the object is supported + * @param object the object selected in the graph + */ + public isSupported(object: any): boolean { + return super.isSupported(object) && this.object instanceof PBRMetallicRoughnessMaterial; + } + + /** + * Updates the edition tool + * @param object the object selected in the graph + */ + public update(object: any): void { + super.update(object); + + // Base Color + const baseColor = this.tool.addFolder('Base'); + baseColor.open(); + + this.tool.addColor(baseColor, 'Base Color', this.object.baseColor).open(); + this.tool.addTexture(baseColor, this.editor, this.editor.core.scene, 'baseTexture', this.object, false).name('Texture'); + + // Bump + const normal = this.tool.addFolder('Normal'); + normal.open(); + this.tool.addTexture(normal, this.editor, this.editor.core.scene, 'normalTexture', this.object).name('Normal Texture'); + normal.add(this.object, 'invertNormalMapX').name('Invert Normal Map X'); + normal.add(this.object, 'invertNormalMapY').name('Invert Normal Map Y'); + + // Reflection + const reflection = this.tool.addFolder('Reflection'); + reflection.open(); + this.tool.addTexture(reflection, this.editor, this.editor.core.scene, 'environmentTexture', this.object, true, false).name('Environment Texture'); + + // Metallic Roughness + const metallic = this.tool.addFolder('Metallic Roughness'); + metallic.open(); + metallic.add(this.object, 'roughness').step(0.01).name('Roughness'); + metallic.add(this.object, 'metallic').step(0.01).name('Metallic'); + this.tool.addTexture(metallic, this.editor, this.editor.core.scene, 'metallicRoughnessTexture', this.object).name('Metallic Roughness Texture'); + + // Emissive + const emissive = this.tool.addFolder('Emissive'); + emissive.open(); + this.tool.addColor(emissive, 'Color', this.object.emissiveColor).open(); + this.tool.addTexture(emissive, this.editor, this.editor.core.scene, 'emissiveTexture', this.object).name('Emissive Texture'); + + // Lightmap + const lightmap = this.tool.addFolder('Lightmap'); + lightmap.open(); + lightmap.add(this.object, 'useLightmapAsShadowmap').name('Use Lightmap As Shadowmap'); + this.tool.addTexture(lightmap, this.editor, this.editor.core.scene, 'lightmapTexture', this.object).name('Lightmap Texture'); + + // Occlusion + const occlusion = this.tool.addFolder('Occlusion'); + occlusion.open(); + occlusion.add(this.object, 'occlusionStrength').name('Occlusion Strength'); + this.tool.addTexture(occlusion, this.editor, this.editor.core.scene, 'occlusionTexture', this.object).name('Occlusion Texture'); + + // Options + super.addOptions(); + } +} diff --git a/src/editor/edition-tools/materials/pbr-specular-glossiness-tool.ts b/src/editor/edition-tools/materials/pbr-specular-glossiness-tool.ts index 7f472e83f..e67fece6f 100644 --- a/src/editor/edition-tools/materials/pbr-specular-glossiness-tool.ts +++ b/src/editor/edition-tools/materials/pbr-specular-glossiness-tool.ts @@ -1,71 +1,71 @@ -import { PBRSpecularGlossinessMaterial } from 'babylonjs'; - -import MaterialTool from './material-tool'; -import Tools from '../../tools/tools'; - -export default class PBRSpecularGlossinessMaterialTool extends MaterialTool { - // Public members - public divId: string = 'PBR-SPECULAR-GLOSSINESS-MATERIAL-TOOL'; - public tabName: string = 'PBR Specular Glossiness'; - - /** - * Returns if the object is supported - * @param object the object selected in the graph - */ - public isSupported(object: any): boolean { - return super.isSupported(object) && this.object instanceof PBRSpecularGlossinessMaterial; - } - - /** - * Updates the edition tool - * @param object the object selected in the graph - */ - public update(object: any): void { - super.update(object); - - // Diffuse Color - const diffuseColor = this.tool.addFolder('Diffuse'); - diffuseColor.open(); - this.tool.addColor(diffuseColor, 'Color', this.object.diffuseColor).open(); - this.tool.addTexture(diffuseColor, this.editor, 'diffuseTexture', this.object, false).name('Texture'); - - // Bump - const normal = this.tool.addFolder('Normal'); - normal.open(); - this.tool.addTexture(normal, this.editor, 'normalTexture', this.object).name('Normal Texture'); - normal.add(this.object, 'invertNormalMapX').name('Invert Normal Map X'); - normal.add(this.object, 'invertNormalMapY').name('Invert Normal Map Y'); - - // Reflection - const reflection = this.tool.addFolder('Reflection'); - reflection.open(); - this.tool.addTexture(reflection, this.editor, 'environmentTexture', this.object, true, false).name('Environment Texture'); - - // Specular Roughness - const glossiness = this.tool.addFolder('Glossiness'); - glossiness.open(); - glossiness.add(this.object, 'glossiness').step(0.01).name('Glossiness'); - this.tool.addTexture(glossiness, this.editor, 'specularGlossinessTexture', this.object).name('Specular Glossiness Texture'); - - // Emissive - const emissive = this.tool.addFolder('Emissive'); - emissive.open(); - this.tool.addColor(emissive, 'Color', this.object.emissiveColor).open(); - this.tool.addTexture(emissive, this.editor, 'emissiveTexture', this.object).name('Emissive Texture'); - - // Lightmap - const lightmap = this.tool.addFolder('Lightmap'); - lightmap.open(); - lightmap.add(this.object, 'useLightmapAsShadowmap').name('Use Lightmap As Shadowmap'); - this.tool.addTexture(lightmap, this.editor, 'lightmapTexture', this.object).name('Lightmap Texture'); - - // Occlusion - const occlusion = this.tool.addFolder('Occlusion'); - occlusion.open(); - occlusion.add(this.object, 'occlusionStrength').name('Occlusion Strength'); - this.tool.addTexture(occlusion, this.editor, 'occlusionTexture', this.object).name('Occlusion Texture'); - - // Options - super.addOptions(); - } -} +import { PBRSpecularGlossinessMaterial } from 'babylonjs'; + +import MaterialTool from './material-tool'; +import Tools from '../../tools/tools'; + +export default class PBRSpecularGlossinessMaterialTool extends MaterialTool { + // Public members + public divId: string = 'PBR-SPECULAR-GLOSSINESS-MATERIAL-TOOL'; + public tabName: string = 'PBR Specular Glossiness'; + + /** + * Returns if the object is supported + * @param object the object selected in the graph + */ + public isSupported(object: any): boolean { + return super.isSupported(object) && this.object instanceof PBRSpecularGlossinessMaterial; + } + + /** + * Updates the edition tool + * @param object the object selected in the graph + */ + public update(object: any): void { + super.update(object); + + // Diffuse Color + const diffuseColor = this.tool.addFolder('Diffuse'); + diffuseColor.open(); + this.tool.addColor(diffuseColor, 'Color', this.object.diffuseColor).open(); + this.tool.addTexture(diffuseColor, this.editor, this.editor.core.scene, 'diffuseTexture', this.object, false).name('Texture'); + + // Bump + const normal = this.tool.addFolder('Normal'); + normal.open(); + this.tool.addTexture(normal, this.editor, this.editor.core.scene, 'normalTexture', this.object).name('Normal Texture'); + normal.add(this.object, 'invertNormalMapX').name('Invert Normal Map X'); + normal.add(this.object, 'invertNormalMapY').name('Invert Normal Map Y'); + + // Reflection + const reflection = this.tool.addFolder('Reflection'); + reflection.open(); + this.tool.addTexture(reflection, this.editor, this.editor.core.scene, 'environmentTexture', this.object, true, false).name('Environment Texture'); + + // Specular Roughness + const glossiness = this.tool.addFolder('Glossiness'); + glossiness.open(); + glossiness.add(this.object, 'glossiness').step(0.01).name('Glossiness'); + this.tool.addTexture(glossiness, this.editor, this.editor.core.scene, 'specularGlossinessTexture', this.object).name('Specular Glossiness Texture'); + + // Emissive + const emissive = this.tool.addFolder('Emissive'); + emissive.open(); + this.tool.addColor(emissive, 'Color', this.object.emissiveColor).open(); + this.tool.addTexture(emissive, this.editor, this.editor.core.scene, 'emissiveTexture', this.object).name('Emissive Texture'); + + // Lightmap + const lightmap = this.tool.addFolder('Lightmap'); + lightmap.open(); + lightmap.add(this.object, 'useLightmapAsShadowmap').name('Use Lightmap As Shadowmap'); + this.tool.addTexture(lightmap, this.editor, this.editor.core.scene, 'lightmapTexture', this.object).name('Lightmap Texture'); + + // Occlusion + const occlusion = this.tool.addFolder('Occlusion'); + occlusion.open(); + occlusion.add(this.object, 'occlusionStrength').name('Occlusion Strength'); + this.tool.addTexture(occlusion, this.editor, this.editor.core.scene, 'occlusionTexture', this.object).name('Occlusion Texture'); + + // Options + super.addOptions(); + } +} diff --git a/src/editor/edition-tools/materials/pbr-tool.ts b/src/editor/edition-tools/materials/pbr-tool.ts index f26b3b6e9..2a4995b54 100644 --- a/src/editor/edition-tools/materials/pbr-tool.ts +++ b/src/editor/edition-tools/materials/pbr-tool.ts @@ -42,12 +42,12 @@ export default class PBRMaterialTool extends MaterialTool { // Albedo const albedo = this.tool.addFolder('Albedo'); albedo.open(); - this.tool.addTexture(albedo, this.editor, 'albedoTexture', this.object).name('Albedo Texture'); + this.tool.addTexture(albedo, this.editor, this.editor.core.scene, 'albedoTexture', this.object).name('Albedo Texture'); this.tool.addColor(albedo, 'Color', this.object.albedoColor).open(); // Bump const bump = this.tool.addFolder('Bump'); - this.tool.addTexture(bump, this.editor, 'bumpTexture', this.object).name('Bump Texture'); + this.tool.addTexture(bump, this.editor, this.editor.core.scene, 'bumpTexture', this.object).name('Bump Texture'); bump.add(this.object, 'invertNormalMapX').name('Invert Normal Map X'); bump.add(this.object, 'invertNormalMapY').name('Invert Normal Map Y'); bump.add(this.object, 'useParallax').name('Use Parallax'); @@ -57,19 +57,19 @@ export default class PBRMaterialTool extends MaterialTool { // Reflectivity const reflectivity = this.tool.addFolder('Reflectivity'); reflectivity.open(); - this.tool.addTexture(reflectivity, this.editor, 'reflectivityTexture', this.object).name('Reflectivity Texture'); + this.tool.addTexture(reflectivity, this.editor, this.editor.core.scene, 'reflectivityTexture', this.object).name('Reflectivity Texture'); this.tool.addColor(reflectivity, 'Color', this.object.reflectivityColor).open(); // Reflection const reflection = this.tool.addFolder('Reflection'); reflection.open(); - this.tool.addTexture(reflection, this.editor, 'reflectionTexture', this.object, true, false).name('Reflection Texture'); + this.tool.addTexture(reflection, this.editor, this.editor.core.scene, 'reflectionTexture', this.object, true, false).name('Reflection Texture'); this.tool.addColor(reflection, 'Color', this.object.reflectionColor).open(); reflection.add(this.object, 'environmentIntensity').step(0.01).name('Environment Intensity'); // Microsurface const micro = this.tool.addFolder('Micro Surface'); - this.tool.addTexture(micro, this.editor, 'microSurfaceTexture', this.object, false).name('Micro Surface Texture'); + this.tool.addTexture(micro, this.editor, this.editor.core.scene, 'microSurfaceTexture', this.object, false).name('Micro Surface Texture'); micro.add(this.object, 'microSurface').min(0).max(1).name('Micro Surface'); micro.add(this.object, 'useAutoMicroSurfaceFromReflectivityMap').name('Use Auto Micro Surface From Reflectivity Map'); micro.add(this.object, 'useMicroSurfaceFromReflectivityMapAlpha').name('Use Micro Surface From Reflectivity Map Alpha'); @@ -79,7 +79,7 @@ export default class PBRMaterialTool extends MaterialTool { metallic.add(this.object, 'useMetallnessFromMetallicTextureBlue').name('Metallness From Metallic Texture Blue'); metallic.add(this.object, 'useRoughnessFromMetallicTextureAlpha').name('Use Roughness From Metallic Texture Alpha'); metallic.add(this.object, 'useRoughnessFromMetallicTextureGreen').name('Use Roughness From Metallic Texture Green'); - this.tool.addTexture(metallic, this.editor, 'metallicTexture', this.object, false, false, t => this.update(this.object)).name('Metallic Texture'); + this.tool.addTexture(metallic, this.editor, this.editor.core.scene, 'metallicTexture', this.object, false, false, t => this.update(this.object)).name('Metallic Texture'); const metallicWorkflow = metallic.addFolder('Metallic Workflow'); metallicWorkflow.open(); @@ -128,7 +128,7 @@ export default class PBRMaterialTool extends MaterialTool { // Sub surface const subSurface = this.tool.addFolder('Sub Surface'); this.tool.addColor(subSurface, 'Tint Color', this.object.subSurface.tintColor).open(); - this.tool.addTexture(subSurface.addFolder('Thickness Texture'), this.editor, 'thicknessTexture', this.object.subSurface, false); + this.tool.addTexture(subSurface.addFolder('Thickness Texture'), this.editor, this.editor.core.scene, 'thicknessTexture', this.object.subSurface, false); subSurface.add(this.object.subSurface, 'useMaskFromThicknessTexture').name('Use Mask From Thickness Texture'); // Sub surface Refraction @@ -149,7 +149,7 @@ export default class PBRMaterialTool extends MaterialTool { clearCoat.add(this.object.clearCoat, 'isEnabled').name('Clear Coat Enabled'); clearCoat.add(this.object.clearCoat, 'roughness').min(0).step(0.01).name('Roughness'); clearCoat.add(this.object.clearCoat, 'indiceOfRefraction').min(0).step(0.01).name('Indice Of Refraction'); - this.tool.addTexture(clearCoat.addFolder('Bump Texture'), this.editor, 'bumpTexture', this.object.clearCoat, false, false); + this.tool.addTexture(clearCoat.addFolder('Bump Texture'), this.editor, this.editor.core.scene, 'bumpTexture', this.object.clearCoat, false, false); clearCoat.add(this.object.clearCoat, 'isTintEnabled').name('Tint Enabled'); clearCoat.add(this.object.clearCoat, 'tintColorAtDistance').min(0).step(0.01).name('Tint Color At Distance'); @@ -160,14 +160,14 @@ export default class PBRMaterialTool extends MaterialTool { anisotropy.add(this.object.anisotropy, 'isEnabled').name('Anisotropy Enabled'); anisotropy.add(this.object.anisotropy, 'intensity').min(0).step(0.01).name('Intensity'); this.tool.addVector(anisotropy, 'Direction', this.object.anisotropy.direction); - this.tool.addTexture(anisotropy.addFolder('Texture'), this.editor, 'texture', this.object.anisotropy, false, false); + this.tool.addTexture(anisotropy.addFolder('Texture'), this.editor, this.editor.core.scene, 'texture', this.object.anisotropy, false, false); // Sheen const sheen = this.tool.addFolder('Sheen'); sheen.add(this.object.sheen, 'isEnabled').name('Sheen Enabled'); sheen.add(this.object.sheen, 'intensity').min(0).step(0.01).name('Intensity'); this.tool.addColor(sheen, 'Color', this.object.sheen.color); - this.tool.addTexture(sheen.addFolder('Texture'), this.editor, 'texture', this.object.sheen, false, false); + this.tool.addTexture(sheen.addFolder('Texture'), this.editor, this.editor.core.scene, 'texture', this.object.sheen, false, false); // Opacity const opacity = this.tool.addFolder('Opacity'); @@ -178,25 +178,25 @@ export default class PBRMaterialTool extends MaterialTool { const emissive = this.tool.addFolder('Emissive'); this.tool.addColor(emissive, 'Emissive', this.object.emissiveColor).open(); emissive.add(this.object, 'emissiveIntensity').step(0.01).name('Emissive Intensity'); - this.tool.addTexture(emissive, this.editor, 'emissiveTexture', this.object).name('Emissive Texture'); + this.tool.addTexture(emissive, this.editor, this.editor.core.scene, 'emissiveTexture', this.object).name('Emissive Texture'); // Ambient const ambient = this.tool.addFolder('Ambient'); this.tool.addColor(ambient, 'Ambient', this.object.ambientColor).open(); - this.tool.addTexture(ambient, this.editor, 'ambientTexture', this.object).name('Ambient Texture'); + this.tool.addTexture(ambient, this.editor, this.editor.core.scene, 'ambientTexture', this.object).name('Ambient Texture'); ambient.add(this.object, 'ambientTextureStrength').step(0.01).name('Ambient Texture Strength'); // Light map const lightmap = this.tool.addFolder('Lightmap'); lightmap.add(this.object, 'useLightmapAsShadowmap').name('Use Lightmap As Shadowmap'); - this.tool.addTexture(lightmap, this.editor, 'lightmapTexture', this.object).name('Lightmap Texture'); + this.tool.addTexture(lightmap, this.editor, this.editor.core.scene, 'lightmapTexture', this.object).name('Lightmap Texture'); // Refraction const refraction = this.tool.addFolder('Refraction (backward compatibility)'); refraction.add(this.object, 'indexOfRefraction').step(0.01).name('Index of Refraction'); refraction.add(this.object, 'invertRefractionY').name('Invert Y'); refraction.add(this.object, 'linkRefractionWithTransparency').name('Link Refraction With Transparency'); - this.tool.addTexture(refraction, this.editor, 'refractionTexture', this.object, true).name('Refraction Texture'); + this.tool.addTexture(refraction, this.editor, this.editor.core.scene, 'refractionTexture', this.object, true).name('Refraction Texture'); // Options super.addOptions(); diff --git a/src/editor/edition-tools/materials/standard-tool.ts b/src/editor/edition-tools/materials/standard-tool.ts index 70526bf71..21269c2e2 100644 --- a/src/editor/edition-tools/materials/standard-tool.ts +++ b/src/editor/edition-tools/materials/standard-tool.ts @@ -28,12 +28,12 @@ export default class StandardMaterialTool extends MaterialTool diffuse.open(); diffuse.add(this.object, 'linkEmissiveWithDiffuse').name('Link Emissive With Diffuse'); diffuse.add(this.object, 'useAlphaFromDiffuseTexture').name('Use Alpha From Diffuse Texture'); - this.tool.addTexture(diffuse, this.editor, 'diffuseTexture', this.object).name('Diffuse Texture'); + this.tool.addTexture(diffuse, this.editor, this.editor.core.scene, 'diffuseTexture', this.object).name('Diffuse Texture'); this.tool.addColor(diffuse, 'Color', this.object.diffuseColor).open(); // Bump const bump = this.tool.addFolder('Bump'); - this.tool.addTexture(bump, this.editor, 'bumpTexture', this.object).name('Bump Texture'); + this.tool.addTexture(bump, this.editor, this.editor.core.scene, 'bumpTexture', this.object).name('Bump Texture'); bump.add(this.object, 'invertNormalMapX').name('Invert Normal Map X'); bump.add(this.object, 'invertNormalMapY').name('Invert Normal Map Y'); bump.add(this.object, 'useParallax').name('Use Parallax'); @@ -47,38 +47,38 @@ export default class StandardMaterialTool extends MaterialTool specular.add(this.object, 'useGlossinessFromSpecularMapAlpha').name('Use Glossiness From Specular Map Alpha'); specular.add(this.object, 'useReflectionFresnelFromSpecular').name('Use Reflection Fresnel From Specular'); specular.add(this.object, 'useSpecularOverAlpha').name('Use Specular Over Alpha'); - this.tool.addTexture(specular, this.editor, 'specularTexture', this.object).name('Specular Texture'); + this.tool.addTexture(specular, this.editor, this.editor.core.scene, 'specularTexture', this.object).name('Specular Texture'); this.tool.addColor(specular, 'Color', this.object.specularColor).open(); // Opacity const opacity = this.tool.addFolder('Opacity'); - this.tool.addTexture(opacity, this.editor, 'opacityTexture', this.object).name('Opacity Texture'); + this.tool.addTexture(opacity, this.editor, this.editor.core.scene, 'opacityTexture', this.object).name('Opacity Texture'); // Emissive const emissive = this.tool.addFolder('Emissive'); this.tool.addColor(emissive, 'Emissive', this.object.emissiveColor).open(); emissive.add(this.object, 'useEmissiveAsIllumination').name('Use Emissive As Illumination'); - this.tool.addTexture(emissive, this.editor, 'emissiveTexture', this.object).name('Emissive Texture'); + this.tool.addTexture(emissive, this.editor, this.editor.core.scene, 'emissiveTexture', this.object).name('Emissive Texture'); // Ambient const ambient = this.tool.addFolder('Ambient'); this.tool.addColor(ambient, 'Ambient', this.object.ambientColor).open(); - this.tool.addTexture(ambient, this.editor, 'ambientTexture', this.object).name('Ambient Texture'); + this.tool.addTexture(ambient, this.editor, this.editor.core.scene, 'ambientTexture', this.object).name('Ambient Texture'); // Light map const lightmap = this.tool.addFolder('Lightmap'); lightmap.add(this.object, 'useLightmapAsShadowmap').name('Use Lightmap As Shadowmap'); - this.tool.addTexture(lightmap, this.editor, 'lightmapTexture', this.object).name('Lightmap Texture'); + this.tool.addTexture(lightmap, this.editor, this.editor.core.scene, 'lightmapTexture', this.object).name('Lightmap Texture'); // Reflection const reflection = this.tool.addFolder('Reflection'); - this.tool.addTexture(reflection, this.editor, 'reflectionTexture', this.object, true).name('Reflection Texture'); + this.tool.addTexture(reflection, this.editor, this.editor.core.scene, 'reflectionTexture', this.object, true).name('Reflection Texture'); // Refraction const refraction = this.tool.addFolder('Refraction'); refraction.add(this.object, 'indexOfRefraction').name('Index of Refraction'); refraction.add(this.object, 'invertRefractionY').name('Invert Y'); - this.tool.addTexture(refraction, this.editor, 'refractionTexture', this.object, true).name('Refraction Texture'); + this.tool.addTexture(refraction, this.editor, this.editor.core.scene, 'refractionTexture', this.object, true).name('Refraction Texture'); // Options const options = super.addOptions(); diff --git a/src/editor/edition-tools/materials/terrain-tool.ts b/src/editor/edition-tools/materials/terrain-tool.ts index 68cd7d010..97cbd4a47 100644 --- a/src/editor/edition-tools/materials/terrain-tool.ts +++ b/src/editor/edition-tools/materials/terrain-tool.ts @@ -27,24 +27,24 @@ export default class TerrainMaterialTool extends MaterialTool { const terrain = this.tool.addFolder('Terrain'); terrain.open(); - this.tool.addTexture(terrain, this.editor, 'mixTexture', this.object, false).name('Mix Texture'); + this.tool.addTexture(terrain, this.editor, this.editor.core.scene, 'mixTexture', this.object, false).name('Mix Texture'); // Diffuse const diffuse = terrain.addFolder('Diffuse'); diffuse.open(); this.tool.addColor(diffuse, 'Color', this.object.diffuseColor).open(); - this.tool.addTexture(diffuse, this.editor, 'diffuseTexture1', this.object, false).name('Diffuse Texture R'); - this.tool.addTexture(diffuse, this.editor, 'diffuseTexture2', this.object, false).name('Diffuse Texture G'); - this.tool.addTexture(diffuse, this.editor, 'diffuseTexture3', this.object, false).name('Diffuse Texture B'); + this.tool.addTexture(diffuse, this.editor, this.editor.core.scene, 'diffuseTexture1', this.object, false).name('Diffuse Texture R'); + this.tool.addTexture(diffuse, this.editor, this.editor.core.scene, 'diffuseTexture2', this.object, false).name('Diffuse Texture G'); + this.tool.addTexture(diffuse, this.editor, this.editor.core.scene, 'diffuseTexture3', this.object, false).name('Diffuse Texture B'); // Bump const bump = terrain.addFolder('Bump'); bump.open(); - this.tool.addTexture(bump, this.editor, 'bumpTexture1', this.object, false).name('Bump Texture R'); - this.tool.addTexture(bump, this.editor, 'bumpTexture2', this.object, false).name('Bump Texture G'); - this.tool.addTexture(bump, this.editor, 'bumpTexture3', this.object, false).name('Bump Texture B'); + this.tool.addTexture(bump, this.editor, this.editor.core.scene, 'bumpTexture1', this.object, false).name('Bump Texture R'); + this.tool.addTexture(bump, this.editor, this.editor.core.scene, 'bumpTexture2', this.object, false).name('Bump Texture G'); + this.tool.addTexture(bump, this.editor, this.editor.core.scene, 'bumpTexture3', this.object, false).name('Bump Texture B'); // Specular const specular = terrain.addFolder('Specular'); diff --git a/src/editor/edition-tools/materials/tri-planar-tool.ts b/src/editor/edition-tools/materials/tri-planar-tool.ts index af607e086..5ca7004e5 100644 --- a/src/editor/edition-tools/materials/tri-planar-tool.ts +++ b/src/editor/edition-tools/materials/tri-planar-tool.ts @@ -34,17 +34,17 @@ export default class TriPlanarMaterialTool extends MaterialTool { // Bump const bump = this.tool.addFolder('Bump'); bump.open(); - this.tool.addTexture(bump, this.editor, 'bumpTexture', this.object, false); + this.tool.addTexture(bump, this.editor, this.editor.core.scene, 'bumpTexture', this.object, false); bump.add(this.object, 'bumpHeight').min(0).max(10).step(0.001).name('Bump Height'); // Wind diff --git a/src/editor/edition-tools/particle-system-tool.ts b/src/editor/edition-tools/particle-system-tool.ts deleted file mode 100644 index 9acbf8326..000000000 --- a/src/editor/edition-tools/particle-system-tool.ts +++ /dev/null @@ -1,240 +0,0 @@ -import { - ParticleSystem, GPUParticleSystem, Vector3, IParticleSystem, - BoxParticleEmitter, SphereParticleEmitter, ConeParticleEmitter, - SphereDirectedParticleEmitter, ParticleHelper, - FilesInputStore, - Tools as BabylonTools -} from 'babylonjs'; - -import AbstractEditionTool from './edition-tool'; -import Tools from '../tools/tools'; - -import Dialog from '../gui/dialog'; - -export default class ParticleSystemTool extends AbstractEditionTool { - // Public members - public divId: string = 'PARTICLE-SYSTEM-TOOL'; - public tabName: string = 'Particle System'; - - // Private members - private _currentEmitter: string = ''; - private _currentBlendMode: string = ''; - private _currentEmiterType: string = ''; - - /** - * Returns if the object is supported - * @param object the object selected in the graph - */ - public isSupported(object: any): boolean { - return object instanceof ParticleSystem; - } - - /** - * Updates the edition tool - * @param object the object selected in the graph - */ - public update(ps: ParticleSystem | GPUParticleSystem): void { - super.update(ps); - - // Misc. - const scene = this.editor.core.scene; - - // Load / save - const presets = this.tool.addFolder('Presets'); - presets.open(); - - presets.add(this, '_saveSet').name('Save...'); - presets.add(this, '_loadSet').name('Load...'); - - // Particle System - if (ps instanceof ParticleSystem) { - // Emitter - const emitter = this.tool.addFolder('Emitter'); - emitter.open(); - - emitter.add(ps, 'id').name('Id'); - emitter.add(ps, 'name').name('Name'); - - if (ps.emitter instanceof Vector3) - this.tool.addVector(emitter, 'Emitter', ps.emitter); - else { - this._currentEmitter = ps.emitter.name; - const nodes = scene.meshes.map(m => m.name); - - emitter.add(this, '_currentEmitter', nodes).name('Emitter').onFinishChange(r => { - const mesh = scene.getMeshByName(r); - if (mesh) - ps.emitter = mesh; - }); - } - - // Emitter type - const emiterType = this.tool.addFolder('Emiter Type'); - emiterType.open(); - - this._currentEmiterType = this._getEmiterTypeString(ps); - const emiterTypes: string[] = [ - 'Box', - 'Sphere', - 'Sphere Directed', - 'Cone' - ]; - emiterType.add(this, '_currentEmiterType', emiterTypes).name('Emiter Type').onFinishChange(r => { - switch (r) { - case 'Box': ps.createBoxEmitter(ps.direction1, ps.direction2, ps.minEmitBox, ps.maxEmitBox); break; - case 'Sphere': ps.createSphereEmitter(10); break; - case 'Sphere Directed': ps.createDirectedSphereEmitter(10, ps.direction1, ps.direction2); break; - case 'Cone': ps.createConeEmitter(10, 0); break; - default: break; - } - - this.update(ps); - }); - - if (ps.particleEmitterType instanceof SphereParticleEmitter) { - emiterType.add(ps.particleEmitterType, 'radius').step(0.01).name('Radius'); - } - else if (ps.particleEmitterType instanceof ConeParticleEmitter) { - emiterType.add(ps.particleEmitterType, 'radius').step(0.01).name('Radius'); - emiterType.add(ps.particleEmitterType, 'angle').step(0.01).name('Angle'); - } - - if (!(ps.particleEmitterType instanceof BoxParticleEmitter)) - emiterType.add(ps.particleEmitterType, 'directionRandomizer').min(0).max(1).step(0.001).name('Direction Randomizer'); - - // Texture - const texture = this.tool.addFolder('Texture'); - texture.open(); - this.tool.addTexture(texture, this.editor, 'particleTexture', ps, false).name('Particle Texture'); - - const blendModes = ['BLENDMODE_ONEONE', 'BLENDMODE_STANDARD']; - this._currentBlendMode = blendModes[ps.blendMode]; - texture.add(this, '_currentBlendMode', blendModes).name('Blend Mode').onChange(r => ps.blendMode = ParticleSystem[r]); - - // Actions - const actions = this.tool.addFolder('Actions'); - actions.open(); - actions.add(ps, 'rebuild').name('Rebuild'); - actions.add(ps, 'start').name('Start'); - actions.add(ps, 'stop').name('Stop'); - - // Emit - const emit = this.tool.addFolder('Emit'); - emit.open(); - emit.add(ps, 'emitRate').min(0).step(0.01).name('Emit Rate'); - emit.add(ps, 'minEmitPower').min(0).step(0.01).name('Min Emit Power'); - emit.add(ps, 'maxEmitPower').min(0).step(0.01).name('Max Emit Power'); - - // Update - const update = this.tool.addFolder('Update'); - update.open(); - update.add(ps, 'updateSpeed').min(0).step(0.01).name('Update Speed'); - - // Life - const life = this.tool.addFolder('Life Time'); - life.open(); - life.add(ps, 'minLifeTime').min(0).step(0.01).name('Min Life Time'); - life.add(ps, 'maxLifeTime').min(0).step(0.01).name('Max Life Time'); - - // Size - const size = this.tool.addFolder('Size'); - size.open(); - size.add(ps, 'minSize').min(0).step(0.01).name('Min Size'); - size.add(ps, 'maxSize').min(0).step(0.01).name('Max Size'); - - // Angular Speed - const angular = this.tool.addFolder('Angular Speed'); - angular.open(); - angular.add(ps, 'minAngularSpeed').min(0).step(0.01).name('Min Angular Speed'); - angular.add(ps, 'maxAngularSpeed').min(0).step(0.01).name('Max Angular Speed'); - - // Sprite - if (ps.isAnimationSheetEnabled) { - const sprite = this.tool.addFolder('Sprite'); - sprite.open(); - sprite.add(ps, 'startSpriteCellID').min(0).step(1).name('Start Sprite Cell ID'); - sprite.add(ps, 'endSpriteCellID').min(0).step(1).name('End Sprite Cell ID'); - sprite.add(ps, 'spriteCellWidth').min(0).step(1).name('Sprite Cell Width'); - sprite.add(ps, 'spriteCellHeight').min(0).step(1).name('Sprite Cell Height'); - // sprite.add(ps, 'spriteCellLoop').name('Sprite Cell Loop').onFinishChange(r => ps.spriteCellLoop = r); - sprite.add(ps, 'spriteCellChangeSpeed').min(0).step(1).name('Sprite Cell Change Speed'); - } - - // Gravity - this.tool.addVector(this.tool.element, 'Gravity', ps.gravity).open(); - - if (ps.particleEmitterType instanceof BoxParticleEmitter || ps.particleEmitterType instanceof SphereDirectedParticleEmitter) { - // Direction1 - this.tool.addVector(this.tool.element, 'Direction 1', ps.direction1).open(); - - // Direction2 - this.tool.addVector(this.tool.element, 'Direction 2', ps.direction2).open(); - - if (ps.particleEmitterType instanceof BoxParticleEmitter) { - // Min Emit Box - this.tool.addVector(this.tool.element, 'Min Emit Box', ps.minEmitBox).open(); - - // Max Emit Box - this.tool.addVector(this.tool.element, 'Max Emit Box', ps.maxEmitBox).open(); - } - } - - // Color 1 - this.tool.addColor(this.tool.element, 'Color 1', ps.color1).open(); - - // Color 2 - this.tool.addColor(this.tool.element, 'Color 2', ps.color2).open(); - - // Color Dead - this.tool.addColor(this.tool.element, 'Color Dead', ps.colorDead).open(); - } - } - - // Returns the emiter type as a string - private _getEmiterTypeString (ps: IParticleSystem): string { - if (ps.particleEmitterType instanceof BoxParticleEmitter) return 'Box'; - if (ps.particleEmitterType instanceof SphereDirectedParticleEmitter) return 'Sphere Directed'; - if (ps.particleEmitterType instanceof SphereParticleEmitter) return 'Sphere'; - if (ps.particleEmitterType instanceof ConeParticleEmitter) return 'Cone'; - return 'None'; - } - - // Exports the current set - private async _saveSet (): Promise { - // Export - const set = ParticleHelper.ExportSet([this.object]); - const serializationObject = set.serialize(); - - // Embed? - const embed = await Dialog.Create('Embed textures?', 'Do you want to embed textures in the set?'); - if (embed) { - for (const s of serializationObject.systems) { - const file = FilesInputStore.FilesToLoad[s.textureName.toLowerCase()]; - if (!file) - continue; - s.textureName = await Tools.ReadFileAsBase64(file); - } - } - - // Save - const json = JSON.stringify(serializationObject, null, '\t'); - const file = Tools.CreateFile(Tools.ConvertStringToUInt8Array(json), this.object.name + '.json'); - BabylonTools.Download(file, file.name); - } - - // Loads the selected preset from files open dialog - private async _loadSet (): Promise { - const files = await Tools.OpenFileDialog(); - const content = JSON.parse(await Tools.ReadFileAsText(files[0])); - - if (!content.systems || content.systems.length === 0) - return; - - const savedEmitter = this.object.emitter; - - const rootUrl = content.systems[0].textureName.indexOf('data:') === 0 ? '' : 'file:'; - ParticleSystem._Parse(content.systems[0], this.object, this.editor.core.scene, rootUrl); - - this.object.emitter = savedEmitter; - } -} diff --git a/src/editor/edition-tools/particles/particle-system-tool.ts b/src/editor/edition-tools/particles/particle-system-tool.ts new file mode 100644 index 000000000..8c877f0f5 --- /dev/null +++ b/src/editor/edition-tools/particles/particle-system-tool.ts @@ -0,0 +1,308 @@ +import { + ParticleSystem, Vector3, IParticleSystem, + BoxParticleEmitter, SphereParticleEmitter, ConeParticleEmitter, + SphereDirectedParticleEmitter, ParticleHelper, + FilesInputStore, + Tools as BabylonTools, + Texture +} from 'babylonjs'; + +import AbstractEditionTool from '../edition-tool'; +import Tools from '../../tools/tools'; +import UndoRedo from '../../tools/undo-redo'; + +import Dialog from '../../gui/dialog'; + +export default class ParticleSystemTool extends AbstractEditionTool { + // Public members + public divId: string = 'PARTICLE-SYSTEM-TOOL'; + public tabName: string = 'Particle System'; + + // Private members + private _currentEmitter: string = ''; + private _currentBillboard: string = ''; + private _currentBlendMode: string = ''; + private _currentEmiterType: string = ''; + private _currentTexture: string = ''; + + private _isFromScene: boolean = true; + + /** + * Returns if the object is supported + * @param object the object selected in the graph + */ + public isSupported(object: any): boolean { + return object instanceof ParticleSystem; + } + + /** + * Updates the edition tool + * @param object the object selected in the graph + */ + public update(ps: ParticleSystem): void { + super.update(ps); + + // Misc. + const scene = this.editor.core.scene; + this._isFromScene = scene.getParticleSystemByID(ps.id) !== null; + + // Load / save + const presets = this.tool.addFolder('Presets'); + presets.open(); + + presets.add(this, '_saveSet').name('Save...'); + presets.add(this, '_loadSet').name('Load...'); + + // Emitter + const emitter = this.tool.addFolder('Emitter'); + emitter.open(); + + emitter.add(ps, 'name').name('Name'); + + if (this._isFromScene) { + emitter.add(ps, 'id').name('Id'); + + if (ps.emitter instanceof Vector3) + this.tool.addVector(emitter, 'Emitter', ps.emitter); + else { + this._currentEmitter = ps.emitter.name; + const nodes = scene.meshes.map(m => m.name); + + emitter.add(this, '_currentEmitter', nodes).name('Emitter').onFinishChange(r => { + const mesh = scene.getMeshByName(r); + if (mesh) + ps.emitter = mesh; + }); + } + } + else { + this.tool.addVector(emitter, 'Emitter', ps.emitter); + } + + // Emitter type + const emiterType = this.tool.addFolder('Emiter Type'); + emiterType.open(); + + this._currentEmiterType = this._getEmiterTypeString(ps); + const emiterTypes: string[] = [ + 'Box', + 'Sphere', + 'Sphere Directed', + 'Cone' + ]; + emiterType.add(this, '_currentEmiterType', emiterTypes).name('Emiter Type').onFinishChange(r => { + switch (r) { + case 'Box': ps.createBoxEmitter(ps.direction1, ps.direction2, ps.minEmitBox, ps.maxEmitBox); break; + case 'Sphere': ps.createSphereEmitter(10); break; + case 'Sphere Directed': ps.createDirectedSphereEmitter(10, ps.direction1, ps.direction2); break; + case 'Cone': ps.createConeEmitter(10, 0); break; + default: break; + } + + this.update(ps); + }); + + if (ps.particleEmitterType instanceof SphereParticleEmitter) { + emiterType.add(ps.particleEmitterType, 'radius').step(0.01).name('Radius'); + } + else if (ps.particleEmitterType instanceof ConeParticleEmitter) { + emiterType.add(ps.particleEmitterType, 'radius').step(0.01).name('Radius'); + emiterType.add(ps.particleEmitterType, 'angle').step(0.01).name('Angle'); + } + + if (!(ps.particleEmitterType instanceof BoxParticleEmitter)) + emiterType.add(ps.particleEmitterType, 'directionRandomizer').min(0).max(1).step(0.001).name('Direction Randomizer'); + + // Texture + const texture = this.tool.addFolder('Texture'); + texture.open(); + + if (this._isFromScene) { + this.tool.addTexture(texture, this.editor, this.editor.core.scene, 'particleTexture', ps, false, false).name('Particle Texture'); + } + else { + const exts = ['png' ,'jpg', 'jpeg', 'bmp']; + const files = ['None'].concat(Object.keys(FilesInputStore.FilesToLoad).filter(k => exts.indexOf(Tools.GetFileExtension(k)) !== -1)); + this._currentTexture = ps.particleTexture ? files[files.indexOf(ps.particleTexture.name)] || 'None' : 'None'; + texture.add(this, '_currentTexture', files).name('Particle Texture').onChange(r => { + const file = FilesInputStore.FilesToLoad[r]; + if (!file) + return; + + const texture = new Texture('file:' + r, ps.getScene()); + texture.name = texture.url = texture.name.replace('file:', ''); + ps.particleTexture = texture; + }); + } + + const blendModes = ['BLENDMODE_ONEONE', 'BLENDMODE_STANDARD']; + this._currentBlendMode = blendModes[ps.blendMode] || 'BLENDMODE_ONEONE'; + texture.add(this, '_currentBlendMode', blendModes).name('Blend Mode').onChange(r => ps.blendMode = ParticleSystem[r]); + + // Actions + const actions = this.tool.addFolder('Actions'); + actions.open(); + actions.add(ps, 'preventAutoStart').name('Prevent Auto Start'); + actions.add(ps, 'rebuild').name('Rebuild'); + actions.add(ps, 'start').name('Start'); + actions.add(ps, 'stop').name('Stop'); + + // Billboard + const modes: string[] = ['BILLBOARDMODE_ALL', 'BILLBOARDMODE_Y', 'BILLBOARDMODE_STRETCHED']; + this._currentBillboard = 'None'; + switch (ps.billboardMode) { + case ParticleSystem.BILLBOARDMODE_ALL: this._currentBillboard = 'BILLBOARDMODE_ALL'; break; + case ParticleSystem.BILLBOARDMODE_Y: this._currentBillboard = 'BILLBOARDMODE_Y'; break; + case ParticleSystem.BILLBOARDMODE_STRETCHED: this._currentBillboard = 'BILLBOARDMODE_STRETCHED'; break; + default: debugger; break; + } + + const billboard = this.tool.addFolder('Billboarding'); + billboard.open(); + billboard.add(ps, 'isBillboardBased').name('Is Billboard Based'); + billboard.add(this, '_currentBillboard', modes).name('Billboard Type').onChange(r => { + const from = ps.billboardMode; + const to = ParticleSystem[r]; + + UndoRedo.Push({ baseObject: ps, property: 'billboardMode', from: from, to: to }); + }); + + // Emit + const emit = this.tool.addFolder('Emit'); + emit.open(); + emit.add(ps, 'emitRate').min(0).step(0.01).name('Emit Rate'); + emit.add(ps, 'minEmitPower').min(0).step(0.01).name('Min Emit Power'); + emit.add(ps, 'maxEmitPower').min(0).step(0.01).name('Max Emit Power'); + + // Update + const update = this.tool.addFolder('Update'); + update.open(); + update.add(ps, 'updateSpeed').min(0).step(0.01).name('Update Speed'); + update.add(ps, 'startDelay').min(0).name('Start Delay (ms)'); + update.add(ps, 'targetStopDuration').min(0).name('Stop Duration (seconds)'); + + // Life + const life = this.tool.addFolder('Life Time'); + life.open(); + life.add(ps, 'minLifeTime').min(0).step(0.01).name('Min Life Time'); + life.add(ps, 'maxLifeTime').min(0).step(0.01).name('Max Life Time'); + + // Size + const size = this.tool.addFolder('Size'); + size.open(); + size.add(ps, 'minSize').min(0).step(0.01).name('Min Size'); + size.add(ps, 'maxSize').min(0).step(0.01).name('Max Size'); + + // Angular Speed + const angular = this.tool.addFolder('Angular Speed'); + angular.open(); + angular.add(ps, 'minAngularSpeed').min(0).step(0.01).name('Min Angular Speed'); + angular.add(ps, 'maxAngularSpeed').min(0).step(0.01).name('Max Angular Speed'); + + // Sprite + if (ps.isAnimationSheetEnabled) { + const sprite = this.tool.addFolder('Sprite'); + sprite.open(); + sprite.add(ps, 'startSpriteCellID').min(0).step(1).name('Start Sprite Cell ID'); + sprite.add(ps, 'endSpriteCellID').min(0).step(1).name('End Sprite Cell ID'); + sprite.add(ps, 'spriteCellWidth').min(0).step(1).name('Sprite Cell Width'); + sprite.add(ps, 'spriteCellHeight').min(0).step(1).name('Sprite Cell Height'); + // sprite.add(ps, 'spriteCellLoop').name('Sprite Cell Loop').onFinishChange(r => ps.spriteCellLoop = r); + sprite.add(ps, 'spriteCellChangeSpeed').min(0).step(1).name('Sprite Cell Change Speed'); + } + + // Gravity + this.tool.addVector(this.tool.element, 'Gravity', ps.gravity).open(); + + if (ps.particleEmitterType instanceof BoxParticleEmitter || ps.particleEmitterType instanceof SphereDirectedParticleEmitter) { + // Direction1 + this.tool.addVector(this.tool.element, 'Direction 1', ps.direction1).open(); + + // Direction2 + this.tool.addVector(this.tool.element, 'Direction 2', ps.direction2).open(); + + if (ps.particleEmitterType instanceof BoxParticleEmitter) { + // Min Emit Box + this.tool.addVector(this.tool.element, 'Min Emit Box', ps.minEmitBox).open(); + + // Max Emit Box + this.tool.addVector(this.tool.element, 'Max Emit Box', ps.maxEmitBox).open(); + } + } + + // Color 1 + this.tool.addColor(this.tool.element, 'Color 1', ps.color1).open(); + + // Color 2 + this.tool.addColor(this.tool.element, 'Color 2', ps.color2).open(); + + // Color Dead + this.tool.addColor(this.tool.element, 'Color Dead', ps.colorDead).open(); + + // Animations + if (!this._isFromScene) { + const animations = this.tool.addFolder('Animations'); + animations.open(); + + ps.beginAnimationOnStart = ps.beginAnimationOnStart || false; + animations.add(ps, 'beginAnimationOnStart').name('Begin Animations On Start'); + + ps.beginAnimationFrom = ps.beginAnimationFrom || 0; + animations.add(ps, 'beginAnimationFrom').min(0).step(1).name('Begin Animation From'); + + ps.beginAnimationTo = ps.beginAnimationTo || 60; + animations.add(ps, 'beginAnimationTo').min(0).step(1).name('Begin Animation To'); + + ps.beginAnimationLoop = ps.beginAnimationLoop || false; + animations.add(ps, 'beginAnimationLoop').name('Begin Animation Loop'); + } + } + + // Returns the emiter type as a string + private _getEmiterTypeString (ps: IParticleSystem): string { + if (ps.particleEmitterType instanceof BoxParticleEmitter) return 'Box'; + if (ps.particleEmitterType instanceof SphereDirectedParticleEmitter) return 'Sphere Directed'; + if (ps.particleEmitterType instanceof SphereParticleEmitter) return 'Sphere'; + if (ps.particleEmitterType instanceof ConeParticleEmitter) return 'Cone'; + return 'None'; + } + + // Exports the current set + private async _saveSet (): Promise { + // Export + const set = ParticleHelper.ExportSet([this.object]); + const serializationObject = set.serialize(); + + // Embed? + const embed = await Dialog.Create('Embed textures?', 'Do you want to embed textures in the set?'); + if (embed === 'Yes') { + for (const s of serializationObject.systems) { + const file = FilesInputStore.FilesToLoad[s.textureName.toLowerCase()]; + if (!file) + continue; + s.textureName = await Tools.ReadFileAsBase64(file); + } + } + + // Save + const json = JSON.stringify(serializationObject, null, '\t'); + const file = Tools.CreateFile(Tools.ConvertStringToUInt8Array(json), this.object.name + '.json'); + BabylonTools.Download(file, file.name); + } + + // Loads the selected preset from files open dialog + private async _loadSet (): Promise { + const files = await Tools.OpenFileDialog(); + const content = JSON.parse(await Tools.ReadFileAsText(files[0])); + + if (!content.systems || content.systems.length === 0) + return; + + const savedEmitter = this.object.emitter; + + const rootUrl = content.systems[0].textureName.indexOf('data:') === 0 ? '' : 'file:'; + ParticleSystem._Parse(content.systems[0], this.object, this.object.getScene(), rootUrl); + + this.object.emitter = savedEmitter; + } +} diff --git a/src/editor/edition-tools/post-processes/custom-tool.ts b/src/editor/edition-tools/post-processes/custom-tool.ts index 87dcbfc96..312cda7b7 100644 --- a/src/editor/edition-tools/post-processes/custom-tool.ts +++ b/src/editor/edition-tools/post-processes/custom-tool.ts @@ -1,68 +1,68 @@ -import { Vector2, Vector3 } from 'babylonjs'; - -import PostProcessEditor, { CustomPostProcessConfig } from '../../../extensions/post-process-editor/post-process'; -import AbstractEditionTool from '../edition-tool'; -import Tools from '../../tools/tools'; - -export default class CustomPostProcessTool extends AbstractEditionTool { - // Public members - public divId: string = 'CUSTOM-POST-PROCESS-TOOL'; - public tabName: string = 'Custom Post-Process'; - - /** - * Returns if the object is supported - * @param object the object selected in the graph - */ - public isSupported(object: any): boolean { - return object.getClassName && object.getClassName() === 'PostProcessEditor'; - } - - /** - * Updates the edition tool - * @param object the object selected in the graph - */ - public update(object: any): void { - super.update(object); - this.setTabName('Custom Post-Process'); - - // Get current config of the post-process - const config = this.object.config; - - // Floats - const floats = this.tool.addFolder('Floats'); - floats.open(); - - config.floats.forEach(f => { - if (this.object.userConfig[f] === undefined) - this.object.userConfig[f] = 1; - - floats.add(this.object.userConfig, f).step(0.01).name(f); - }); - - // Vectors - const vectors = this.tool.addFolder('Vectors'); - vectors.open(); - - config.vectors2.forEach(v => { - if (!this.object.userConfig[v] || !(this.object.userConfig[v] instanceof Vector2)) - this.object.userConfig[v] = Vector2.Zero(); - - this.tool.addVector(vectors, v, this.object.userConfig[v]).open(); - }); - - config.vectors3.forEach(v => { - if (!this.object.userConfig[v] || !(this.object.userConfig[v] instanceof Vector3)) - this.object.userConfig[v] = Vector3.Zero(); - - this.tool.addVector(vectors, v, this.object.userConfig[v]).open(); - }); - - // Samplers - const samplers = this.tool.addFolder('Samplers'); - samplers.open(); - - config.textures.forEach(t => { - this.tool.addTexture(samplers, this.editor, t, this.object.userConfig, false, false).name(t); - }); - } -} +import { Vector2, Vector3 } from 'babylonjs'; + +import PostProcessEditor, { CustomPostProcessConfig } from '../../../extensions/post-process-editor/post-process'; +import AbstractEditionTool from '../edition-tool'; +import Tools from '../../tools/tools'; + +export default class CustomPostProcessTool extends AbstractEditionTool { + // Public members + public divId: string = 'CUSTOM-POST-PROCESS-TOOL'; + public tabName: string = 'Custom Post-Process'; + + /** + * Returns if the object is supported + * @param object the object selected in the graph + */ + public isSupported(object: any): boolean { + return object.getClassName && object.getClassName() === 'PostProcessEditor'; + } + + /** + * Updates the edition tool + * @param object the object selected in the graph + */ + public update(object: any): void { + super.update(object); + this.setTabName('Custom Post-Process'); + + // Get current config of the post-process + const config = this.object.config; + + // Floats + const floats = this.tool.addFolder('Floats'); + floats.open(); + + config.floats.forEach(f => { + if (this.object.userConfig[f] === undefined) + this.object.userConfig[f] = 1; + + floats.add(this.object.userConfig, f).step(0.01).name(f); + }); + + // Vectors + const vectors = this.tool.addFolder('Vectors'); + vectors.open(); + + config.vectors2.forEach(v => { + if (!this.object.userConfig[v] || !(this.object.userConfig[v] instanceof Vector2)) + this.object.userConfig[v] = Vector2.Zero(); + + this.tool.addVector(vectors, v, this.object.userConfig[v]).open(); + }); + + config.vectors3.forEach(v => { + if (!this.object.userConfig[v] || !(this.object.userConfig[v] instanceof Vector3)) + this.object.userConfig[v] = Vector3.Zero(); + + this.tool.addVector(vectors, v, this.object.userConfig[v]).open(); + }); + + // Samplers + const samplers = this.tool.addFolder('Samplers'); + samplers.open(); + + config.textures.forEach(t => { + this.tool.addTexture(samplers, this.editor, this.editor.core.scene, t, this.object.userConfig, false, false).name(t); + }); + } +} diff --git a/src/editor/edition-tools/post-processes/post-processes-tool.ts b/src/editor/edition-tools/post-processes/post-processes-tool.ts index bcd830db3..212e6d8c5 100644 --- a/src/editor/edition-tools/post-processes/post-processes-tool.ts +++ b/src/editor/edition-tools/post-processes/post-processes-tool.ts @@ -97,7 +97,7 @@ export default class PostProcessesTool extends AbstractEditionTool { const lensTexture = bloom.addFolder('Lens Dirt Texture'); lensTexture.open(); - this.tool.addTexture(lensTexture, this.editor, 'lensTexture', SceneManager.StandardRenderingPipeline, false, false, texture => { + this.tool.addTexture(lensTexture, this.editor, this.editor.core.scene, 'lensTexture', SceneManager.StandardRenderingPipeline, false, false, texture => { SceneManager.StandardRenderingPipeline.lensFlareDirtTexture = texture; }).name('Texture'); diff --git a/src/editor/edition-tools/procedural-textures/normal-tool.ts b/src/editor/edition-tools/procedural-textures/normal-tool.ts index 6e0115e4d..02c92674f 100644 --- a/src/editor/edition-tools/procedural-textures/normal-tool.ts +++ b/src/editor/edition-tools/procedural-textures/normal-tool.ts @@ -1,32 +1,32 @@ -import { NormalMapProceduralTexture } from 'babylonjs-procedural-textures'; - -import AbstractEditionTool from '../edition-tool'; - -export default class NormalProceduralTool extends AbstractEditionTool { - // Public members - public divId: string = 'NORMAL-PROCEDURAL-TOOL'; - public tabName: string = 'Normal'; - - /** - * Returns if the object is supported - * @param object the object selected in the graph - */ - public isSupported (object: any): boolean { - return object instanceof NormalMapProceduralTexture; - } - - /** - * Updates the edition tool - * @param object the object selected in the graph - */ - public update (object: NormalMapProceduralTexture): void { - // Super - super.update(object); - - // Normal - const normal = this.tool.addFolder('Normal'); - normal.open(); - - this.tool.addTexture(normal, this.editor, 'baseTexture', object, false, false, () => object.updateShaderUniforms()); - } -} +import { NormalMapProceduralTexture } from 'babylonjs-procedural-textures'; + +import AbstractEditionTool from '../edition-tool'; + +export default class NormalProceduralTool extends AbstractEditionTool { + // Public members + public divId: string = 'NORMAL-PROCEDURAL-TOOL'; + public tabName: string = 'Normal'; + + /** + * Returns if the object is supported + * @param object the object selected in the graph + */ + public isSupported (object: any): boolean { + return object instanceof NormalMapProceduralTexture; + } + + /** + * Updates the edition tool + * @param object the object selected in the graph + */ + public update (object: NormalMapProceduralTexture): void { + // Super + super.update(object); + + // Normal + const normal = this.tool.addFolder('Normal'); + normal.open(); + + this.tool.addTexture(normal, this.editor, this.editor.core.scene, 'baseTexture', object, false, false, () => object.updateShaderUniforms()); + } +} diff --git a/src/editor/edition-tools/scene-tool.ts b/src/editor/edition-tools/scene-tool.ts index cdda7dec6..546860e05 100644 --- a/src/editor/edition-tools/scene-tool.ts +++ b/src/editor/edition-tools/scene-tool.ts @@ -98,7 +98,7 @@ export default class SceneTool extends AbstractEditionTool { // Environment texture const environment = this.tool.addFolder('Environment Texture'); environment.open(); - this.tool.addTexture(environment, this.editor, 'environmentTexture', scene, true, true).name('Environment Texture'); + this.tool.addTexture(environment, this.editor, this.editor.core.scene, 'environmentTexture', scene, true, true).name('Environment Texture'); // Collisions const collisions = this.tool.addFolder('Collisions'); diff --git a/src/editor/editor.ts b/src/editor/editor.ts index 5d141efac..a63d33c17 100644 --- a/src/editor/editor.ts +++ b/src/editor/editor.ts @@ -693,10 +693,7 @@ export default class Editor implements IUpdatable { document.addEventListener('contextmenu', (e) => e.preventDefault()); // Undo - UndoRedo.onUndo = (e) => { - this.core.onGlobalPropertyChange.notifyObservers({ baseObject: e.baseObject, object: e.object, property: e.property, value: e.to, initialValue: e.from }); - Tools.SetWindowTitle(this.projectFileName + ' *'); - }; + UndoRedo.onUndo = (e) => Tools.SetWindowTitle(this.projectFileName + ' *'); document.addEventListener('keyup', (ev) => { if (!CodeEditor.HasOneFocused() && ev.ctrlKey && ev.key === 'z') { UndoRedo.Undo(); @@ -707,10 +704,7 @@ export default class Editor implements IUpdatable { }); // Redo - UndoRedo.onRedo = (e) => { - this.core.onGlobalPropertyChange.notifyObservers({ baseObject: e.baseObject, object: e.object, property: e.property, value: e.to, initialValue: e.from }); - Tools.SetWindowTitle(this.projectFileName + ' *'); - }; + UndoRedo.onRedo = (e) => Tools.SetWindowTitle(this.projectFileName + ' *'); document.addEventListener('keyup', (ev) => { if (!CodeEditor.HasOneFocused() && ev.ctrlKey && ev.key === 'y') { UndoRedo.Redo(); diff --git a/src/editor/gui/edition.ts b/src/editor/gui/edition.ts index 626cc55c8..7215ef982 100644 --- a/src/editor/gui/edition.ts +++ b/src/editor/gui/edition.ts @@ -1,7 +1,8 @@ import { Color3, Color4, Vector2, Vector3, Vector4, - BaseTexture, CubeTexture + BaseTexture, CubeTexture, + Scene } from 'babylonjs'; import * as dat from 'dat-gui'; @@ -218,9 +219,7 @@ export default class Edition { * @param object the object which has a texture * @param callback: called when changed texture */ - public addTexture(parent: dat.GUI, editor: Editor, property: string, object: any, allowCubes: boolean = false, onlyCubes: boolean = false, callback?: (texture: BaseTexture) => void): dat.GUIController { - const scene = editor.core.scene; - + public addTexture(parent: dat.GUI, editor: Editor, scene: Scene, property: string, object: any, allowCubes: boolean = false, onlyCubes: boolean = false, callback?: (texture: BaseTexture) => void): dat.GUIController { const textures = ['None']; scene.textures.forEach(t => { const isCube = t instanceof CubeTexture; diff --git a/src/editor/particles/asset-component.ts b/src/editor/particles/asset-component.ts new file mode 100644 index 000000000..425c98688 --- /dev/null +++ b/src/editor/particles/asset-component.ts @@ -0,0 +1,138 @@ +import { Tools, AbstractMesh, PickingInfo, Mesh, ParticleSystem, Tags } from 'babylonjs'; +import { IAssetComponent, AssetElement } from '../../extensions/typings/asset'; + +import Editor from '../editor'; +import SceneFactory from '../scene/scene-factory'; + +export interface ParticlesCreatorMetadata { + name: string; + psData: any; +} + +export default class ParticlesAssetComponent implements IAssetComponent { + // Public members + public id: string = 'particles'; + public assetsCaption: string = 'Particles'; + + public datas: AssetElement[] = []; + + /** + * Constructor + * @param scene: the babylonjs scene + */ + constructor (public editor: Editor) + { } + + /** + * On the user renames the asset + * @param asset the asset being renamed + * @param name the new name of the asset + */ + public onRenameAsset (asset: AssetElement, name: string): void { + asset.data.name = name; + } + + /** + * On the user wants to remove the asset + * @param asset the asset to remove + */ + public onRemoveAsset (asset: AssetElement): void { + // Remove from library + const index = this.datas.indexOf(asset); + if (index !== -1) + this.datas.splice(index, 1); + + // Update assets + this.editor.assets.refresh(this.id); + } + + /** + * On the user adds an asset + * @param asset the asset to add + */ + public onAddAsset (asset: AssetElement): void { + this.datas.push(asset); + } + + /** + * Creates a new particle systems set asset + */ + public async onCreateAsset (name: string): Promise> { + const set = await new Promise((resolve) => { + Tools.LoadFile('./assets/templates/particles-creator/default-set.json', (data) => resolve( data)); + }); + + const asset = { + name: name, + data: { + name: name, + psData: JSON.parse(set) + } + }; + this.datas.push(asset); + + return asset; + } + + /** + * On get all the assets to be drawn in the assets component + */ + public onGetAssets (): AssetElement[] { + return this.datas; + } + + /** + * On the user double clicks on asset + * @param asset the asset being double-clicked by the user + */ + public onDoubleClickAsset (asset: AssetElement): void { + this.editor.addEditPanelPlugin('particles-creator', false, 'Particles System Creator...', asset.data); + } + + /** + * On the user drops an asset in the scene + * @param targetMesh the mesh under the pointer + * @param asset the asset being dropped + * @param pickInfo the pick info once the user dropped the asset + */ + public onDragAndDropAsset (targetMesh: AbstractMesh, asset: AssetElement, pickInfo: PickingInfo): void { + const m = new Mesh(asset.name, this.editor.core.scene); + m.position = pickInfo.pickedPoint.clone(); + SceneFactory.AddToGraph(this.editor, m); + + asset.data.psData.systems.forEach(s => { + const rootUrl = s.textureName ? (s.textureName.indexOf('data:') === 0 ? '' : 'file:') : ''; + const ps = ParticleSystem.Parse(s, this.editor.core.scene, rootUrl, true); + ps.id = Tools.RandomId(); + ps.emitter = m; + ps.preventAutoStart = false; + ps.start(); + + Tags.AddTagsTo(ps, 'added'); + this.editor.graph.add({ + id: ps.id, + data: ps, + img: 'icon-particles', + text: ps.name + }, m.id); + }); + } + + /** + * Called by the editor when serializing the scene + */ + public onSerializeAssets (): AssetElement[] { + return this.datas.map(d => ({ + name: d.name, + data: d.data + })); + } + + /** + * On the user loads the editor project + * @param data the previously saved data + */ + public onParseAssets (data: AssetElement[]): void { + this.datas = data; + } +} diff --git a/src/editor/project/project-exporter.ts b/src/editor/project/project-exporter.ts index 8adf4a256..3991cee55 100644 --- a/src/editor/project/project-exporter.ts +++ b/src/editor/project/project-exporter.ts @@ -234,7 +234,7 @@ export default class ProjectExporter { // Usable assets for extensions project.customMetadatas['AssetsExtension'] = { }; - const usableAssets = ['prefabs']; + const usableAssets = ['prefabs', 'particles']; usableAssets.forEach(ua => { const assets = project.assets[ua]; if (!assets) diff --git a/src/editor/scene/scene-icons.ts b/src/editor/scene/scene-icons.ts index 2a13c4389..3d8096938 100644 --- a/src/editor/scene/scene-icons.ts +++ b/src/editor/scene/scene-icons.ts @@ -72,9 +72,9 @@ export default class SceneIcons { } /** - * On before render the scene + * On post update the scenes */ - public onPreUpdate (): void { + public onPostUpdate (): void { const scene = this.editor.core.scene; // Cameras @@ -104,13 +104,6 @@ export default class SceneIcons { this._lastSoundsCount = sounds.length; this.createPlanes(this.soundsPlanes, this.soundMaterial, this._lastSoundsCount); } - } - - /** - * On post update the scenes - */ - public onPostUpdate (): void { - const scene = this.editor.core.scene; // Render helpers scene this.scene.activeCamera = this.editor.core.scene.activeCamera; @@ -148,10 +141,6 @@ export default class SceneIcons { }); // Sounds - const sounds: Sound[] = []; - if (scene.soundTracks) - scene.soundTracks.forEach(st => st.soundCollection.forEach(s => s.spatialSound && sounds.push(s))); - sounds.forEach((s, index) => { const plane = this.soundsPlanes[index]; plane.metadata.object = s; diff --git a/src/editor/scene/scene-loader.ts b/src/editor/scene/scene-loader.ts index 50424a8b2..34413b0fc 100644 --- a/src/editor/scene/scene-loader.ts +++ b/src/editor/scene/scene-loader.ts @@ -102,7 +102,8 @@ export default class SceneLoader { Tools.ImportScript('material-editor'), Tools.ImportScript('post-process-editor'), Tools.ImportScript('post-processes'), - Tools.ImportScript('path-finder') + Tools.ImportScript('path-finder'), + Tools.ImportScript('particles-creator') ]); editor.layout.unlockPanel('main'); diff --git a/src/editor/tools/gltf-tools.ts b/src/editor/tools/gltf-tools.ts index e50b52b1b..8714ea667 100644 --- a/src/editor/tools/gltf-tools.ts +++ b/src/editor/tools/gltf-tools.ts @@ -4,8 +4,9 @@ import { BaseTexture } from 'babylonjs'; -import Editor from "../editor"; -import Tools from "./tools"; +import Editor from '../editor'; +import Tools from './tools'; +import GraphicsTools from './graphics-tools'; export default class GLTFTools { /** @@ -35,37 +36,10 @@ export default class GLTFTools { // Configure now tex['url'] = tex.name = tex.name + '.png'; - // Retrieve pixels - const dimensions = tex.getBaseSize(); - const pixels = - tex.textureType === Engine.TEXTURETYPE_UNSIGNED_INT ? - tex.readPixels() as Uint8Array : - tex.readPixels() as Float32Array; - - const canvas = document.createElement('canvas'); - canvas.width = dimensions.width; - canvas.height = dimensions.height; - - const imageData = new ImageData(new Uint8ClampedArray(pixels.buffer), tex.getBaseSize().width, tex.getBaseSize().height); - - const context = canvas.getContext("2d"); - context.putImageData(imageData, 0, 0); - - if (canvas.toBlob) { - const blob = await this._ToBlob(canvas); - blob['name'] = tex.name; - Tags.AddTagsTo(blob, 'doNotExport'); - FilesInputStore.FilesToLoad[tex.name.toLowerCase()] = blob; - } - - context.restore(); - canvas.remove(); - } - - // Converts the canvas data to blob - private static async _ToBlob (canvas: HTMLCanvasElement): Promise { - return new Promise((resolve) => { - BabylonTools.ToBlob(canvas, b => resolve(b)); - }); + // Get blob + const blob = await GraphicsTools.TextureToFile(tex); + blob['name'] = tex.name; + Tags.AddTagsTo(blob, 'doNotExport'); + FilesInputStore.FilesToLoad[tex.name.toLowerCase()] = blob; } } diff --git a/src/editor/tools/graphics-tools.ts b/src/editor/tools/graphics-tools.ts new file mode 100644 index 000000000..dc8d7f9df --- /dev/null +++ b/src/editor/tools/graphics-tools.ts @@ -0,0 +1,42 @@ +import { Engine, BaseTexture, FilesInputStore, Tags, Tools as BabylonTools } from 'babylonjs'; + +export default class GraphicsTools { + /** + * Configures the given texture to retrieve its pixels and create a new file (blob) + * @param tex the texture to transform to a blob + */ + public static async TextureToFile (tex: BaseTexture): Promise { + // Retrieve pixels + const dimensions = tex.getBaseSize(); + const pixels = + tex.textureType === Engine.TEXTURETYPE_UNSIGNED_INT ? + tex.readPixels() as Uint8Array : + tex.readPixels() as Float32Array; + + const canvas = document.createElement('canvas'); + canvas.width = dimensions.width; + canvas.height = dimensions.height; + + const imageData = new ImageData(new Uint8ClampedArray(pixels.buffer), tex.getBaseSize().width, tex.getBaseSize().height); + + const context = canvas.getContext("2d"); + context.putImageData(imageData, 0, 0); + + const blob = await this.CanvasToBlob(canvas); + + context.restore(); + canvas.remove(); + + return blob; + } + + /** + * Converts the given canvas data to blob + * @param canvas the canvas to take its data and convert to a blob + */ + public static async CanvasToBlob (canvas: HTMLCanvasElement): Promise { + return new Promise((resolve) => { + BabylonTools.ToBlob(canvas, b => resolve(b)); + }); + } +} diff --git a/src/editor/tools/tools.ts b/src/editor/tools/tools.ts index 7f32c33dc..f90becff8 100644 --- a/src/editor/tools/tools.ts +++ b/src/editor/tools/tools.ts @@ -330,6 +330,14 @@ export default class Tools { return target; } + /** + * Deep clones the given object. Take care of cycling objects! + * @param data the data of the object to clone + */ + public static Clone (data: T): T { + return JSON.parse(JSON.stringify(data)); + } + /** * Reads the given file * @param file the file to read diff --git a/src/extensions/assets/assets.ts b/src/extensions/assets/assets.ts index 1b66463c6..d17e86e0e 100644 --- a/src/extensions/assets/assets.ts +++ b/src/extensions/assets/assets.ts @@ -1,4 +1,4 @@ -import { Node, Scene, Mesh } from 'babylonjs'; +import { Node, Scene, Mesh, ParticleSystemSet, Vector3, ParticleSystem } from 'babylonjs'; import Extensions from '../extensions'; import Extension from '../extension'; @@ -11,11 +11,16 @@ import { IStringDictionary } from '../typings/typings'; */ export interface ProjectAssets { prefabs: AssetElement[]; + particles: AssetElement[]; } export default class AssetsExtension extends Extension { // Public members public prefabs: AssetElement[] = []; + public particles: AssetElement[] = []; + + // Private members + private _particlesInstances: IStringDictionary = { }; /** * Constructor @@ -30,7 +35,8 @@ export default class AssetsExtension extends Extension { */ public onApply (data: ProjectAssets): void { // Prefabs - data.prefabs.forEach(p => this.prefabs.push(p)); + data.prefabs && data.prefabs.forEach(p => this.prefabs.push(p)); + data.particles && data.particles.forEach(p => this.particles.push(p)); } /** @@ -72,6 +78,39 @@ export default class AssetsExtension extends Extension { return null; } + /** + * Instantiates a particle system set identified by the given name + * @param name the name of the particle system set to instantiate + * @param position the position where to start systems + */ + public instantiateParticleSystemsSet (name: string, position?: Vector3): ParticleSystemSet { + for (const ps of this.particles) { + if (ps.name !== name) + continue; + + let set = this._particlesInstances[name]; + if (!set) { + set = new ParticleSystemSet(); + ps.data.psData.systems.forEach(s => { + const ps = ParticleSystem.Parse(s, this.scene, Extensions.RoolUrl, true); + set.systems.push(ps); + }); + + this._particlesInstances[name] = set; + } + + if (position) { + set.systems.forEach(s => s.emitter = position); + } + + this._particlesInstances[name] = set; + + return set; + } + + return null; + } + // Creates a new instance of the given node (InstancedMesh or just clone) private _createInstance (node: any): Node { if (node instanceof Mesh) diff --git a/src/extensions/index.ts b/src/extensions/index.ts index 637fb1ebd..1c15f65a5 100644 --- a/src/extensions/index.ts +++ b/src/extensions/index.ts @@ -5,7 +5,6 @@ import AssetsExtension from './assets/assets'; import CodeExtension from './behavior/code'; import GraphExtension, { LGraph, LGraphCanvas, LiteGraph, LiteGraphNode, LGraphGroup } from './behavior/graph'; import PathFinderExtension from './path-finder/index'; -import ParticlesCreatorExtension from './particles-creator/particles-creator'; import PostProcessEditorExtension from './post-process-editor/post-process-editor'; import MaterialEditorExtension from './material-editor/material-editor'; import PostProcessExtension from './post-process/post-processes'; @@ -24,7 +23,6 @@ export { MaterialEditorExtension, PathFinderExtension, PostProcessEditorExtension, - ParticlesCreatorExtension, CustomMetadatasExtension, IExtension, ExtensionConstructor diff --git a/src/extensions/particles-creator/particles-creator.ts b/src/extensions/particles-creator/particles-creator.ts deleted file mode 100644 index c195f3278..000000000 --- a/src/extensions/particles-creator/particles-creator.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { Scene, Tools, ParticleSystem, Effect } from 'babylonjs'; - -import Extensions from '../extensions'; -import Extension from '../extension'; - -import { IStringDictionary } from '../typings/typings'; - -export interface ParticlesCreatorMetadata { - id: string; - apply: boolean; - code: string; - compiledCode?: string; - vertex: string; - pixel: string; -} - -const template = ` -EDITOR.ParticlesCreator.Constructors['{{name}}'] = function (scene, particleSystem) { -{{code}} -} -`; - -// Set EDITOR on Window -export module EDITOR { - export class ParticlesCreator { - public static Constructors = { }; - } -} -window['EDITOR'] = window['EDITOR'] || { }; -window['EDITOR'].ParticlesCreator = EDITOR.ParticlesCreator; - -export default class ParticlesCreatorExtension extends Extension { - // Public members - public instances: IStringDictionary = { }; - - /** - * Constructor - * @param scene: the babylonjs scene - */ - constructor (scene: Scene) { - super(scene); - this.datas = []; - } - - /** - * On apply the extension - */ - public onApply (data: ParticlesCreatorMetadata[], rootUrl?: string): void { - this.datas = data; - - // Add custom code - data.forEach(d => { - if (!d.apply) - return; - - const id = d.id + Tools.RandomId(); - - // Get particle system - const ps = this.scene.getParticleSystemByID(d.id); - if (!ps) - return; - - // Url - let url = window.location.href; - url = url.replace(Tools.GetFilename(url), '') + 'particle-systems/' + d.id.replace(/ /g, '') + '.js'; - - // Add script - Extension.AddScript(template.replace('{{name}}', id).replace('{{code}}', d.compiledCode || d.code), url); - - // Create code - const ctor = new EDITOR.ParticlesCreator.Constructors[id](this.scene, ps); - const code = new (ctor.ctor || ctor)(); - - // Create effect - const uniforms: string[] = []; - const samplers: string[] = []; - const defines: string[] = []; - code.setUniforms(uniforms, samplers); - code.setDefines(defines); - - Effect.ShadersStore[id + 'VertexShader'] = d.vertex; - Effect.ShadersStore[id + 'PixelShader'] = d.pixel; - - const effect = this.scene.getEngine().createEffectForParticles(id, uniforms, samplers, defines.join('\n')); - effect.onBind = effect => code.onBind(effect); - - ps.customShader = effect; - - // Save instance - this.instances[d.id] = code; - }); - } - - /** - * Called by the editor when serializing the scene - */ - public onSerialize (): ParticlesCreatorMetadata[] { - const datas: ParticlesCreatorMetadata[] = []; - this.scene.particleSystems.forEach(ps => { - if (ps['metadata'] && ps['metadata'].particlesCreator) { - ps['metadata'].particlesCreator.id = ps.id; - datas.push(ps['metadata'].particlesCreator); - } - }); - - return datas; - } - - /** - * On load the extension (called by the editor when - * loading a scene) - */ - public onLoad (data: ParticlesCreatorMetadata[]): void { - data.forEach(d => { - const ps = this.scene.getParticleSystemByID(d.id); - if (!ps) - return; - - ps['metadata'] = ps['metadata'] || { }; - ps['metadata'].particlesCreator = d; - }); - } -} - -// Register -Extensions.Register('ParticlesCreatorExtension', ParticlesCreatorExtension); diff --git a/src/extensions/path-finder/index.ts b/src/extensions/path-finder/index.ts index f0a832f4e..ef1803997 100644 --- a/src/extensions/path-finder/index.ts +++ b/src/extensions/path-finder/index.ts @@ -52,8 +52,7 @@ export default class PathFinderExtension extends Extension } /** - * On load the extension (called by the editor when - * loading a scene) + * On load the extension (called by the editor when loading a scene) */ public onLoad (data: PathFinderMetadata[]): void { this.datas = data; diff --git a/src/extensions/tools/tools.ts b/src/extensions/tools/tools.ts index 72f8519f7..131ad52d7 100644 --- a/src/extensions/tools/tools.ts +++ b/src/extensions/tools/tools.ts @@ -1,4 +1,4 @@ -import { Node, Scene, ParticleSystem, FilesInputStore } from 'babylonjs'; +import { Node, Scene, ParticleSystem, FilesInputStore, ParticleSystemSet, Vector3 } from 'babylonjs'; import Extensions from '../extensions'; import AssetsExtension from '../assets/assets'; @@ -107,7 +107,7 @@ export default class Tools { } /** - * Instantiate a prefab identified by the given name + * Instantiates a prefab identified by the given name * @param name the name of the prefab to instantiate */ public instantiatePrefab (name: string): T { @@ -115,6 +115,16 @@ export default class Tools { return ext.instantiatePrefab(name); } + /** + * Instantiates a particle system set identified by the given name + * @param name the name of the particle system set to instantiate + * @param position the position where to start systems + */ + public instantiateParticleSystemSet (name: string, position?: Vector3): ParticleSystemSet { + const ext = Extensions.Instances['AssetsExtension']; + return ext.instantiateParticleSystemsSet(name, position); + } + /** * Calls the given method with the given parameters on the given object which has scripts providing the given method * @param object the object reference where to send the message by calling the given method name diff --git a/src/index.ts b/src/index.ts index ea99d9e93..e92460196 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,7 @@ import Tools from './editor/tools/tools'; import Request from './editor/tools/request'; import UndoRedo from './editor/tools/undo-redo'; import ThemeSwitcher, { ThemeType } from './editor/tools/theme'; +import GraphicsTools from './editor/tools/graphics-tools'; import Layout from './editor/gui/layout'; import Toolbar from './editor/gui/toolbar'; @@ -35,6 +36,10 @@ import ScenePreview from './editor/scene/scene-preview'; import PrefabAssetComponent from './editor/prefabs/asset-component'; import { Prefab, PrefabNodeType } from './editor/prefabs/prefab'; +import ParticlesCreatorExtension, { ParticlesCreatorMetadata } from './editor/particles/asset-component'; + +import Storage from './editor/storage/storage'; + import VSCodeSocket from './editor/vscode/vscode-socket'; export default Editor; @@ -45,6 +50,7 @@ export { Request, UndoRedo, ThemeSwitcher, ThemeType, + GraphicsTools, IStringDictionary, INumberDictionary, @@ -79,5 +85,10 @@ export { PrefabAssetComponent, Prefab, PrefabNodeType, + ParticlesCreatorExtension, + ParticlesCreatorMetadata, + + Storage, + VSCodeSocket } diff --git a/src/tools/animations/editor.ts b/src/tools/animations/editor.ts index 027acc30d..0772e5800 100644 --- a/src/tools/animations/editor.ts +++ b/src/tools/animations/editor.ts @@ -167,10 +167,14 @@ export default class AnimationEditor extends EditorPlugin { // No data text this.noDataText = this.paper.text(0, 0, 'No Animation Selected'); + this.noDataText.node.style.pointerEvents = 'none'; + this.noDataText.node.style.userSelect = 'none'; this.noDataText.attr('font-size', 64); // Value text this.valueText = this.paper.text(0, 0, '0.0'); + this.valueText.node.style.pointerEvents = 'none'; + this.valueText.node.style.userSelect = 'none'; this.valueText.attr('font-size', 10); this.valueText.hide(); @@ -440,9 +444,6 @@ export default class AnimationEditor extends EditorPlugin { * @param object: the IAnimatable object */ protected objectSelected(object: IAnimatable): void { - if (this.animatable === object) - return; - this.toolbar.element.disable('remove-animation'); // Clean @@ -618,6 +619,8 @@ export default class AnimationEditor extends EditorPlugin { // Text const text = this.paper.text(x, 30, (((x * maxFrame) / this.paper.width)).toFixed(2)); + text.node.style.pointerEvents = 'none'; + text.node.style.userSelect = 'none'; text.attr('opacity', 0.4); this.timelineTexts.push(text); } @@ -645,6 +648,8 @@ export default class AnimationEditor extends EditorPlugin { if (i > 0) { const text = this.paper.text(30, y, currentValue.toFixed(2)); text.attr('opacity', 0.4); + text.node.style.pointerEvents = 'none'; + text.node.style.userSelect = 'none'; this.timelineTexts.push(text); } @@ -856,6 +861,9 @@ export default class AnimationEditor extends EditorPlugin { this.updateGraph(animation); } }); + + // Notify + this.editor.core.onModifiedObject.notifyObservers(this.animatable); }; data.point.drag(onMove, onStart, onEnd); @@ -918,7 +926,7 @@ export default class AnimationEditor extends EditorPlugin { * On click on the timeline */ protected onClickTimeline(maxFrame: number): void { - this.timeline.click((ev: MouseEvent) => { + this.timeline.mousedown((ev: MouseEvent) => { if (this.isPlaying) return; diff --git a/src/tools/animations/property-browser.ts b/src/tools/animations/property-browser.ts index 3aeba70f4..b8d9a55e3 100644 --- a/src/tools/animations/property-browser.ts +++ b/src/tools/animations/property-browser.ts @@ -3,8 +3,8 @@ import { Vector2, Vector3, Vector4, Color3, Color4, Quaternion, - Camera, - Material + Camera, ParticleSystem, + Material, } from 'babylonjs'; import { Window, Graph, GraphNode, Tools } from 'babylonjs-editor'; @@ -150,7 +150,7 @@ export default class PropertyBrowser { continue; const id = `${rootName === '' ? '' : (rootName + '.')}${thing}`; - const allowed = this._allowedTypes.indexOf(ctor) !== -1; + const allowed = (thing === 'emitter' && root instanceof ParticleSystem) || (this._allowedTypes.indexOf(ctor) !== -1); const deep = this._deepTypes.find(dt => value instanceof dt); if (!allowed && !deep) diff --git a/src/tools/helpers.ts b/src/tools/helpers.ts index 875167ac9..8510f438d 100644 --- a/src/tools/helpers.ts +++ b/src/tools/helpers.ts @@ -1,7 +1,22 @@ +import { Engine, Scene, ArcRotateCamera, Vector3 } from 'babylonjs'; import { Editor, CodeEditor } from 'babylonjs-editor'; import Extensions from '../extensions/extensions'; -import CodeExtension from '../extensions/behavior/code'; + +import CodeExtension, { BehaviorCode } from '../extensions/behavior/code'; +import { GraphData } from '../extensions/behavior/graph'; + +export interface SceneMetadata { + behaviorScripts?: BehaviorCode[]; + behaviorGraphs?: GraphData[]; + [index: string]: any; +} + +export interface Preview { + engine: Engine; + scene: Scene; + camera: ArcRotateCamera; +} export default class Helpers { /** @@ -43,4 +58,31 @@ export default class Helpers { }); } } + + /** + * Returns the scene's metadatas. If empty, default object is created + * @param scene the scene to retrieve its metadatas + */ + public static GetSceneMetadatas (scene: Scene): SceneMetadata { + scene.metadata = scene.metadata || { }; + return scene.metadata; + } + + /** + * Creates a new preview object by creating a new engine, scene and camera + * @param canvas the canvas where to render the created scene + */ + public static CreatePreview (canvas: HTMLCanvasElement): Preview { + const engine = new Engine(canvas); + const scene = new Scene(engine); + + const camera = new ArcRotateCamera('PreviewCamera', 0, 0, 10, Vector3.Zero(), scene, true); + camera.attachControl(canvas, true, false); + + return { + engine: engine, + scene: scene, + camera: camera + }; + } } diff --git a/src/tools/particles-creator/index.ts b/src/tools/particles-creator/index.ts index c208d32c5..759fb6e34 100644 --- a/src/tools/particles-creator/index.ts +++ b/src/tools/particles-creator/index.ts @@ -1,43 +1,51 @@ -import { ParticleSystem, Effect, Observer } from 'babylonjs'; +import { + ParticleSystemSet, Observer, ParticleSystem, Tools as BabylonTools, + FilesInputStore, Vector3, ParticleHelper, FactorGradient +} from 'babylonjs'; import Editor, { - EditorPlugin, - - Layout, - CodeEditor, - Tools, - Toolbar + EditorPlugin, Tools, + Layout, Toolbar, Tree, + Dialog, UndoRedo, GraphicsTools, + Storage, ParticlesCreatorMetadata, INumberDictionary } from 'babylonjs-editor'; -import '../../extensions/particles-creator/particles-creator'; -import { ParticlesCreatorMetadata } from '../../extensions/particles-creator/particles-creator'; -import Extensions from '../../extensions/extensions'; +import Helpers, { Preview } from '../helpers'; + +import Timeline from './timeline'; export default class ParticlesCreator extends EditorPlugin { // Public members public layout: Layout = null; public toolbar: Toolbar = null; + public tree: Tree = null; + public tabs: W2UI.W2Tabs = null; - public functionsCode: CodeEditor = null; - public pixelCode: CodeEditor = null; - public vertexCode: CodeEditor = null; + public undoRedoId: string = 'particles-creator'; // Protected members protected data: ParticlesCreatorMetadata = null; - protected currentTab: string = 'PARTICLES-CREATOR-FUNCTIONS'; - protected selectedObjectEvent: Observer = null; + protected preview: Preview = null; + protected set: ParticleSystemSet = null; + + protected timeline: Timeline = null; + protected currentParticleSystem: ParticleSystem = null; + protected onSelectAssetObserver: Observer = null; + + // Private members + private _modifyingObjectObserver: Observer; + private _modifiedObjectObserver: Observer; // Static members - public static DefaultCode: string = ''; - public static DefaultVertex: string = ''; - public static DefaultPixel: string = ''; + public static DefaultSet: any = null; /** * Constructor * @param name: the name of the plugin */ - constructor(public editor: Editor) { - super('Particles Creator'); + constructor(public editor: Editor, asset: ParticlesCreatorMetadata = null) { + super('Particle Systems Creator'); + this.data = asset; } /** @@ -47,11 +55,14 @@ export default class ParticlesCreator extends EditorPlugin { this.layout.element.destroy(); this.toolbar.element.destroy(); - this.functionsCode.dispose(); - this.vertexCode.dispose(); - this.pixelCode.dispose(); + this.preview.scene.dispose(); + this.preview.engine.dispose(); + + this.timeline.dispose(); - this.editor.core.onSelectObject.remove(this.selectedObjectEvent); + this.editor.core.onSelectAsset.remove(this.onSelectAssetObserver); + this.editor.core.onModifyingObject.remove(this._modifyingObjectObserver); + this.editor.core.onModifiedObject.remove(this._modifiedObjectObserver); await super.close(); } @@ -60,88 +71,120 @@ export default class ParticlesCreator extends EditorPlugin { * Creates the plugin */ public async create(): Promise { - // Template - !ParticlesCreator.DefaultCode && (ParticlesCreator.DefaultCode = await Tools.LoadFile('./assets/templates/particles-creator/class.ts')); - !ParticlesCreator.DefaultVertex && (ParticlesCreator.DefaultVertex = await Tools.LoadFile('./assets/templates/particles-creator/shader.vertex.fx')); - !ParticlesCreator.DefaultPixel && (ParticlesCreator.DefaultPixel = await Tools.LoadFile('./assets/templates/particles-creator/shader.fragment.fx')); - + // Default + if (!ParticlesCreator.DefaultSet) { + const set = await Tools.LoadFile('./assets/templates/particles-creator/default-set.json', false); + ParticlesCreator.DefaultSet = JSON.parse(set); + } + // Layout this.layout = new Layout(this.divElement.id); this.layout.panels = [ - { type: 'top', size: 32, resizable: false, content: `
` }, + { type: 'top', size: 30, resizable: false, content: `
` }, { - type: 'main', - resizable: false, + type: 'left', + size: '50%', + resizable: false, content: ` -
- - - `, - tabs: [ - { id: 'functions', caption: 'Functions' }, - { id: 'vertex', caption: 'Vertex Shader' }, - { id: 'pixel', caption: 'Pixel Shader' } +
+
`, + tabs: [ + { id: 'tree', caption: 'List' }, + { id: 'timeline', caption: 'Timeline' } ] - } + }, + { type: 'main', size: '50%', resizable: false, content: `` }, ]; this.layout.build(this.divElement.id); + // Tabs + this.tabs = this.layout.getPanelFromType('left').tabs; + this.tabs.on('click', (ev) => this.tabChanged(ev.target)); + this.tabs.select('timeline'); + this.tabChanged('timeline'); + // Toolbar this.toolbar = new Toolbar('PARTICLES-CREATOR-TOOLBAR'); this.toolbar.items = [ - { type: 'button', id: 'apply', img: 'icon-play-game-windowed', text: 'Apply' }, + { id: 'add', text: 'Add System...', caption: 'Add System...', img: 'icon-add' }, + { id: 'reset', text: 'Reset', caption: 'Reset', img: 'icon-play-game' }, + { type: 'break' }, + { id: 'export', text: 'Export As...', caption: 'Export As...', img: 'icon-export' }, { type: 'break' }, - { type: 'button', img: 'icon-add', text: 'Import From...' } + { id: 'preset', type: 'menu', text: 'Presets', caption: 'Presets', img: 'icon-particles', items: [ + { id: 'smoke', text: 'Smoke', caption: 'Smoke' }, + { id: 'sun', text: 'Sun', caption: 'Sun' }, + // { id: 'rain', text: 'Rain', caption: 'Rain' }, + { id: 'fire', text: 'Fire', caption: 'Fire' }, + { id: 'explosion', text: 'Explosion', caption: 'Explosion' } + ] } ]; this.toolbar.onClick = id => this.toolbarClicked(id); this.toolbar.build('PARTICLES-CREATOR-TOOLBAR'); + this.toolbar.items.forEach(i => this.toolbar.enable(i.id, false)); - // Create editors - this.functionsCode = new CodeEditor('typescript', ''); - await this.functionsCode.build('PARTICLES-CREATOR-FUNCTIONS'); - this.functionsCode.onChange = value => { - if (this.data) { - this.data.code = value; - this.data.compiledCode = this.functionsCode.transpileTypeScript(value, this.data.id.replace(/ /, '')); - } - }; + // Create tree + this.tree = new Tree('PARTICLES-CREATOR-TREE'); + this.tree.wholerow = true; + this.tree.onClick = ( (id, data) => { + this.currentParticleSystem = data; + this.editor.core.onSelectObject.notifyObservers(data); + }); + this.tree.onCanDrag = () => false; + this.tree.onRename = ( (id, name, data) => { + this.currentParticleSystem.name = name; + this.resetSet(true); + return true; + }); + this.tree.onContextMenu = ( (id, data) => { + return [ + { id: 'remove', text: 'Remove', callback: () => this.removeSystemFromSet(data) } + ]; + }); + this.tree.build('PARTICLES-CREATOR-TREE'); - this.vertexCode = new CodeEditor('cpp', ''); - await this.vertexCode.build('PARTICLES-CREATOR-VERTEX'); - this.vertexCode.onChange = value => this.data && (this.data.vertex = value); + // Create timeline + this.timeline = new Timeline(this, $('#PARTICLES-CREATOR-TIMELINE')[0]); + + // Create preview + this.preview = Helpers.CreatePreview( $('#PARTICLES-CREATOR-CANVAS')[0]); + this.preview.camera.wheelPrecision = 100; + this.preview.engine.runRenderLoop(() => this.preview.scene.render()); - this.pixelCode = new CodeEditor('cpp', ''); - await this.pixelCode.build('PARTICLES-CREATOR-PIXEL'); - this.pixelCode.onChange = value => this.data && (this.data.pixel = value); - // Events - this.selectedObjectEvent = this.editor.core.onSelectObject.add(obj => this.selectObject(obj)); - - this.layout.getPanelFromType('main').tabs.on('click', (ev) => { - $('#' + this.currentTab).hide(); - this.currentTab = 'PARTICLES-CREATOR-' + ev.target.toUpperCase(); - $('#' + this.currentTab).show(); + this.onSelectAssetObserver = this.editor.core.onSelectAsset.add((a) => this.selectAsset(a)); + this._modifyingObjectObserver = this.editor.core.onModifyingObject.add((o: ParticleSystem) => { + if (!this.data) + return; + + this.timeline.onModifyingSystem(o); + this.saveSet(); }); + this._modifiedObjectObserver = this.editor.core.onModifiedObject.add((o: ParticleSystem) => { + if (!this.data) + return; - // Extension - Extensions.RequestExtension(this.editor.core.scene, 'ParticlesCreatorExtension'); + this.timeline.onModifiedSystem(o); + this.saveSet(); + }); - // Finish - this.selectObject(this.editor.core.currentSelectedObject); + // Select asset + if (this.data) + this.selectAsset(this.data); } /** * On hide the plugin (do not render scene) */ - public async onHide (): Promise { + public onHide (): void { } /** * On show the plugin (render scene) */ - public async onShow (): Promise { - + public onShow (asset?: ParticlesCreatorMetadata): void { + } /** @@ -149,56 +192,328 @@ export default class ParticlesCreator extends EditorPlugin { */ public onResize (): void { this.layout.element.resize(); + this.preview.engine.resize(); + + const size = this.layout.getPanelSize('left'); + this.timeline.resize(size.width, size.height); + } + + /** + * Called on the user clicks on an item of the toolbar + * @param id the id of the clicked item + */ + protected async toolbarClicked (id: string): Promise { + switch (id) { + // Add a new particle systems set + case 'add': + const name = await Dialog.CreateWithTextInput('Particle System name?'); + await this.addSystemToSet(ParticlesCreator.DefaultSet.systems[0], name); + this.saveSet(); + this.resetSet(true); + break; + // Reset particle systems set + case 'reset': + this.resetSet(true); + break; + + // Export + case 'export': + this.exportSet(); + break; + + // Presets + case 'preset:smoke': this.createFromPreset('smoke'); break; + case 'preset:sun': this.createFromPreset('sun'); break; + case 'preset:rain': this.createFromPreset('rain'); break; + case 'preset:fire': this.createFromPreset('fire'); break; + case 'preset:explosion': this.createFromPreset('explosion'); break; + } } /** - * On the user selects an object - * @param object the selected object + * Called on the user changes tab + * @param id the id of the new tab */ - public selectObject (object: any): void { - if (!(object instanceof ParticleSystem)) { - this.data = null; - this.functionsCode.setValue(''); - this.vertexCode.setValue(''); - this.pixelCode.setValue(''); - this.layout.lockPanel('main', 'No Particle System Selected...'); + protected tabChanged (id: string): void { + // Hide all + $('#PARTICLES-CREATOR-TREE').hide(); + $('#PARTICLES-CREATOR-TIMELINE').hide(); + + // Select which to show + switch (id) { + case 'tree': $('#PARTICLES-CREATOR-TREE').show(); break; + case 'timeline': $('#PARTICLES-CREATOR-TIMELINE').show(); break; + default: break; + } + } + + /** + * Called on the user selects an asset in the assets panel + * @param asset the asset being selected + */ + protected selectAsset (asset: ParticlesCreatorMetadata): void { + if (!asset || !asset.psData) { + // Lock toolbar + this.toolbar.items.forEach(i => this.toolbar.enable(i.id, false)); return; } - object['metadata'] = object['metadata'] || { }; - this.data = object['metadata'].particlesCreator || { - id: object.id, - apply: false, - code: ParticlesCreator.DefaultCode, - vertex: ParticlesCreator.DefaultVertex, - pixel: ParticlesCreator.DefaultPixel - }; - object['metadata'].particlesCreator = this.data; + // Unlock toolbar + this.toolbar.items.forEach(i => this.toolbar.enable(i.id, true)); + + // Misc. + this.data = asset; + + // Set + if (this.set) { + this.set.dispose(); + this.set = null; + } + + this.resetSet(true); + + // Timeline + this.timeline.setSet(this.set); - // Unlock and fill - this.layout.unlockPanel('main'); - this.functionsCode.setValue(this.data.code); - this.vertexCode.setValue(this.data.vertex); - this.pixelCode.setValue(this.data.pixel); + // Undo/Redo + UndoRedo.ClearScope(this.undoRedoId); } /** - * On the user clicks on the toolbar - * @param id the id of the clicked item + * Adds a new particle system to the current set according to the given data + * @param particleSystemData the particle system data to parse + */ + protected addSystemToSet (particleSystemData: any, name?: string): Promise { + if (!this.set) + return; + + // Replace id + particleSystemData.id = BabylonTools.RandomId(); + + return new Promise((resolve) => { + // Create system + const rootUrl = particleSystemData.textureName ? (particleSystemData.textureName.indexOf('data:') === 0 ? '' : 'file:') : ''; + + const ps = ParticleSystem.Parse(particleSystemData, this.preview.scene, rootUrl, true); + ps.emitter = ps.emitter || Vector3.Zero(); + ps.name = name || ps.name; + + this._cleanGradients(ps); + + // Add to set + this.set.systems.push(ps); + + // Resolve + if (ps.particleTexture && rootUrl === 'file:') + ps.particleTexture.onLoadObservable.add(tex => resolve(ps)); + else + resolve(ps); + }); + } + + /** + * Removes the given particle system from the current set + * @param ps the particle system to remove + */ + public removeSystemFromSet (ps: ParticleSystem): void { + // Remove from set + const index = this.set.systems.indexOf(ps); + if (index === -1) + return; + + this.set.systems.splice(index, 1); + + // Finally dispose + ps.dispose(); + + // Remove from tree + this.tree.remove(ps.id); + + // Save & reset + this.saveSet(); + this.resetSet(true); + } + + /** + * Resets the particle systems set + * @param fillTree wehter or not the list tree should be filled. */ - public toolbarClicked (id: string): void { + public async resetSet (fillTree: boolean = false): Promise { + if (!this.data) + return; + + // Const data + const data = this.set ? this.set.serialize() : this.data.psData; + + // Dispose previous set + if (this.set) { + this.set.dispose(); + this.preview.scene.particleSystems = []; + } + + // Clear tree? + if (fillTree) + this.tree.clear(); + + // Parse set + this.set = new ParticleSystemSet(); + for (const s of data.systems) { + const ps = await this.addSystemToSet(s); + if (fillTree) + this.tree.add({ id: ps.id, text: ps.name, data: ps, img: 'icon-particles' }); + } + + this.set.start(); + + if (this.currentParticleSystem) { + const ps = this.set.systems.find(ps => ps.name === this.currentParticleSystem.name); + if (ps) { + this.tree.select(ps.id); + this.editor.core.onSelectObject.notifyObservers(ps); + } + } + + // Refresh timeline + this.timeline.setSet(this.set); + } + + /** + * Saves the current particle systems set + */ + public saveSet (): void { + if (!this.data) + return; + + // Save! + this.data.psData = this.set.serialize(); + } + + /** + * Exports the current set + */ + protected async exportSet (): Promise { if (!this.data) return; - switch (id) { - // Apply - case 'apply': - const checked = this.toolbar.isChecked(id, true); - this.data.apply = checked; - this.toolbar.setChecked(id, checked); - break; - - default: break; + // Save + this.saveSet(); + + // Export + const serializationObject = this.set.serialize(); + + // Embed? + const embed = await Dialog.Create('Embed textures?', 'Do you want to embed textures in the set?'); + if (embed === 'Yes') { + for (const s of serializationObject.systems) { + const file = FilesInputStore.FilesToLoad[s.textureName.toLowerCase()]; + if (!file) + continue; + s.textureName = await Tools.ReadFileAsBase64(file); + } } + + // Save data + const json = JSON.stringify(serializationObject, null, '\t'); + const file = Tools.CreateFile(Tools.ConvertStringToUInt8Array(json), this.data.name + '.json'); + + // Embeded + if (embed === 'Yes') + return BabylonTools.Download(file, file.name); + + // Not embeded + const textureFiles: File[] = []; + for (const s of serializationObject.systems) { + const file = FilesInputStore.FilesToLoad[s.textureName.toLowerCase()]; + if (!file || textureFiles.indexOf(file) !== -1) + continue; + textureFiles.push(file); + } + + const storage = await Storage.GetStorage(this.editor); + await storage.openPicker('Choose destination folder...', [ + { name: 'systems', folder: [ + { name: file.name, file: file }, + { name: 'textures', folder: textureFiles.map(tf => ({ + name: tf.name, + file: tf + })) } + ] } + ]); + } + + /** + * Creates a new system set from the already know presets form babylonjs.com + * @param preset the preset id to create from the babylon.js presets + */ + protected async createFromPreset (preset: string): Promise { + // Ask override + const override = await Dialog.Create('Override current set?', 'Setting from a preset will override your current set configuration. Are you sure?'); + if (override === 'No') + return; + + // Lock panel + this.layout.lockPanel('top', 'Loading...', true); + + // Dispose existing set + if (this.set) + this.set.dispose(); + + // Create set! + this.set = await ParticleHelper.CreateAsync(preset, this.preview.scene); + this.set.systems.forEach(s => this._cleanGradients( s)); + + // Save textures + const promises: Promise[] = []; + for (const s of this.set.systems) { + promises.push(new Promise((resolve) => { + s.particleTexture.onLoadObservable.addOnce(async tex => { + const blob = await GraphicsTools.TextureToFile(tex); + const split = tex.name.split('/'); + const name = split[split.length - 1]; + + blob['name'] = tex.name = tex.url = name.toLowerCase(); + FilesInputStore.FilesToLoad[name.toLowerCase()] = blob; + + resolve(); + }); + })); + } + + await Promise.all(promises); + + // Save and reset + this.saveSet(); + this.resetSet(true); + + // Unlock + this.layout.unlockPanel('top'); + } + + /** + * Cleans the size gradients + * @param system the system to clean + * @todo remove this fix in future + */ + private _cleanGradients (system: ParticleSystem): void { + const sizeGradients = system['_sizeGradients']; + if (!sizeGradients) + return; + + const gradientsDict: INumberDictionary = { }; + sizeGradients.forEach(sg => { + gradientsDict[sg.gradient] = gradientsDict[sg.gradient] || []; + gradientsDict[sg.gradient].push(sg); + }); + + sizeGradients.splice(0, sizeGradients.length); + for (const k in gradientsDict) { + const g = gradientsDict[k]; + while (g.length > 1) + g.pop(); + + sizeGradients.push(g[0]) + } + + system['_sizeGradients'] = sizeGradients; } } diff --git a/src/tools/particles-creator/timeline.ts b/src/tools/particles-creator/timeline.ts new file mode 100644 index 000000000..e5acd72cb --- /dev/null +++ b/src/tools/particles-creator/timeline.ts @@ -0,0 +1,348 @@ +import * as Raphael from 'raphael'; +import { ParticleSystemSet, ParticleSystem, Observer } from 'babylonjs'; +import { ContextMenu } from 'babylonjs-editor'; + +import ParticlesCreator from './index'; + +export default class Timeline { + // Public members + public paper: RaphaelPaper; + public background: RaphaelElement; + public timeBackground: RaphaelElement; + public timeLines: RaphaelElement[] = []; + public playLine: RaphaelElement; + + public separators: RaphaelElement[] = []; + public systems: RaphaelElement[] = []; + public names: RaphaelElement[] = []; + public times: RaphaelElement[] = []; + + // Private members + private _maxX: number = 0; + private _backgroundX: number = 0; + private _set: ParticleSystemSet = null; + + // Static members + private static _Scale: number = 100; + + /** + * Constructor + * @param root the root element where to draw the timeline + */ + constructor (public creator: ParticlesCreator, root: HTMLDivElement) { + // Create paper + this.paper = Raphael(root, 0, 0); + + // Create background + this.background = this.paper.rect(0, 0, 1, 1); + this.background.attr('fill', '#aaa'); + this.background.attr('stroke', '#aaa'); + this._onMoveBackground(); + + // Create time background + this.timeBackground = this.paper.rect(0, 0, 1, 25); + this.timeBackground.attr('fill', '#777'); + this.timeBackground.attr('stroke', '#777'); + + // Play line + this.playLine = this.paper.rect(0, 0, 1, 1); + this.playLine.attr('fill', '#999'); + this.playLine.attr('stroke', '#999'); + } + + /** + * Disposes the timeline + */ + public dispose (): void { + this.paper.remove(); + } + + /** + * Resizes the timeline + * @param width the new width of the timeline + * @param height the new height of the timeline + */ + public resize (width: number, height: number): void { + this.paper.setSize(width, height); + + this.background.attr('width', width); + this.background.attr('height', height); + + this.timeBackground.attr('width', width); + + this.playLine.attr('height', height); + + this.setSet(this._set); + } + + /** + * Sets the current particle systems set + * @param set the new set to draw + */ + public setSet (set: ParticleSystemSet): void { + if (!set) + return; + + // Misc. + const shouldPlay = this._set !== set; + this._maxX = 0; + this._backgroundX = 0; + + // Save set + this._set = set; + + // Destroy all + this.systems.forEach(s => s.remove()); + this.systems = []; + + this.separators.forEach(s => s.remove()); + this.separators = []; + + this.names.forEach(n => n.remove()); + this.names = []; + + this.times.forEach(t => t.remove()); + this.times = []; + + this.timeLines.forEach(t => t.remove()); + this.timeLines = []; + + // Add systems + set.systems.forEach((s, i) => { + const index = i + 1; + + // Create new system element + const system = this.paper.rect(0, 40 * index + 1, 100, 35, 16); + system.attr('fill', '#ddd'); + system.attr('stroke-width', 0); + system.attr('x', (s.startDelay / 1000 * Timeline._Scale)); + system.data('bx', system.attr('x')); + this.systems.push(system); + + if (shouldPlay) { + const scaleFrom = Raphael.animation({ transform: 's1.25,1.25' }, 300); + const strokeFrom = Raphael.animation({ 'stroke-width': 5 }, 300); + const scaleTo = Raphael.animation({ transform: 's1,1' }, 300); + const strokeTo = Raphael.animation({ 'stroke-width': 0 }, 300); + + system.animate(scaleFrom.delay(s.startDelay)); + system.animate(strokeFrom.delay(s.startDelay)); + system.animate(scaleTo.delay(s.startDelay + 301)); + system.animate(strokeTo.delay(s.startDelay + 301)); + } + + // Name + const name = this.paper.text(0, 0, s.name); + name.attr('x', system.attr('x') + system.attr('width') / 2 - name.attr('width') / 2); + name.attr('y', system.attr('y') + system.attr('height') / 2 - name.attr('height') / 2 - 10); + name.data('bx', name.attr('x')); + name.node.style.pointerEvents = 'none'; + this.names.push(name); + + // Time + const time = this.paper.text(0, 0, s.startDelay + ' (ms)'); + time.attr('x', system.attr('x') + system.attr('width') / 2 - time.attr('width') / 2); + time.attr('y', system.attr('y') + system.attr('height') / 2 - time.attr('height') / 2 + 10); + time.data('bx', name.attr('x')); + time.node.style.pointerEvents = 'none'; + this.times.push(time); + + // Create line + const separator = this.paper.rect(0, 40 * (index + 1) - 2.5, this.paper.width, 1); + separator.attr('fill', '#666'); + separator.attr('stroke', '#666'); + this.separators.push(separator); + + // Events + this._onMoveSystem( s, system, name, time); + this._onShowSystemContextMenu( s, system); + + if (system.attr('x') > this._maxX) + this._maxX = system.attr('x') + system.attr('width'); + }); + + // Max z + if (this._maxX < 300) + this._maxX = 300; + + // Add timelines + const steps = 5; + const diff = Timeline._Scale / steps; + const end = (this._maxX / diff) * 2; + + for (let i = 0; i < end; i++) { + const isSecond = i % steps === 0; + + const line = this.paper.rect(i * diff, 0, 1, isSecond ? this.paper.height : (this.timeBackground.attr('height') - 15)); + line.attr('fill', '#999'); + line.attr('stroke-width', 0); + line.data('bx', line.attr('x')); + this.timeLines.push(line); + + if (isSecond) { + const text = this.paper.text(0, 20, ((i / steps) >> 0) + ' (s)'); + text.attr('x', i * diff + 5 + text.attr('width')); + text.data('bx', text.attr('x')); + text.node.style.pointerEvents = 'none'; + this.timeLines.push(text); + } + } + + // Play + this.playLine.transform('t0,0'); + this.playLine.attr('x', 0); + this.playLine.toFront(); + + if (shouldPlay) + this.playLine.animate({ transform: `t${this._maxX},0` }, (this._maxX * 1000) / Timeline._Scale); + + // Systems are front + this.systems.forEach(s => s.toFront()); + this.names.forEach(n => n.toFront()); + this.times.forEach(t => t.toFront()); + } + + /** + * Called on the user modifies a system + * @param system the system that is being modified + */ + public onModifyingSystem (system: ParticleSystem): void { + if (!this._set) + return; + + const index = this._set.systems.indexOf(system); + if (index !== -1) { + const s = this.systems[index]; + const n = this.names[index]; + const t = this.times[index]; + const diff = (system.startDelay - s.data('sd')) / 1000 * Timeline._Scale; + + s.transform(`t${diff},0`); + n.transform(`t${diff},0`); + t.transform(`t${diff},0`); + } + } + + /** + * Calld on the user modified a system + * @param system the system that has been modified + */ + public onModifiedSystem (system: ParticleSystem): void { + if (!this._set) + return; + + const index = this._set.systems.indexOf(system); + if (index !== -1) + this.setSet(this._set); + } + + // Performs a drag'n'drop animation for the background + private _onMoveBackground (): void { + // Drag'n'drop + let lx = 0; + let all: RaphaelElement[] = []; + + const onStart = ((x: number, y: number, ev: DragEvent) => { + all = this._getAllMovableElements(); + }); + + const onMove = ((dx: number, dy: number, x: number, y: number, ev: DragEvent) => { + lx = dx + this._backgroundX; + all.forEach(a => a.attr('x', (a.data('bx') || 0) + lx)); + }); + + const onEnd = ((ev) => { + this._backgroundX = lx; + }); + + this.background.drag( onMove, onStart, onEnd); + + // Wheel + this.background.node.addEventListener('wheel', (ev) => { + Timeline._Scale -= ev.deltaY * 0.05; + if (Timeline._Scale < 30) + Timeline._Scale = 30; + + this.setSet(this._set); + }); + } + + // Performs a drag'n'drop animation for systems + private _onMoveSystem (system: ParticleSystem, s: RaphaelElement, n: RaphaelElement, t: RaphaelElement): void { + const bx = s.attr('x'); + let ox = 0; + let lx = 0; + + const onStart = ((x: number, y: number, ev: DragEvent) => { + s.attr('opacity', 0.3); + + // Stroke width + this.systems.forEach(s => s.attr('stroke-width', 0)); + s.attr('stroke-width', 2); + + // Notify + this.creator.editor.core.onSelectObject.notifyObservers(system); + }); + + const onMove = ((dx: number, dy: number, x: number, y: number, ev: DragEvent) => { + const ms = ((bx + (dx + ox)) / Timeline._Scale * 1000) >> 0; + if (ms < 0) { + return; + } + + lx = dx + ox; + + s.transform(`t${lx},0`); + n.transform(`t${lx},0`); + t.transform(`t${lx},0`); + + t.attr('text', ms + ' (ms)'); + }); + + const onEnd = ((ev) => { + ox = lx; + system.startDelay = ((bx + ox) / Timeline._Scale * 1000) >> 0; + + // Update system + s.attr('opacity', 1); + s.data('sd', system.startDelay); + + // Save set + this.creator.saveSet(); + + // Update tools + if (this.creator.editor.edition.currentObject === system) + this.creator.editor.edition.updateDisplay(); + }); + + s.drag( onMove, onStart, onEnd); + } + + // Performs a context menu on the user right-clicks on the system + private _onShowSystemContextMenu (system: ParticleSystem, s: RaphaelElement): void { + s.node.classList.add('ctxmenu'); + s.node.addEventListener('contextmenu', (ev: MouseEvent) => { + ContextMenu.Show(ev, { + remove: { name: 'Remove', callback: () => this.creator.removeSystemFromSet(system) } + }); + }); + } + + // Returns all movable elements of the paper + private _getAllMovableElements (): RaphaelElement[] { + const result: RaphaelElement[] = []; + let bot = this.paper.bottom; + while (bot) { + if ( + bot === this.background || bot === this.timeBackground || this.separators.indexOf(bot) !== -1) { + bot = bot.next; + continue; + } + + result.push(bot); + bot = bot.next; + } + + return result; + } +} diff --git a/src/tools/play-game/index.ts b/src/tools/play-game/index.ts index 15bb69a8d..5c9ca495a 100644 --- a/src/tools/play-game/index.ts +++ b/src/tools/play-game/index.ts @@ -14,9 +14,6 @@ export default class PlayGame extends EditorPlugin { public capturer: CCapture = null; public isCapturing: boolean = false; public captureBlob: Blob = null; - - // Protected members - protected changeValueObserver: Observer = null; /** * Constructor @@ -36,9 +33,6 @@ export default class PlayGame extends EditorPlugin { // Capturer if (this.capturer) this.capturer.stop(); - - // Callbacks - this.editor.core.onGlobalPropertyChange.remove(this.changeValueObserver); await super.close(); } @@ -69,9 +63,6 @@ export default class PlayGame extends EditorPlugin { // Create iFrame await this.createIFrame(); - - // Events - this.changeValueObserver = this.editor.core.onGlobalPropertyChange.add(data => this.updateValue(data)); } /** @@ -227,49 +218,4 @@ export default class PlayGame extends EditorPlugin { protected showVideo (): void { } - - /** - * Updates the value in the preview page according to undo/redo - * @param data the data to undo-redo - */ - protected updateValue (data: { baseObject?: any; object: any; property: string; value: any; initialValue: any; }): void { - if (!data.baseObject) - return; - - // Get property - let additionalProperty: string = null; - - if (data.baseObject[data.property] === undefined) { - for (const thing in data.baseObject) { - if (data.baseObject[thing] !== data.object) - continue; - - additionalProperty = thing; - break; - } - - if (!additionalProperty) - return; - } - - const scene = this.contentWindow['effectiveScene']; - - const id = data.baseObject.id; - const obj = - scene.getMeshByID(id) || - scene.getMaterialByID(id) || - scene.getLightByID(id) || - scene.getCameraByID(id) || - scene.getParticleSystemByID(id) || - scene.getSkeletonById(id) || - scene.getLensFlareSystemByID(id); - - if (!obj) - return; - - if (additionalProperty) - obj[additionalProperty][data.property] = data.value; - else - obj[data.property] = data.value; - } } diff --git a/src/tools/post-process-editor/index.ts b/src/tools/post-process-editor/index.ts index 9b90a0ea0..6433d110f 100644 --- a/src/tools/post-process-editor/index.ts +++ b/src/tools/post-process-editor/index.ts @@ -128,7 +128,7 @@ export default class PostProcessEditor extends EditorPlugin { `, resizable: true, - tabs: [ + tabs: [ { id: 'code', caption: 'Code' }, { id: 'pixel', caption: 'Pixel' }, { id: 'config', caption: 'Config' } diff --git a/src/tools/textures/viewer.ts b/src/tools/textures/viewer.ts index 85fe6948b..25f099eb0 100644 --- a/src/tools/textures/viewer.ts +++ b/src/tools/textures/viewer.ts @@ -371,6 +371,11 @@ export default class TextureViewer extends EditorPlugin { const texture = new Texture('file:' + file.name, this.editor.core.scene); texture.name = texture.url = texture.name.replace('file:', ''); } + + // Drag'n'drop + const dropListener = this.dragEnd(originalTexture, true); + img.addEventListener('dragstart', () => this.editor.core.engine.getRenderingCanvas().addEventListener('drop', dropListener)); + img.addEventListener('dragend', () => this.editor.core.engine.getRenderingCanvas().removeEventListener('drop', dropListener)); } else { const data = await Tools.ReadFileAsBase64(file); @@ -391,6 +396,11 @@ export default class TextureViewer extends EditorPlugin { const texture = new Texture('file:' + file.name, this.editor.core.scene); texture.name = texture.url = texture.name.replace('file:', ''); } + + // Drag'n'drop + const dropListener = this.dragEnd(originalTexture, false); + img.addEventListener('dragstart', () => this.editor.core.engine.getRenderingCanvas().addEventListener('drop', dropListener)); + img.addEventListener('dragend', () => this.editor.core.engine.getRenderingCanvas().removeEventListener('drop', dropListener)); } // Add text @@ -409,6 +419,40 @@ export default class TextureViewer extends EditorPlugin { parent.appendChild(text); } + /** + * Returns an event called when the user drops a texture on the preview canvas + * @param texture: the texture to drop on a mesh/instanced-mesh + */ + protected dragEnd (texture: BaseTexture, isCube: boolean): (ev: DragEvent) => void { + return (ev: DragEvent) => { + const scene = this.editor.core.scene; + const pick = scene.pick(ev.offsetX, ev.offsetY); + + if (!pick.pickedMesh || !pick.pickedMesh.material) + return; + + // Apply + const material = pick.pickedMesh.material; + + if (isCube) { + UndoRedo.Push({ baseObject: material, object: material, property: 'reflectionTexture', from: material['reflectionTexture'], to: texture }); + material['reflectionTexture'] = texture; + } + else { + if (material instanceof PBRMaterial) { + UndoRedo.Push({ baseObject: material, object: material, property: 'albedoTexture', from: material.albedoTexture, to: texture }); + material.albedoTexture = texture; + } + else { + UndoRedo.Push({ baseObject: material, object: material, property: 'diffuseTexture', from: material['diffuseTexture'], to: texture }); + material['diffuseTexture'] = texture; + } + } + + this.editor.core.onSelectObject.notifyObservers(pick.pickedMesh); + }; + } + /** * Convets a cube texture to */