فهرست منبع

Merge branch 'master' into PolygonalSun/Add-DSM-InputManager

Dave Solares 4 سال پیش
والد
کامیت
03703fc1e1
100فایلهای تغییر یافته به همراه72447 افزوده شده و 14946 حذف شده
  1. 13 0
      .editorconfig
  2. 1 0
      .gitignore
  3. 27 0
      .vscode/launch.json
  4. 1 0
      Playground/frame.html
  5. 1 0
      Playground/full.html
  6. 2 1
      Playground/index-local.html
  7. 1 0
      Playground/index.html
  8. 1 0
      Playground/index.js
  9. 21 29
      Playground/libs/babylon.manager.d.ts
  10. 11 11
      Playground/libs/babylon.manager.js
  11. BIN
      Playground/scenes/cubeMorph_8target.glb
  12. 4 0
      Playground/src/components/commandBarComponent.tsx
  13. 59 2
      Tools/Config/config.json
  14. 1 2
      Viewer/package.json
  15. 1 1
      Viewer/src/templating/templateManager.ts
  16. 4 2
      Viewer/tests/validation/validation.js
  17. 428 237
      dist/preview release/babylon.d.ts
  18. 2 2
      dist/preview release/babylon.js
  19. 1358 420
      dist/preview release/babylon.max.js
  20. 1 1
      dist/preview release/babylon.max.js.map
  21. 893 483
      dist/preview release/babylon.module.d.ts
  22. 428 237
      dist/preview release/documentation.d.ts
  23. 1 1
      dist/preview release/glTF2Interface/package.json
  24. 2 2
      dist/preview release/gui/package.json
  25. 1149 0
      dist/preview release/guiEditor/babylon.guiEditor.d.ts
  26. 30 0
      dist/preview release/guiEditor/babylon.guiEditor.js
  27. 46326 0
      dist/preview release/guiEditor/babylon.guiEditor.max.js
  28. 1 0
      dist/preview release/guiEditor/babylon.guiEditor.max.js.map
  29. 2434 0
      dist/preview release/guiEditor/babylon.guiEditor.module.d.ts
  30. 27 0
      dist/preview release/guiEditor/package.json
  31. 15 0
      dist/preview release/guiEditor/readme-es6.md
  32. 1 0
      dist/preview release/guiEditor/readme.md
  33. 6 6
      dist/preview release/inspector/babylon.inspector.bundle.js
  34. 12603 12614
      dist/preview release/inspector/babylon.inspector.bundle.max.js
  35. 1 1
      dist/preview release/inspector/babylon.inspector.bundle.max.js.map
  36. 58 33
      dist/preview release/inspector/babylon.inspector.d.ts
  37. 219 169
      dist/preview release/inspector/babylon.inspector.module.d.ts
  38. 7 7
      dist/preview release/inspector/package.json
  39. 17 18
      dist/preview release/loaders/babylon.glTF2FileLoader.js
  40. 1 1
      dist/preview release/loaders/babylon.glTF2FileLoader.js.map
  41. 1 1
      dist/preview release/loaders/babylon.glTF2FileLoader.min.js
  42. 17 18
      dist/preview release/loaders/babylon.glTFFileLoader.js
  43. 1 1
      dist/preview release/loaders/babylon.glTFFileLoader.js.map
  44. 1 1
      dist/preview release/loaders/babylon.glTFFileLoader.min.js
  45. 17 18
      dist/preview release/loaders/babylonjs.loaders.js
  46. 1 1
      dist/preview release/loaders/babylonjs.loaders.js.map
  47. 1 1
      dist/preview release/loaders/babylonjs.loaders.min.js
  48. 3 3
      dist/preview release/loaders/package.json
  49. 2 2
      dist/preview release/materialsLibrary/package.json
  50. 425 14
      dist/preview release/nodeEditor/babylon.nodeEditor.d.ts
  51. 6 6
      dist/preview release/nodeEditor/babylon.nodeEditor.max.js
  52. 1 1
      dist/preview release/nodeEditor/babylon.nodeEditor.max.js.map
  53. 900 33
      dist/preview release/nodeEditor/babylon.nodeEditor.module.d.ts
  54. 2 2
      dist/preview release/nodeEditor/package.json
  55. 1 1
      dist/preview release/package.json
  56. 1 1
      dist/preview release/packagesSizeBaseLine.json
  57. 2 2
      dist/preview release/postProcessesLibrary/package.json
  58. 2 2
      dist/preview release/proceduralTexturesLibrary/package.json
  59. 0 1
      dist/preview release/serializers/babylon.glTF2Serializer.js
  60. 1 1
      dist/preview release/serializers/babylon.glTF2Serializer.js.map
  61. 0 1
      dist/preview release/serializers/babylonjs.serializers.js
  62. 1 1
      dist/preview release/serializers/babylonjs.serializers.js.map
  63. 3 3
      dist/preview release/serializers/package.json
  64. 893 483
      dist/preview release/viewer/babylon.module.d.ts
  65. 81 61
      dist/preview release/viewer/babylon.viewer.js
  66. 3 3
      dist/preview release/viewer/babylon.viewer.max.js
  67. 19 4
      dist/preview release/what's new.md
  68. 1 0
      guiEditor/README-ES6.md
  69. 18 0
      guiEditor/README.md
  70. 3 0
      guiEditor/imgs/downArrow.svg
  71. 43 0
      guiEditor/public/index-local.html
  72. 42 0
      guiEditor/public/index.html
  73. 116 0
      guiEditor/public/index.js
  74. 211 0
      guiEditor/src/components/guiList/guiList.scss
  75. 89 0
      guiEditor/src/components/guiList/guiListComponent.tsx
  76. 20 0
      guiEditor/src/components/log/log.scss
  77. 63 0
      guiEditor/src/components/log/logComponent.tsx
  78. 798 0
      guiEditor/src/components/propertyTab/propertyTab.scss
  79. 228 0
      guiEditor/src/components/propertyTab/propertyTabComponent.tsx
  80. 198 0
      guiEditor/src/diagram/guiNode.ts
  81. 113 0
      guiEditor/src/diagram/properties/genericNodePropertyComponent.tsx
  82. 7 0
      guiEditor/src/diagram/properties/propertyComponentProps.ts
  83. 55 0
      guiEditor/src/diagram/properties/sliderGuiPropertyComponent.tsx
  84. 10 0
      guiEditor/src/diagram/propertyLedger.ts
  85. 541 0
      guiEditor/src/diagram/workbench.tsx
  86. 452 0
      guiEditor/src/diagram/workbenchCanvas.scss
  87. 42 0
      guiEditor/src/globalState.ts
  88. 80 0
      guiEditor/src/guiEditor.ts
  89. 54 0
      guiEditor/src/guiNodeTools.ts
  90. 1 0
      guiEditor/src/index.ts
  91. 9 0
      guiEditor/src/legacy/legacy.ts
  92. 344 0
      guiEditor/src/main.scss
  93. 26 0
      guiEditor/src/nodeLocationInfo.ts
  94. 17 0
      guiEditor/src/portal.tsx
  95. 8 0
      guiEditor/src/serializationTools.ts
  96. 147 0
      guiEditor/src/sharedComponents/floatLineComponent.tsx
  97. 52 0
      guiEditor/src/sharedComponents/lineWithFileButtonComponent.tsx
  98. 42 0
      guiEditor/src/sharedComponents/messageDialog.tsx
  99. 76 0
      guiEditor/src/sharedComponents/numericInputComponent.tsx
  100. 0 0
      guiEditor/src/sharedComponents/propertyChangedEvent.ts

+ 13 - 0
.editorconfig

@@ -0,0 +1,13 @@
+# editorconfig.org
+root = true
+
+[*]
+indent_style = space
+indent_size = 4
+end_of_line = crlf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = false
+
+[*.md]
+trim_trailing_whitespace = false

+ 1 - 0
.gitignore

@@ -212,3 +212,4 @@ ktx2Decoder/dist/
 # Symlinks
 inspector/src/sharedUiComponents/**/*
 nodeEditor/src/sharedUiComponents/**/*
+guiEditor/src/sharedUiComponents/**/*

+ 27 - 0
.vscode/launch.json

@@ -40,6 +40,19 @@
             "runtimeArgs": [
                 "--enable-unsafe-es3-apis"
             ]
+        }, 
+        {
+            "name": "Launch GUI Editor (Chrome)",
+            "type": "chrome",
+            "request": "launch",
+            "url": "http://localhost:1338/guiEditor/public/index-local.html",
+            "webRoot": "${workspaceRoot}/",
+            "sourceMaps": true,
+            "preLaunchTask": "run",
+            "userDataDir": "${workspaceRoot}/.tempChromeProfileForDebug",
+            "runtimeArgs": [
+                "--enable-unsafe-es3-apis"
+            ]
         },     
         {
             "name": "Launch Viewer (Chrome)",
@@ -107,6 +120,20 @@
                 "--enable-unsafe-es3-apis"
             ]
         },
+        {
+            "name": "Launch playground (Chrome Canary)",
+            "type": "chrome",
+            "request": "launch",
+            "url": "http://localhost:1338/Playground/index-local.html",
+            "webRoot": "${workspaceRoot}/",
+            "sourceMaps": true,
+            "preLaunchTask": "run",
+            "userDataDir": "${workspaceRoot}/.tempChromeCanaryProfileForDebug",
+            "runtimeExecutable": "C:/Users/alexis/AppData/Local/Google/Chrome SxS/Application/Chrome.exe",
+            "runtimeArgs": [
+                "--enable-unsafe-es3-apis"
+            ]
+        },
         { 
             "name": "Launch playground (Edge)",
             "type": "edge",

+ 1 - 0
Playground/frame.html

@@ -39,6 +39,7 @@
         <script src="https://preview.babylonjs.com/babylon.js"></script>
         <script src="https://preview.babylonjs.com/gui/babylon.gui.min.js"></script>
         <script src="https://preview.babylonjs.com/nodeEditor/babylon.nodeEditor.js"></script>
+        <script src="https://preview.babylonjs.com/guiEditor/babylon.guiEditor.js"></script>
         <script src="https://preview.babylonjs.com/materialsLibrary/babylonjs.materials.min.js"></script>
         <script src="https://preview.babylonjs.com/proceduralTexturesLibrary/babylonjs.proceduralTextures.min.js"></script>
         <script src="https://preview.babylonjs.com/postProcessesLibrary/babylonjs.postProcess.min.js"></script>

+ 1 - 0
Playground/full.html

@@ -39,6 +39,7 @@
         <script src="https://preview.babylonjs.com/babylon.js"></script>
         <script src="https://preview.babylonjs.com/gui/babylon.gui.min.js"></script>
         <script src="https://preview.babylonjs.com/nodeEditor/babylon.nodeEditor.js"></script>
+        <script src="https://preview.babylonjs.com/guiEditor/babylon.guiEditor.js"></script>
         <script src="https://preview.babylonjs.com/materialsLibrary/babylonjs.materials.min.js"></script>
         <script src="https://preview.babylonjs.com/proceduralTexturesLibrary/babylonjs.proceduralTextures.min.js"></script>
         <script src="https://preview.babylonjs.com/postProcessesLibrary/babylonjs.postProcess.min.js"></script>

+ 2 - 1
Playground/index-local.html

@@ -82,7 +82,8 @@
                         wasmUASTCToRGBA_UNORM: GetAbsoluteUrl("../dist/preview%20release/ktx2Transcoders/uastc_rgba32_unorm.wasm"),
                         wasmUASTCToRGBA_SRGB: GetAbsoluteUrl("../dist/preview%20release/ktx2Transcoders/uastc_rgba32_srgb.wasm"),
                         jsMSCTranscoder: GetAbsoluteUrl("../dist/preview%20release/ktx2Transcoders/msc_basis_transcoder.js"),
-                        wasmMSCTranscoder: GetAbsoluteUrl("../dist/preview%20release/ktx2Transcoders/msc_basis_transcoder.wasm")
+                        wasmMSCTranscoder: GetAbsoluteUrl("../dist/preview%20release/ktx2Transcoders/msc_basis_transcoder.wasm"),
+                        wasmZSTDDecoder: GetAbsoluteUrl("../dist/preview%20release/zstddec.wasm"),
                     };
                 });
         </script>

+ 1 - 0
Playground/index.html

@@ -45,6 +45,7 @@
         <script src="https://preview.babylonjs.com/gui/babylon.gui.min.js"></script>
         <script src="https://preview.babylonjs.com/inspector/babylon.inspector.bundle.js"></script>
         <script src="https://preview.babylonjs.com/nodeEditor/babylon.nodeEditor.js"></script>
+        <script src="https://preview.babylonjs.com/guiEditor/babylon.guiEditor.js"></script>
 
          <!-- Extensions -->
          <script

+ 1 - 0
Playground/index.js

@@ -5,6 +5,7 @@ var Versions = {
         "https://preview.babylonjs.com/gui/babylon.gui.min.js",
         "https://preview.babylonjs.com/inspector/babylon.inspector.bundle.js",
         "https://preview.babylonjs.com/nodeEditor/babylon.nodeEditor.js",
+        "https://preview.babylonjs.com/guiEditor/babylon.guiEditor.js",
         "https://preview.babylonjs.com/materialsLibrary/babylonjs.materials.min.js",
         "https://preview.babylonjs.com/proceduralTexturesLibrary/babylonjs.proceduralTextures.min.js",
         "https://preview.babylonjs.com/postProcessesLibrary/babylonjs.postProcess.min.js",

+ 21 - 29
Playground/libs/babylon.manager.d.ts

@@ -198,6 +198,8 @@ declare module BABYLON {
         static GetTransformNode(scene: BABYLON.Scene, name: string): BABYLON.TransformNode;
         /** Gets the specified transform node by id from scene. */
         static GetTransformNodeByID(scene: BABYLON.Scene, id: string): BABYLON.TransformNode;
+        /** Gets the transform node child detail mesh. */
+        static GetTransformDetailMesh(transform: TransformNode): BABYLON.AbstractMesh;
         /** Gets the transform node primitive meshes. */
         static GetPrimitiveMeshes(transform: TransformNode): BABYLON.AbstractMesh[];
         /** Gets the specified transform node primary layer index. */
@@ -1475,19 +1477,19 @@ declare module BABYLON {
         /** Set animation target property */
         static SetAnimationTargetProperty(animation: BABYLON.Animation, property: string): void;
         /** Gets the float "result" as the sampled key frame value for the specfied animation track. */
-        static SampleAnimationFloat(animation: BABYLON.Animation, frame: number): number;
+        static SampleAnimationFloat(animation: BABYLON.Animation, time: number): number;
         /** Set the passed vector2 "result" as the sampled key frame value for the specfied animation track. */
-        static SampleAnimationVector2(animation: BABYLON.Animation, frame: number): BABYLON.Vector2;
+        static SampleAnimationVector2(animation: BABYLON.Animation, time: number): BABYLON.Vector2;
         /** Set the passed vector3 "result" as the sampled key frame value for the specfied animation track. */
-        static SampleAnimationVector3(animation: BABYLON.Animation, frame: number): BABYLON.Vector3;
+        static SampleAnimationVector3(animation: BABYLON.Animation, time: number): BABYLON.Vector3;
         /** Set the passed quaternion "result" as the sampled key frame value for the specfied animation track. */
-        static SampleAnimationQuaternion(animation: BABYLON.Animation, frame: number): BABYLON.Quaternion;
+        static SampleAnimationQuaternion(animation: BABYLON.Animation, time: number): BABYLON.Quaternion;
         /** Set the passed matrix "result" as the sampled key frame value for the specfied animation track. */
-        static SampleAnimationMatrix(animation: BABYLON.Animation, frame: number): BABYLON.Matrix;
+        static SampleAnimationMatrix(animation: BABYLON.Animation, time: number): BABYLON.Matrix;
         /** Creates a targeted float animation for tweening.  */
-        static CreateFloatAnimation(name: string, targetProperty: string, startValue: number, endValue: number, frameRate?: number, loopMode?: number): BABYLON.Animation;
+        static CreateTweenAnimation(name: string, targetProperty: string, startValue: number, endValue: number, frameRate?: number, loopMode?: number): BABYLON.Animation;
         /** Gets the last key frame index value. */
-        static GetLastKeyFrameIndex(animation: BABYLON.Animation): number;
+        static GetLastKeyFrameValue(animation: BABYLON.Animation): number;
         /** Private internal frame interpolation helper */
         private static InterpolateAnimation;
         /** Initialize default shader material properties */
@@ -2293,20 +2295,24 @@ declare module BABYLON {
         heightOffset: number;
         angularSpeed: number;
         updatePosition: boolean;
+        updateRotation: boolean;
         distanceEpsilon: number;
         velocityEpsilon: number;
+        offMeshVelocity: number;
         stoppingDistance: number;
+        isReady(): boolean;
         isNavigating(): boolean;
+        isOnOffMeshLink(): boolean;
         getAgentType(): number;
         getAgentState(): number;
         getAgentIndex(): number;
-        getAgentRadius(): number;
-        getAgentHeight(): number;
-        getAgentSpeed(): number;
         getAgentOffset(): number;
         getTargetDistance(): number;
+        getAgentParameters(): BABYLON.IAgentParameters;
+        setAgentParameters(parameters: BABYLON.IAgentParameters): void;
         protected m_agentState: number;
         protected m_agentIndex: number;
+        protected m_agentReady: boolean;
         protected m_agentGhost: BABYLON.TransformNode;
         protected m_agentParams: BABYLON.IAgentParameters;
         protected m_agentRotation: BABYLON.Quaternion;
@@ -2315,8 +2321,10 @@ declare module BABYLON {
         protected m_agentQuaternion: BABYLON.Quaternion;
         protected m_agentDestination: BABYLON.Vector3;
         protected awake(): void;
-        protected update(): void;
+        protected late(): void;
         protected destroy(): void;
+        /** Register handler that is triggered when the agent is ready for navigation */
+        onReadyObservable: Observable<TransformNode>;
         /** Register handler that is triggered before the navigation update */
         onPreUpdateObservable: Observable<TransformNode>;
         /** Register handler that is triggered after the navigation update */
@@ -2325,13 +2333,14 @@ declare module BABYLON {
         onNavCompleteObservable: Observable<TransformNode>;
         private awakeNavigationAgent;
         private updateNavigationAgent;
+        private updateAgentParameters;
         private destroyNavigationAgent;
         /** Move agent relative to current position. */
         move(offset: BABYLON.Vector3, closetPoint?: boolean): void;
         /** Teleport agent to destination point. */
         teleport(destination: BABYLON.Vector3, closetPoint?: boolean): void;
         /** Sets agent current destination point. */
-        setDestination(destination: BABYLON.Vector3, closetPoint?: boolean, resetAgent?: boolean): void;
+        setDestination(destination: BABYLON.Vector3, closetPoint?: boolean): void;
         /** Gets agent current world space velocity. */
         getAgentVelocity(): BABYLON.Vector3;
         /** Gets agent current world space velocity. */
@@ -2664,23 +2673,6 @@ declare module BABYLON {
 
 declare module BABYLON {
     /**
-     * Babylon window socket controller pro class (Socket.IO)
-     * @class SocketController - All rights reserved (c) 2020 Mackey Kinard
-     */
-    class SocketController {
-        /** Registers an handler for window socket connect event */
-        static RegisterOnSocketConnect(func: () => void): void;
-        /** Registers an handler for window socket disconnect event */
-        static RegisterOnSocketDisconnect(func: () => void): void;
-        /** Connects a window state socket */
-        static ConnectWindowSocket(connection: string): SocketIOClient.Socket;
-        /** Get the window state socket */
-        static GetWindowSocket(): SocketIOClient.Socket;
-    }
-}
-
-declare module BABYLON {
-    /**
      * Babylon web video player pro class (Unity Style Shuriken Particle System)
      * @class WebVideoPlayer - All rights reserved (c) 2020 Mackey Kinard
      */

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 11 - 11
Playground/libs/babylon.manager.js


BIN
Playground/scenes/cubeMorph_8target.glb


+ 4 - 0
Playground/src/components/commandBarComponent.tsx

@@ -54,6 +54,10 @@ export class CommandBarComponent extends React.Component<ICommandBarComponentPro
         let activeVersion = Utilities.ReadStringFromStore("version", "Latest");
         let activeEngineVersion = Utilities.ReadStringFromStore("engineVersion", "WebGL2");
 
+        if (location.href.indexOf("webgpu") !== -1 && !!navigator.gpu) {
+            activeEngineVersion = "WebGPU";
+        }
+
         var versionOptions = Object.keys(Versions).map(key => {
             return {
                 label: key,

+ 59 - 2
Tools/Config/config.json

@@ -48,7 +48,8 @@
         "serializers",
         "gui",
         "inspector",
-        "nodeEditor"
+        "nodeEditor",
+        "guiEditor"
     ],
     "es6modules": [
         "core",
@@ -60,7 +61,8 @@
         "gui",
         "inspector",
         "viewer",
-        "nodeEditor"
+        "nodeEditor",
+        "guiEditor"
     ],
     "apps": [
         "playground",
@@ -651,6 +653,61 @@
             }
         }
     },
+    "guiEditor": {
+        "libraries": [
+            {
+                "output": "babylon.guiEditor.js",
+                "entry": "./legacy/legacy.ts"
+            }
+        ],
+        "build": {            
+            "ignoreInWorkerMode": true,
+            "ignoreInTestMode": true,
+            "mainFolder": "./guiEditor/",
+            "uncheckedLintImports": [
+                "react",
+                "react-dom",
+                "dagre",
+                "re-resizable",
+                "glTF",
+                "file-saver"
+            ],
+            "sharedUiComponents": "src/sharedUiComponents/",
+            "umd": {
+                "packageName": "babylonjs-gui-editor",
+                "webpackRoot": "GUIEDITOR",
+                "processDeclaration": {
+                    "filename": "babylon.guiEditor.module.d.ts",
+                    "moduleName": "GUIEDITOR",
+                    "importsToRemove": [],
+                    "classMap": {
+                        "babylonjs": "BABYLON",
+                        "react": "React",
+                        "@babylonjs/core": "BABYLON",
+                        "@fortawesome": false,
+                        "react-contextmenu": false
+                    }
+                }
+            },
+            "es6": {
+                "webpackBuild": true,
+                "buildDependencies": [
+                    "node_modules/re-resizable/lib/index.es5.js",
+                    "Tools/**/*"
+                ],
+                "packageName": "@babylonjs/gui-editor",
+                "readme": "dist/preview release/guiEditor/readme-es6.md",
+                "packagesFiles": [
+                    "babylon.guiEditor.max.js",
+                    "babylon.guiEditor.max.js.map",
+                    "babylon.guiEditor.module.d.ts",
+                    "readme.md"
+                ],
+                "typings": "babylon.guiEditor.module.d.ts",
+                "index": "babylon.guiEditor.max.js"
+            }
+        }
+    },
     "ktx2Decoder": {
         "tempFileName": "babylon.ktx2Decoder.js",
         "distFile": "/dist/preview release/babylon.ktx2Decoder.js",

+ 1 - 2
Viewer/package.json

@@ -29,11 +29,10 @@
         "base64-image-loader": "^1.2.1",
         "base64-inline-loader": "^1.1.1",
         "deepmerge": "~2.1.1",
-        "handlebars": "^4.5.1",
+        "handlebars": "^4.7.6",
         "html-loader": "^0.5.5",
         "json-loader": "^0.5.7",
         "ts-loader": "^4.4.0",
-        "uglifyjs-webpack-plugin": "^1.2.2",
         "webpack": "^4.29.3",
         "webpack-cli": "^3.3.9",
         "webpack-dev-server": "^3.1.14"

+ 1 - 1
Viewer/src/templating/templateManager.ts

@@ -596,7 +596,7 @@ export class Template {
                             selector = this.parent.tagName;
                         }
                         let binding = functionToFire.bind(this, selector);
-                        this.parent.addEventListener(eventName, functionToFire.bind(this, selector), false);
+                        this.parent.addEventListener(eventName, binding, false);
                         this._registeredEvents.push({
                             htmlElement: this.parent,
                             eventName: eventName,

+ 4 - 2
Viewer/tests/validation/validation.js

@@ -320,7 +320,8 @@ function init() {
         wasmUASTCToRGBA_UNORM: GetAbsoluteUrl("../../dist/preview%20release/ktx2Transcoders/uastc_rgba32_unorm.wasm"),
         wasmUASTCToRGBA_SRGB: GetAbsoluteUrl("../../dist/preview%20release/ktx2Transcoders/uastc_rgba32_srgb.wasm"),
         jsMSCTranscoder: GetAbsoluteUrl("../../dist/preview%20release/ktx2Transcoders/msc_basis_transcoder.js"),
-        wasmMSCTranscoder: GetAbsoluteUrl("../../dist/preview%20release/ktx2Transcoders/msc_basis_transcoder.wasm")
+        wasmMSCTranscoder: GetAbsoluteUrl("../../dist/preview%20release/ktx2Transcoders/msc_basis_transcoder.wasm"),
+        wasmZSTDDecoder: GetAbsoluteUrl("../../dist/preview%20release/zstddec.wasm"),
     };
 
     BABYLON.KhronosTextureContainer2.URLConfig = {
@@ -330,7 +331,8 @@ function init() {
         wasmUASTCToRGBA_UNORM: GetAbsoluteUrl("../../dist/preview%20release/ktx2Transcoders/uastc_rgba32_unorm.wasm"),
         wasmUASTCToRGBA_SRGB: GetAbsoluteUrl("../../dist/preview%20release/ktx2Transcoders/uastc_rgba32_srgb.wasm"),
         jsMSCTranscoder: GetAbsoluteUrl("../../dist/preview%20release/ktx2Transcoders/msc_basis_transcoder.js"),
-        wasmMSCTranscoder: GetAbsoluteUrl("../../dist/preview%20release/ktx2Transcoders/msc_basis_transcoder.wasm")
+        wasmMSCTranscoder: GetAbsoluteUrl("../../dist/preview%20release/ktx2Transcoders/msc_basis_transcoder.wasm"),
+        wasmZSTDDecoder: GetAbsoluteUrl("../../dist/preview%20release/zstddec.wasm"),
     };
 
     viewerElement = document.createElement("babylon");

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 428 - 237
dist/preview release/babylon.d.ts


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 2 - 2
dist/preview release/babylon.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1358 - 420
dist/preview release/babylon.max.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1 - 1
dist/preview release/babylon.max.js.map


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 893 - 483
dist/preview release/babylon.module.d.ts


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 428 - 237
dist/preview release/documentation.d.ts


+ 1 - 1
dist/preview release/glTF2Interface/package.json

@@ -1,7 +1,7 @@
 {
     "name": "babylonjs-gltf2interface",
     "description": "A typescript declaration of babylon's gltf2 inteface.",
-    "version": "5.0.0-alpha.5",
+    "version": "5.0.0-alpha.6",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"

+ 2 - 2
dist/preview release/gui/package.json

@@ -4,7 +4,7 @@
     },
     "name": "babylonjs-gui",
     "description": "The Babylon.js GUI library is an extension you can use to generate interactive user interface. It is build on top of the DynamicTexture.",
-    "version": "5.0.0-alpha.5",
+    "version": "5.0.0-alpha.6",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -28,7 +28,7 @@
     ],
     "license": "Apache-2.0",
     "dependencies": {
-        "babylonjs": "5.0.0-alpha.5"
+        "babylonjs": "5.0.0-alpha.6"
     },
     "engines": {
         "node": "*"

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1149 - 0
dist/preview release/guiEditor/babylon.guiEditor.d.ts


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 30 - 0
dist/preview release/guiEditor/babylon.guiEditor.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 46326 - 0
dist/preview release/guiEditor/babylon.guiEditor.max.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1 - 0
dist/preview release/guiEditor/babylon.guiEditor.max.js.map


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 2434 - 0
dist/preview release/guiEditor/babylon.guiEditor.module.d.ts


+ 27 - 0
dist/preview release/guiEditor/package.json

@@ -0,0 +1,27 @@
+{
+    "author": {
+        "name": "David CATUHE"
+    },
+    "name": "babylonjs-gui-editor",
+    "description": "The Babylon.js GUI editor.",
+    "version": "5.0.0-alpha.3",
+    "repository": {
+        "type": "git",
+        "url": "https://github.com/BabylonJS/Babylon.js.git"
+    },
+    "license": "Apache-2.0",
+    "dependencies": {
+        "babylonjs": "5.0.0-alpha.3"
+    },
+    "files": [
+        "babylon.guiEditor.max.js.map",
+        "babylon.guiEditor.max.js",
+        "babylon.guiEditor.js",
+        "babylon.guiEditor.module.d.ts",
+		"readme.md",
+        "package.json"
+    ],
+    "engines": {
+        "node": "*"
+    }
+}

+ 15 - 0
dist/preview release/guiEditor/readme-es6.md

@@ -0,0 +1,15 @@
+Gui Editor es6
+
+# Babylon.js Gui Editor
+
+An extension to easily allow users to create and modify GUI for scenes.
+
+## Usage
+Currently avalible for local development by selecting "Launch GUI Editor (Chrome)"
+
+## Current Supported Features
+
+- Launch GUI editor in local dev mode.
+- Drag and drop GUI elements onto a canvas.
+- Select and move individual GUI elements.
+- Modify properties of selected GUI elements.

+ 1 - 0
dist/preview release/guiEditor/readme.md

@@ -0,0 +1 @@
+Gui Editor

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 6 - 6
dist/preview release/inspector/babylon.inspector.bundle.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 12603 - 12614
dist/preview release/inspector/babylon.inspector.bundle.max.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1 - 1
dist/preview release/inspector/babylon.inspector.bundle.max.js.map


+ 58 - 33
dist/preview release/inspector/babylon.inspector.d.ts

@@ -113,18 +113,15 @@ declare module INSPECTOR {
 }
 declare module INSPECTOR {
     interface ILineContainerComponentProps {
-        globalState?: GlobalState;
         title: string;
         children: any[] | any;
         closed?: boolean;
     }
     export class LineContainerComponent extends React.Component<ILineContainerComponentProps, {
         isExpanded: boolean;
-        isHighlighted: boolean;
     }> {
         constructor(props: ILineContainerComponentProps);
         switchExpandedState(): void;
-        componentDidMount(): void;
         renderHeader(): JSX.Element;
         render(): JSX.Element;
     }
@@ -163,6 +160,15 @@ declare module INSPECTOR {
     }
 }
 declare module INSPECTOR {
+    export class PropertyChangedEvent {
+        object: any;
+        property: string;
+        value: any;
+        initialValue: any;
+        allowNullValue?: boolean;
+    }
+}
+declare module INSPECTOR {
     export interface ICheckBoxLineComponentProps {
         label: string;
         target?: any;
@@ -171,9 +177,11 @@ declare module INSPECTOR {
         onSelect?: (value: boolean) => void;
         onValueChanged?: () => void;
         onPropertyChangedObservable?: BABYLON.Observable<PropertyChangedEvent>;
+        disabled?: boolean;
     }
     export class CheckBoxLineComponent extends React.Component<ICheckBoxLineComponentProps, {
         isSelected: boolean;
+        isDisabled?: boolean;
     }> {
         private static _UniqueIdSeed;
         private _uniqueId;
@@ -181,6 +189,7 @@ declare module INSPECTOR {
         constructor(props: ICheckBoxLineComponentProps);
         shouldComponentUpdate(nextProps: ICheckBoxLineComponentProps, nextState: {
             isSelected: boolean;
+            isDisabled: boolean;
         }): boolean;
         onChange(): void;
         render(): JSX.Element;
@@ -1903,36 +1912,6 @@ declare module INSPECTOR {
     }
 }
 declare module INSPECTOR {
-    export interface IColor4LineComponentProps {
-        label: string;
-        target: any;
-        propertyName: string;
-        onPropertyChangedObservable?: BABYLON.Observable<PropertyChangedEvent>;
-        onChange?: () => void;
-        isLinear?: boolean;
-    }
-    export class Color4LineComponent extends React.Component<IColor4LineComponentProps, {
-        isExpanded: boolean;
-        color: BABYLON.Color4;
-    }> {
-        private _localChange;
-        constructor(props: IColor4LineComponentProps);
-        shouldComponentUpdate(nextProps: IColor4LineComponentProps, nextState: {
-            color: BABYLON.Color4;
-        }): boolean;
-        setPropertyValue(newColor: BABYLON.Color4): void;
-        onChange(newValue: string): void;
-        switchExpandState(): void;
-        raiseOnPropertyChanged(previousValue: BABYLON.Color4): void;
-        updateStateR(value: number): void;
-        updateStateG(value: number): void;
-        updateStateB(value: number): void;
-        updateStateA(value: number): void;
-        copyToClipboard(): void;
-        render(): JSX.Element;
-    }
-}
-declare module INSPECTOR {
     interface IGradientStepComponentProps {
         globalState: GlobalState;
         step: BABYLON.GradientBlockColorStep;
@@ -1971,6 +1950,36 @@ declare module INSPECTOR {
     }
 }
 declare module INSPECTOR {
+    export interface IColor4LineComponentProps {
+        label: string;
+        target: any;
+        propertyName: string;
+        onPropertyChangedObservable?: BABYLON.Observable<PropertyChangedEvent>;
+        onChange?: () => void;
+        isLinear?: boolean;
+    }
+    export class Color4LineComponent extends React.Component<IColor4LineComponentProps, {
+        isExpanded: boolean;
+        color: BABYLON.Color4;
+    }> {
+        private _localChange;
+        constructor(props: IColor4LineComponentProps);
+        shouldComponentUpdate(nextProps: IColor4LineComponentProps, nextState: {
+            color: BABYLON.Color4;
+        }): boolean;
+        setPropertyValue(newColor: BABYLON.Color4): void;
+        onChange(newValue: string): void;
+        switchExpandState(): void;
+        raiseOnPropertyChanged(previousValue: BABYLON.Color4): void;
+        updateStateR(value: number): void;
+        updateStateG(value: number): void;
+        updateStateB(value: number): void;
+        updateStateA(value: number): void;
+        copyToClipboard(): void;
+        render(): JSX.Element;
+    }
+}
+declare module INSPECTOR {
     interface INodeMaterialPropertyGridComponentProps {
         globalState: GlobalState;
         material: BABYLON.NodeMaterial;
@@ -4397,4 +4406,20 @@ declare module INSPECTOR {
 }
 declare module INSPECTOR {
     export const Contrast: IToolData;
+}
+declare module INSPECTOR {
+    export interface IButtonLineComponentProps {
+        data: string;
+        tooltip: string;
+    }
+    export class DraggableLineComponent extends React.Component<IButtonLineComponentProps> {
+        constructor(props: IButtonLineComponentProps);
+        render(): JSX.Element;
+    }
+}
+declare module INSPECTOR {
+    export class Popup {
+        static CreatePopup(title: string, windowVariableName: string, width?: number, height?: number): HTMLDivElement | null;
+        private static _CopyStyles;
+    }
 }

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 219 - 169
dist/preview release/inspector/babylon.inspector.module.d.ts


+ 7 - 7
dist/preview release/inspector/package.json

@@ -4,7 +4,7 @@
     },
     "name": "babylonjs-inspector",
     "description": "The Babylon.js inspector.",
-    "version": "5.0.0-alpha.5",
+    "version": "5.0.0-alpha.6",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -29,12 +29,12 @@
     ],
     "license": "Apache-2.0",
     "dependencies": {
-        "babylonjs": "5.0.0-alpha.5",
-        "babylonjs-gui": "5.0.0-alpha.5",
-        "babylonjs-loaders": "5.0.0-alpha.5",
-        "babylonjs-materials": "5.0.0-alpha.5",
-        "babylonjs-serializers": "5.0.0-alpha.5",
-        "babylonjs-gltf2interface": "5.0.0-alpha.5"
+        "babylonjs": "5.0.0-alpha.6",
+        "babylonjs-gui": "5.0.0-alpha.6",
+        "babylonjs-loaders": "5.0.0-alpha.6",
+        "babylonjs-materials": "5.0.0-alpha.6",
+        "babylonjs-serializers": "5.0.0-alpha.6",
+        "babylonjs-gltf2interface": "5.0.0-alpha.6"
     },
     "peerDependencies": {
         "@types/react": ">=16.7.3",

+ 17 - 18
dist/preview release/loaders/babylon.glTF2FileLoader.js

@@ -2047,28 +2047,27 @@ var KHR_materials_variants = /** @class */ (function () {
                     var root = _this._loader.rootBabylonMesh;
                     var metadata = (root.metadata = root.metadata || {});
                     var gltf = (metadata.gltf = metadata.gltf || {});
-                    var extensionMetadata = (gltf[NAME] = gltf[NAME] || { lastSelected: null, original: [], variants: {} });
+                    var extensionMetadata_1 = (gltf[NAME] = gltf[NAME] || { lastSelected: null, original: [], variants: {} });
                     // Store the original material.
-                    extensionMetadata.original.push({ mesh: babylonMesh, material: babylonMesh.material });
-                    // For each mapping, look at the variants and make a new entry for them.
-                    var variants_1 = extensionMetadata.variants;
-                    for (var _i = 0, _a = extension.mappings; _i < _a.length; _i++) {
-                        var mapping = _a[_i];
-                        var _loop_1 = function (variantIndex) {
-                            var variant = _glTFLoader__WEBPACK_IMPORTED_MODULE_0__["ArrayItem"].Get(extensionContext + "/mapping/" + variantIndex, _this._variants, variantIndex);
-                            var material = _glTFLoader__WEBPACK_IMPORTED_MODULE_0__["ArrayItem"].Get("#/materials/", _this._loader.gltf.materials, mapping.material);
-                            promises.push(_this._loader._loadMaterialAsync("#/materials/" + mapping.material, material, babylonMesh, babylonDrawMode, function (babylonMaterial) {
-                                variants_1[variant.name] = variants_1[variant.name] || [];
-                                variants_1[variant.name].push({
+                    extensionMetadata_1.original.push({ mesh: babylonMesh, material: babylonMesh.material });
+                    var _loop_1 = function (mappingIndex) {
+                        var mapping = extension.mappings[mappingIndex];
+                        var material = _glTFLoader__WEBPACK_IMPORTED_MODULE_0__["ArrayItem"].Get(extensionContext + "/mappings/" + mappingIndex + "/material", _this._loader.gltf.materials, mapping.material);
+                        promises.push(_this._loader._loadMaterialAsync("#/materials/" + mapping.material, material, babylonMesh, babylonDrawMode, function (babylonMaterial) {
+                            for (var mappingVariantIndex = 0; mappingVariantIndex < mapping.variants.length; ++mappingVariantIndex) {
+                                var variantIndex = mapping.variants[mappingVariantIndex];
+                                var variant = _glTFLoader__WEBPACK_IMPORTED_MODULE_0__["ArrayItem"].Get("/extensions/" + NAME + "/variants/" + variantIndex, _this._variants, variantIndex);
+                                extensionMetadata_1.variants[variant.name] = extensionMetadata_1.variants[variant.name] || [];
+                                extensionMetadata_1.variants[variant.name].push({
                                     mesh: babylonMesh,
                                     material: babylonMaterial
                                 });
-                            }));
-                        };
-                        for (var _b = 0, _c = mapping.variants; _b < _c.length; _b++) {
-                            var variantIndex = _c[_b];
-                            _loop_1(variantIndex);
-                        }
+                            }
+                        }));
+                    };
+                    // For each mapping, look at the variants and make a new entry for them.
+                    for (var mappingIndex = 0; mappingIndex < extension.mappings.length; ++mappingIndex) {
+                        _loop_1(mappingIndex);
                     }
                 }
             }));

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1 - 1
dist/preview release/loaders/babylon.glTF2FileLoader.js.map


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1 - 1
dist/preview release/loaders/babylon.glTF2FileLoader.min.js


+ 17 - 18
dist/preview release/loaders/babylon.glTFFileLoader.js

@@ -4399,28 +4399,27 @@ var KHR_materials_variants = /** @class */ (function () {
                     var root = _this._loader.rootBabylonMesh;
                     var metadata = (root.metadata = root.metadata || {});
                     var gltf = (metadata.gltf = metadata.gltf || {});
-                    var extensionMetadata = (gltf[NAME] = gltf[NAME] || { lastSelected: null, original: [], variants: {} });
+                    var extensionMetadata_1 = (gltf[NAME] = gltf[NAME] || { lastSelected: null, original: [], variants: {} });
                     // Store the original material.
-                    extensionMetadata.original.push({ mesh: babylonMesh, material: babylonMesh.material });
-                    // For each mapping, look at the variants and make a new entry for them.
-                    var variants_1 = extensionMetadata.variants;
-                    for (var _i = 0, _a = extension.mappings; _i < _a.length; _i++) {
-                        var mapping = _a[_i];
-                        var _loop_1 = function (variantIndex) {
-                            var variant = _glTFLoader__WEBPACK_IMPORTED_MODULE_0__["ArrayItem"].Get(extensionContext + "/mapping/" + variantIndex, _this._variants, variantIndex);
-                            var material = _glTFLoader__WEBPACK_IMPORTED_MODULE_0__["ArrayItem"].Get("#/materials/", _this._loader.gltf.materials, mapping.material);
-                            promises.push(_this._loader._loadMaterialAsync("#/materials/" + mapping.material, material, babylonMesh, babylonDrawMode, function (babylonMaterial) {
-                                variants_1[variant.name] = variants_1[variant.name] || [];
-                                variants_1[variant.name].push({
+                    extensionMetadata_1.original.push({ mesh: babylonMesh, material: babylonMesh.material });
+                    var _loop_1 = function (mappingIndex) {
+                        var mapping = extension.mappings[mappingIndex];
+                        var material = _glTFLoader__WEBPACK_IMPORTED_MODULE_0__["ArrayItem"].Get(extensionContext + "/mappings/" + mappingIndex + "/material", _this._loader.gltf.materials, mapping.material);
+                        promises.push(_this._loader._loadMaterialAsync("#/materials/" + mapping.material, material, babylonMesh, babylonDrawMode, function (babylonMaterial) {
+                            for (var mappingVariantIndex = 0; mappingVariantIndex < mapping.variants.length; ++mappingVariantIndex) {
+                                var variantIndex = mapping.variants[mappingVariantIndex];
+                                var variant = _glTFLoader__WEBPACK_IMPORTED_MODULE_0__["ArrayItem"].Get("/extensions/" + NAME + "/variants/" + variantIndex, _this._variants, variantIndex);
+                                extensionMetadata_1.variants[variant.name] = extensionMetadata_1.variants[variant.name] || [];
+                                extensionMetadata_1.variants[variant.name].push({
                                     mesh: babylonMesh,
                                     material: babylonMaterial
                                 });
-                            }));
-                        };
-                        for (var _b = 0, _c = mapping.variants; _b < _c.length; _b++) {
-                            var variantIndex = _c[_b];
-                            _loop_1(variantIndex);
-                        }
+                            }
+                        }));
+                    };
+                    // For each mapping, look at the variants and make a new entry for them.
+                    for (var mappingIndex = 0; mappingIndex < extension.mappings.length; ++mappingIndex) {
+                        _loop_1(mappingIndex);
                     }
                 }
             }));

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1 - 1
dist/preview release/loaders/babylon.glTFFileLoader.js.map


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1 - 1
dist/preview release/loaders/babylon.glTFFileLoader.min.js


+ 17 - 18
dist/preview release/loaders/babylonjs.loaders.js

@@ -5835,28 +5835,27 @@ var KHR_materials_variants = /** @class */ (function () {
                     var root = _this._loader.rootBabylonMesh;
                     var metadata = (root.metadata = root.metadata || {});
                     var gltf = (metadata.gltf = metadata.gltf || {});
-                    var extensionMetadata = (gltf[NAME] = gltf[NAME] || { lastSelected: null, original: [], variants: {} });
+                    var extensionMetadata_1 = (gltf[NAME] = gltf[NAME] || { lastSelected: null, original: [], variants: {} });
                     // Store the original material.
-                    extensionMetadata.original.push({ mesh: babylonMesh, material: babylonMesh.material });
-                    // For each mapping, look at the variants and make a new entry for them.
-                    var variants_1 = extensionMetadata.variants;
-                    for (var _i = 0, _a = extension.mappings; _i < _a.length; _i++) {
-                        var mapping = _a[_i];
-                        var _loop_1 = function (variantIndex) {
-                            var variant = _glTFLoader__WEBPACK_IMPORTED_MODULE_0__["ArrayItem"].Get(extensionContext + "/mapping/" + variantIndex, _this._variants, variantIndex);
-                            var material = _glTFLoader__WEBPACK_IMPORTED_MODULE_0__["ArrayItem"].Get("#/materials/", _this._loader.gltf.materials, mapping.material);
-                            promises.push(_this._loader._loadMaterialAsync("#/materials/" + mapping.material, material, babylonMesh, babylonDrawMode, function (babylonMaterial) {
-                                variants_1[variant.name] = variants_1[variant.name] || [];
-                                variants_1[variant.name].push({
+                    extensionMetadata_1.original.push({ mesh: babylonMesh, material: babylonMesh.material });
+                    var _loop_1 = function (mappingIndex) {
+                        var mapping = extension.mappings[mappingIndex];
+                        var material = _glTFLoader__WEBPACK_IMPORTED_MODULE_0__["ArrayItem"].Get(extensionContext + "/mappings/" + mappingIndex + "/material", _this._loader.gltf.materials, mapping.material);
+                        promises.push(_this._loader._loadMaterialAsync("#/materials/" + mapping.material, material, babylonMesh, babylonDrawMode, function (babylonMaterial) {
+                            for (var mappingVariantIndex = 0; mappingVariantIndex < mapping.variants.length; ++mappingVariantIndex) {
+                                var variantIndex = mapping.variants[mappingVariantIndex];
+                                var variant = _glTFLoader__WEBPACK_IMPORTED_MODULE_0__["ArrayItem"].Get("/extensions/" + NAME + "/variants/" + variantIndex, _this._variants, variantIndex);
+                                extensionMetadata_1.variants[variant.name] = extensionMetadata_1.variants[variant.name] || [];
+                                extensionMetadata_1.variants[variant.name].push({
                                     mesh: babylonMesh,
                                     material: babylonMaterial
                                 });
-                            }));
-                        };
-                        for (var _b = 0, _c = mapping.variants; _b < _c.length; _b++) {
-                            var variantIndex = _c[_b];
-                            _loop_1(variantIndex);
-                        }
+                            }
+                        }));
+                    };
+                    // For each mapping, look at the variants and make a new entry for them.
+                    for (var mappingIndex = 0; mappingIndex < extension.mappings.length; ++mappingIndex) {
+                        _loop_1(mappingIndex);
                     }
                 }
             }));

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1 - 1
dist/preview release/loaders/babylonjs.loaders.js.map


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1 - 1
dist/preview release/loaders/babylonjs.loaders.min.js


+ 3 - 3
dist/preview release/loaders/package.json

@@ -4,7 +4,7 @@
     },
     "name": "babylonjs-loaders",
     "description": "The Babylon.js file loaders library is an extension you can use to load different 3D file types into a Babylon scene.",
-    "version": "5.0.0-alpha.5",
+    "version": "5.0.0-alpha.6",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -28,8 +28,8 @@
     ],
     "license": "Apache-2.0",
     "dependencies": {
-        "babylonjs-gltf2interface": "5.0.0-alpha.5",
-        "babylonjs": "5.0.0-alpha.5"
+        "babylonjs-gltf2interface": "5.0.0-alpha.6",
+        "babylonjs": "5.0.0-alpha.6"
     },
     "engines": {
         "node": "*"

+ 2 - 2
dist/preview release/materialsLibrary/package.json

@@ -4,7 +4,7 @@
     },
     "name": "babylonjs-materials",
     "description": "The Babylon.js materials library is a collection of advanced materials to be used in a Babylon.js scene.",
-    "version": "5.0.0-alpha.5",
+    "version": "5.0.0-alpha.6",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -28,7 +28,7 @@
     ],
     "license": "Apache-2.0",
     "dependencies": {
-        "babylonjs": "5.0.0-alpha.5"
+        "babylonjs": "5.0.0-alpha.6"
     },
     "engines": {
         "node": "*"

+ 425 - 14
dist/preview release/nodeEditor/babylon.nodeEditor.d.ts

@@ -1681,6 +1681,15 @@ declare module NODEEDITOR {
     }
 }
 declare module NODEEDITOR {
+    export class PropertyChangedEvent {
+        object: any;
+        property: string;
+        value: any;
+        initialValue: any;
+        allowNullValue?: boolean;
+    }
+}
+declare module NODEEDITOR {
     export interface IBooleanLineComponentProps {
         label: string;
         value: boolean;
@@ -1701,6 +1710,146 @@ declare module NODEEDITOR {
     }
 }
 declare module NODEEDITOR {
+    export interface ICheckBoxLineComponentProps {
+        label: string;
+        target?: any;
+        propertyName?: string;
+        isSelected?: () => boolean;
+        onSelect?: (value: boolean) => void;
+        onValueChanged?: () => void;
+        onPropertyChangedObservable?: BABYLON.Observable<PropertyChangedEvent>;
+        disabled?: boolean;
+    }
+    export class CheckBoxLineComponent extends React.Component<ICheckBoxLineComponentProps, {
+        isSelected: boolean;
+        isDisabled?: boolean;
+    }> {
+        private static _UniqueIdSeed;
+        private _uniqueId;
+        private _localChange;
+        constructor(props: ICheckBoxLineComponentProps);
+        shouldComponentUpdate(nextProps: ICheckBoxLineComponentProps, nextState: {
+            isSelected: boolean;
+            isDisabled: boolean;
+        }): boolean;
+        onChange(): void;
+        render(): JSX.Element;
+    }
+}
+declare module NODEEDITOR {
+    interface INumericInputComponentProps {
+        label: string;
+        value: number;
+        step?: number;
+        onChange: (value: number) => void;
+        precision?: number;
+    }
+    export class NumericInputComponent extends React.Component<INumericInputComponentProps, {
+        value: string;
+    }> {
+        static defaultProps: {
+            step: number;
+        };
+        private _localChange;
+        constructor(props: INumericInputComponentProps);
+        shouldComponentUpdate(nextProps: INumericInputComponentProps, nextState: {
+            value: string;
+        }): boolean;
+        updateValue(evt: any): void;
+        onBlur(): void;
+        render(): JSX.Element;
+    }
+}
+declare module NODEEDITOR {
+    export interface IColorPickerComponentProps {
+        value: BABYLON.Color4 | BABYLON.Color3;
+        onColorChanged: (newOne: string) => void;
+    }
+    interface IColorPickerComponentState {
+        pickerEnabled: boolean;
+        color: BABYLON.Color3 | BABYLON.Color4;
+        hex: string;
+    }
+    export class ColorPickerLineComponent extends React.Component<IColorPickerComponentProps, IColorPickerComponentState> {
+        private _floatRef;
+        private _floatHostRef;
+        constructor(props: IColorPickerComponentProps);
+        syncPositions(): void;
+        shouldComponentUpdate(nextProps: IColorPickerComponentProps, nextState: IColorPickerComponentState): boolean;
+        componentDidUpdate(): void;
+        componentDidMount(): void;
+        render(): JSX.Element;
+    }
+}
+declare module NODEEDITOR {
+    export interface IColor3LineComponentProps {
+        label: string;
+        target: any;
+        propertyName: string;
+        onPropertyChangedObservable?: BABYLON.Observable<PropertyChangedEvent>;
+        isLinear?: boolean;
+    }
+    export class Color3LineComponent extends React.Component<IColor3LineComponentProps, {
+        isExpanded: boolean;
+        color: BABYLON.Color3;
+    }> {
+        private _localChange;
+        constructor(props: IColor3LineComponentProps);
+        shouldComponentUpdate(nextProps: IColor3LineComponentProps, nextState: {
+            color: BABYLON.Color3;
+        }): boolean;
+        setPropertyValue(newColor: BABYLON.Color3): void;
+        onChange(newValue: string): void;
+        switchExpandState(): void;
+        raiseOnPropertyChanged(previousValue: BABYLON.Color3): void;
+        updateStateR(value: number): void;
+        updateStateG(value: number): void;
+        updateStateB(value: number): void;
+        copyToClipboard(): void;
+        render(): JSX.Element;
+    }
+}
+declare module NODEEDITOR {
+    export interface IColor4LineComponentProps {
+        label: string;
+        target: any;
+        propertyName: string;
+        onPropertyChangedObservable?: BABYLON.Observable<PropertyChangedEvent>;
+        onChange?: () => void;
+        isLinear?: boolean;
+    }
+    export class Color4LineComponent extends React.Component<IColor4LineComponentProps, {
+        isExpanded: boolean;
+        color: BABYLON.Color4;
+    }> {
+        private _localChange;
+        constructor(props: IColor4LineComponentProps);
+        shouldComponentUpdate(nextProps: IColor4LineComponentProps, nextState: {
+            color: BABYLON.Color4;
+        }): boolean;
+        setPropertyValue(newColor: BABYLON.Color4): void;
+        onChange(newValue: string): void;
+        switchExpandState(): void;
+        raiseOnPropertyChanged(previousValue: BABYLON.Color4): void;
+        updateStateR(value: number): void;
+        updateStateG(value: number): void;
+        updateStateB(value: number): void;
+        updateStateA(value: number): void;
+        copyToClipboard(): void;
+        render(): JSX.Element;
+    }
+}
+declare module NODEEDITOR {
+    export interface IButtonLineComponentProps {
+        data: string;
+        tooltip: string;
+    }
+    export class DraggableLineComponent extends React.Component<IButtonLineComponentProps> {
+        constructor(props: IButtonLineComponentProps);
+        render(): JSX.Element;
+    }
+}
+declare module NODEEDITOR {
     interface IFileButtonLineComponentProps {
         label: string;
         onClick: (file: File) => void;
@@ -1731,6 +1880,117 @@ declare module NODEEDITOR {
     }
 }
 declare module NODEEDITOR {
+    /**
+     * Class used to provide lock mechanism
+     */
+    export class LockObject {
+        /**
+         * Gets or set if the lock is engaged
+         */
+        lock: boolean;
+    }
+}
+declare module NODEEDITOR {
+    interface ISliderLineComponentProps {
+        label: string;
+        target?: any;
+        propertyName?: string;
+        minimum: number;
+        maximum: number;
+        step: number;
+        directValue?: number;
+        useEuler?: boolean;
+        onChange?: (value: number) => void;
+        onInput?: (value: number) => void;
+        onPropertyChangedObservable?: BABYLON.Observable<PropertyChangedEvent>;
+        decimalCount?: number;
+        margin?: boolean;
+    }
+    export class SliderLineComponent extends React.Component<ISliderLineComponentProps, {
+        value: number;
+    }> {
+        private _localChange;
+        constructor(props: ISliderLineComponentProps);
+        shouldComponentUpdate(nextProps: ISliderLineComponentProps, nextState: {
+            value: number;
+        }): boolean;
+        onChange(newValueString: any): void;
+        onInput(newValueString: any): void;
+        prepareDataToRead(value: number): number;
+        render(): JSX.Element;
+    }
+}
+declare module NODEEDITOR {
+    interface IFloatLineComponentProps {
+        label: string;
+        target: any;
+        propertyName: string;
+        lockObject?: LockObject;
+        onChange?: (newValue: number) => void;
+        isInteger?: boolean;
+        onPropertyChangedObservable?: BABYLON.Observable<PropertyChangedEvent>;
+        additionalClass?: string;
+        step?: string;
+        digits?: number;
+        useEuler?: boolean;
+        min?: number;
+        max?: number;
+        smallUI?: boolean;
+        onEnter?: (newValue: number) => void;
+    }
+    export class FloatLineComponent extends React.Component<IFloatLineComponentProps, {
+        value: string;
+    }> {
+        private _localChange;
+        private _store;
+        constructor(props: IFloatLineComponentProps);
+        componentWillUnmount(): void;
+        shouldComponentUpdate(nextProps: IFloatLineComponentProps, nextState: {
+            value: string;
+        }): boolean;
+        raiseOnPropertyChanged(newValue: number, previousValue: number): void;
+        updateValue(valueString: string): void;
+        lock(): void;
+        unlock(): void;
+        render(): JSX.Element;
+    }
+}
+declare module NODEEDITOR {
+    interface IHexLineComponentProps {
+        label: string;
+        target: any;
+        propertyName: string;
+        lockObject?: LockObject;
+        onChange?: (newValue: number) => void;
+        isInteger?: boolean;
+        replaySourceReplacement?: string;
+        onPropertyChangedObservable?: BABYLON.Observable<PropertyChangedEvent>;
+        additionalClass?: string;
+        step?: string;
+        digits?: number;
+        useEuler?: boolean;
+        min?: number;
+    }
+    export class HexLineComponent extends React.Component<IHexLineComponentProps, {
+        value: string;
+    }> {
+        private _localChange;
+        private _store;
+        private _propertyChange;
+        constructor(props: IHexLineComponentProps);
+        componentWillUnmount(): void;
+        shouldComponentUpdate(nextProps: IHexLineComponentProps, nextState: {
+            value: string;
+        }): boolean;
+        raiseOnPropertyChanged(newValue: number, previousValue: number): void;
+        convertToHexString(valueString: string): string;
+        updateValue(valueString: string, raisePropertyChanged: boolean): void;
+        lock(): void;
+        unlock(): void;
+        render(): JSX.Element;
+    }
+}
+declare module NODEEDITOR {
     export interface IIconButtonLineComponentProps {
         icon: string;
         onClick: () => void;
@@ -1759,6 +2019,21 @@ declare module NODEEDITOR {
     }
 }
 declare module NODEEDITOR {
+    interface ILineContainerComponentProps {
+        title: string;
+        children: any[] | any;
+        closed?: boolean;
+    }
+    export class LineContainerComponent extends React.Component<ILineContainerComponentProps, {
+        isExpanded: boolean;
+    }> {
+        constructor(props: ILineContainerComponentProps);
+        switchExpandedState(): void;
+        renderHeader(): JSX.Element;
+        render(): JSX.Element;
+    }
+}
+declare module NODEEDITOR {
     interface ILinkButtonComponentProps {
         label: string;
         buttonLabel: string;
@@ -1783,30 +2058,45 @@ declare module NODEEDITOR {
     }
 }
 declare module NODEEDITOR {
-    interface INumericInputComponentProps {
+    export const Null_Value: number;
+    export class ListLineOption {
         label: string;
         value: number;
-        step?: number;
-        onChange: (value: number) => void;
-        precision?: number;
+        selected?: boolean;
     }
-    export class NumericInputComponent extends React.Component<INumericInputComponentProps, {
-        value: string;
+    export interface IOptionsLineComponentProps {
+        label: string;
+        target: any;
+        propertyName: string;
+        options: ListLineOption[];
+        noDirectUpdate?: boolean;
+        onSelect?: (value: number) => void;
+        extractValue?: () => number;
+        onPropertyChangedObservable?: BABYLON.Observable<PropertyChangedEvent>;
+        allowNullValue?: boolean;
+    }
+    export class OptionsLineComponent extends React.Component<IOptionsLineComponentProps, {
+        value: number;
     }> {
-        static defaultProps: {
-            step: number;
-        };
         private _localChange;
-        constructor(props: INumericInputComponentProps);
-        shouldComponentUpdate(nextProps: INumericInputComponentProps, nextState: {
-            value: string;
+        private remapValueIn;
+        private remapValueOut;
+        constructor(props: IOptionsLineComponentProps);
+        shouldComponentUpdate(nextProps: IOptionsLineComponentProps, nextState: {
+            value: number;
         }): boolean;
-        updateValue(evt: any): void;
-        onBlur(): void;
+        raiseOnPropertyChanged(newValue: number, previousValue: number): void;
+        updateValue(valueString: string): void;
         render(): JSX.Element;
     }
 }
 declare module NODEEDITOR {
+    export class Popup {
+        static CreatePopup(title: string, windowVariableName: string, width?: number, height?: number): HTMLDivElement | null;
+        private static _CopyStyles;
+    }
+}
+declare module NODEEDITOR {
     interface IRadioButtonLineComponentProps {
         onSelectionChangedObservable: BABYLON.Observable<RadioButtonLineComponent>;
         label: string;
@@ -1825,6 +2115,30 @@ declare module NODEEDITOR {
     }
 }
 declare module NODEEDITOR {
+    interface ITextInputLineComponentProps {
+        label: string;
+        lockObject: LockObject;
+        target?: any;
+        propertyName?: string;
+        value?: string;
+        onChange?: (value: string) => void;
+        onPropertyChangedObservable?: BABYLON.Observable<PropertyChangedEvent>;
+    }
+    export class TextInputLineComponent extends React.Component<ITextInputLineComponentProps, {
+        value: string;
+    }> {
+        private _localChange;
+        constructor(props: ITextInputLineComponentProps);
+        componentWillUnmount(): void;
+        shouldComponentUpdate(nextProps: ITextInputLineComponentProps, nextState: {
+            value: string;
+        }): boolean;
+        raiseOnPropertyChanged(newValue: string, previousValue: string): void;
+        updateValue(value: string): void;
+        render(): JSX.Element;
+    }
+}
+declare module NODEEDITOR {
     interface ITextLineComponentProps {
         label?: string;
         value?: string;
@@ -1854,4 +2168,101 @@ declare module NODEEDITOR {
         constructor(props: IValueLineComponentProps);
         render(): JSX.Element;
     }
+}
+declare module NODEEDITOR {
+    interface IVector2LineComponentProps {
+        label: string;
+        target: any;
+        propertyName: string;
+        step?: number;
+        onChange?: (newvalue: BABYLON.Vector2) => void;
+        onPropertyChangedObservable?: BABYLON.Observable<PropertyChangedEvent>;
+    }
+    export class Vector2LineComponent extends React.Component<IVector2LineComponentProps, {
+        isExpanded: boolean;
+        value: BABYLON.Vector2;
+    }> {
+        static defaultProps: {
+            step: number;
+        };
+        private _localChange;
+        constructor(props: IVector2LineComponentProps);
+        shouldComponentUpdate(nextProps: IVector2LineComponentProps, nextState: {
+            isExpanded: boolean;
+            value: BABYLON.Vector2;
+        }): boolean;
+        switchExpandState(): void;
+        raiseOnPropertyChanged(previousValue: BABYLON.Vector2): void;
+        updateStateX(value: number): void;
+        updateStateY(value: number): void;
+        render(): JSX.Element;
+    }
+}
+declare module NODEEDITOR {
+    interface IVector3LineComponentProps {
+        label: string;
+        target: any;
+        propertyName: string;
+        step?: number;
+        onChange?: (newvalue: BABYLON.Vector3) => void;
+        useEuler?: boolean;
+        onPropertyChangedObservable?: BABYLON.Observable<PropertyChangedEvent>;
+        noSlider?: boolean;
+    }
+    export class Vector3LineComponent extends React.Component<IVector3LineComponentProps, {
+        isExpanded: boolean;
+        value: BABYLON.Vector3;
+    }> {
+        static defaultProps: {
+            step: number;
+        };
+        private _localChange;
+        constructor(props: IVector3LineComponentProps);
+        getCurrentValue(): any;
+        shouldComponentUpdate(nextProps: IVector3LineComponentProps, nextState: {
+            isExpanded: boolean;
+            value: BABYLON.Vector3;
+        }): boolean;
+        switchExpandState(): void;
+        raiseOnPropertyChanged(previousValue: BABYLON.Vector3): void;
+        updateVector3(): void;
+        updateStateX(value: number): void;
+        updateStateY(value: number): void;
+        updateStateZ(value: number): void;
+        render(): JSX.Element;
+    }
+}
+declare module NODEEDITOR {
+    interface IVector4LineComponentProps {
+        label: string;
+        target: any;
+        propertyName: string;
+        step?: number;
+        onChange?: (newvalue: BABYLON.Vector4) => void;
+        useEuler?: boolean;
+        onPropertyChangedObservable?: BABYLON.Observable<PropertyChangedEvent>;
+    }
+    export class Vector4LineComponent extends React.Component<IVector4LineComponentProps, {
+        isExpanded: boolean;
+        value: BABYLON.Vector4;
+    }> {
+        static defaultProps: {
+            step: number;
+        };
+        private _localChange;
+        constructor(props: IVector4LineComponentProps);
+        getCurrentValue(): any;
+        shouldComponentUpdate(nextProps: IVector4LineComponentProps, nextState: {
+            isExpanded: boolean;
+            value: BABYLON.Vector4;
+        }): boolean;
+        switchExpandState(): void;
+        raiseOnPropertyChanged(previousValue: BABYLON.Vector4): void;
+        updateVector4(): void;
+        updateStateX(value: number): void;
+        updateStateY(value: number): void;
+        updateStateZ(value: number): void;
+        updateStateW(value: number): void;
+        render(): JSX.Element;
+    }
 }

+ 6 - 6
dist/preview release/nodeEditor/babylon.nodeEditor.max.js

@@ -62793,7 +62793,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony import */ var _diagram_graphFrame__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! ./diagram/graphFrame */ "./diagram/graphFrame.ts");
 /* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! react-dom */ "../../node_modules/react-dom/index.js");
 /* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_15___default = /*#__PURE__*/__webpack_require__.n(react_dom__WEBPACK_IMPORTED_MODULE_15__);
-/* harmony import */ var _sharedComponents_popup__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(/*! ./sharedComponents/popup */ "./sharedComponents/popup.ts");
+/* harmony import */ var _sharedComponents_popup__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(/*! ./sharedComponents/popup */ "./sharedComponents/popup.tsx");
 
 
 
@@ -63576,7 +63576,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react_dom__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _globalState__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./globalState */ "./globalState.ts");
 /* harmony import */ var _graphEditor__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./graphEditor */ "./graphEditor.tsx");
-/* harmony import */ var _src_sharedComponents_popup__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../src/sharedComponents/popup */ "./sharedComponents/popup.ts");
+/* harmony import */ var _src_sharedComponents_popup__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../src/sharedComponents/popup */ "./sharedComponents/popup.tsx");
 /* harmony import */ var _serializationTools__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./serializationTools */ "./serializationTools.ts");
 /* harmony import */ var _components_preview_previewType__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./components/preview/previewType */ "./components/preview/previewType.ts");
 /* harmony import */ var babylonjs_Misc_dataStorage__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! babylonjs/Misc/dataStorage */ "babylonjs/Misc/dataStorage");
@@ -64971,10 +64971,10 @@ module.exports = "data:image/svg+xml,%3Csvg viewBox='0 0 24 24' version='1.1' xm
 
 /***/ }),
 
-/***/ "./sharedComponents/popup.ts":
-/*!***********************************!*\
-  !*** ./sharedComponents/popup.ts ***!
-  \***********************************/
+/***/ "./sharedComponents/popup.tsx":
+/*!************************************!*\
+  !*** ./sharedComponents/popup.tsx ***!
+  \************************************/
 /*! exports provided: Popup */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1 - 1
dist/preview release/nodeEditor/babylon.nodeEditor.max.js.map


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 900 - 33
dist/preview release/nodeEditor/babylon.nodeEditor.module.d.ts


+ 2 - 2
dist/preview release/nodeEditor/package.json

@@ -4,14 +4,14 @@
     },
     "name": "babylonjs-node-editor",
     "description": "The Babylon.js node material editor.",
-    "version": "5.0.0-alpha.5",
+    "version": "5.0.0-alpha.6",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
     },
     "license": "Apache-2.0",
     "dependencies": {
-        "babylonjs": "5.0.0-alpha.5"
+        "babylonjs": "5.0.0-alpha.6"
     },
     "files": [
         "babylon.nodeEditor.max.js.map",

+ 1 - 1
dist/preview release/package.json

@@ -7,7 +7,7 @@
     ],
     "name": "babylonjs",
     "description": "Babylon.js is a JavaScript 3D engine based on webgl.",
-    "version": "5.0.0-alpha.5",
+    "version": "5.0.0-alpha.6",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"

+ 1 - 1
dist/preview release/packagesSizeBaseLine.json

@@ -1 +1 @@
-{"thinEngineOnly":130129,"engineOnly":167503,"sceneOnly":532854,"minGridMaterial":708600,"minStandardMaterial":871072}
+{"thinEngineOnly":130230,"engineOnly":167557,"sceneOnly":532854,"minGridMaterial":708600,"minStandardMaterial":871072}

+ 2 - 2
dist/preview release/postProcessesLibrary/package.json

@@ -4,7 +4,7 @@
     },
     "name": "babylonjs-post-process",
     "description": "The Babylon.js materials library is a collection of advanced materials to be used in a Babylon.js scene.",
-    "version": "5.0.0-alpha.5",
+    "version": "5.0.0-alpha.6",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -28,7 +28,7 @@
     ],
     "license": "Apache-2.0",
     "dependencies": {
-        "babylonjs": "5.0.0-alpha.5"
+        "babylonjs": "5.0.0-alpha.6"
     },
     "engines": {
         "node": "*"

+ 2 - 2
dist/preview release/proceduralTexturesLibrary/package.json

@@ -4,7 +4,7 @@
     },
     "name": "babylonjs-procedural-textures",
     "description": "The Babylon.js materials library is a collection of advanced materials to be used in a Babylon.js scene.",
-    "version": "5.0.0-alpha.5",
+    "version": "5.0.0-alpha.6",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -28,7 +28,7 @@
     ],
     "license": "Apache-2.0",
     "dependencies": {
-        "babylonjs": "5.0.0-alpha.5"
+        "babylonjs": "5.0.0-alpha.6"
     },
     "engines": {
         "node": "*"

+ 0 - 1
dist/preview release/serializers/babylon.glTF2Serializer.js

@@ -4724,7 +4724,6 @@ var _GLTFMaterialExporter = /** @class */ (function () {
         return this._finishMaterial(promises, glTFMaterial, babylonPBRMaterial, mimeType);
     };
     _GLTFMaterialExporter.prototype.getPixelsFromTexture = function (babylonTexture) {
-        // TODO WEBGPU remove the as unknown cast once using the new babylonjs package to compile the glTF material exporter
         var pixels = babylonTexture.textureType === babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_1__["Constants"].TEXTURETYPE_UNSIGNED_INT ? babylonTexture.readPixels() : babylonTexture.readPixels();
         return pixels;
     };

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1 - 1
dist/preview release/serializers/babylon.glTF2Serializer.js.map


+ 0 - 1
dist/preview release/serializers/babylonjs.serializers.js

@@ -4909,7 +4909,6 @@ var _GLTFMaterialExporter = /** @class */ (function () {
         return this._finishMaterial(promises, glTFMaterial, babylonPBRMaterial, mimeType);
     };
     _GLTFMaterialExporter.prototype.getPixelsFromTexture = function (babylonTexture) {
-        // TODO WEBGPU remove the as unknown cast once using the new babylonjs package to compile the glTF material exporter
         var pixels = babylonTexture.textureType === babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_1__["Constants"].TEXTURETYPE_UNSIGNED_INT ? babylonTexture.readPixels() : babylonTexture.readPixels();
         return pixels;
     };

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1 - 1
dist/preview release/serializers/babylonjs.serializers.js.map


+ 3 - 3
dist/preview release/serializers/package.json

@@ -4,7 +4,7 @@
     },
     "name": "babylonjs-serializers",
     "description": "The Babylon.js serializers library is an extension you can use to serialize Babylon scenes.",
-    "version": "5.0.0-alpha.5",
+    "version": "5.0.0-alpha.6",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -28,8 +28,8 @@
     ],
     "license": "Apache-2.0",
     "dependencies": {
-        "babylonjs": "5.0.0-alpha.5",
-        "babylonjs-gltf2interface": "5.0.0-alpha.5"
+        "babylonjs": "5.0.0-alpha.6",
+        "babylonjs-gltf2interface": "5.0.0-alpha.6"
     },
     "engines": {
         "node": "*"

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 893 - 483
dist/preview release/viewer/babylon.module.d.ts


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 81 - 61
dist/preview release/viewer/babylon.viewer.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 3 - 3
dist/preview release/viewer/babylon.viewer.max.js


+ 19 - 4
dist/preview release/what's new.md

@@ -2,6 +2,8 @@
 
 ## Major updates
 
+- Infinite Morph Targets: When supported (WebGL2+) you are no more limited to 4 morph targets per mesh ([Deltakosh](https://github.com/deltakosh))
+
 ## Updates
 
 ### General
@@ -12,11 +14,13 @@
 - Added support for infinite perspective cameras ([Deltakosh](https://github.com/deltakosh))
 - Added ability to enable/disable ArcRotateCamera zoom on multiTouch event ([NicolasBuecher](https://github.com/NicolasBuecher))
 - Moving button to shared uI folder.([msDestiny14](https://github.com/msDestiny14))
-- Moving additional components to shared UI folder.([msDestiny14](https://github.com/msDestiny14))
+- Moving linecontainer component to shared UI folder. ([msDestiny14](https://github.com/msDestiny14))
 - Added encapsulate and encapsulateBoundingInfo methods to BoundingInfo. ([Tolo789](https://github.com/Tolo789))
+- Added onLoadObservable to the textureDome class(es) ([RaananW](https://github.com/RaananW))
 - Modified InputManager to use DeviceInputSystem ([PolygonalSun](https://github.com/PolygonalSun))
 
 ### Engine
+
 - Moved all instance data from Geometry to Mesh such that the same Geometry objects can be used by many meshes with instancing. Reduces memory consumption on CPU/GPU. ([breakin](https://github.com/breakin)
 
 ### Loaders
@@ -49,6 +53,10 @@
 - Increased float precision to 4([msDestiny14](https://github.com/msDestiny14))
 - Added ability to make input node's properties visible in the properties of a custom frame ([msDestiny14](https://github.com/msDestiny14))
 
+### GUIEditor
+
+- Added GUI Editor project to master. ([msDestiny14](https://github.com/msDestiny14))
+
 ### GUI
 
 - Added a `FocusableButton` gui control to simplify creating menus with keyboard navigation ([Flux159](https://github.com/Flux159))
@@ -62,6 +70,12 @@
 - Added a way to extend the XRSessionInit Object from inside of a feature ([RaananW](https://github.com/RaananW))
 - Added image tracking feature ([RaananW](https://github.com/RaananW))
 - Pointer Events of WebXR controllers have pointerType `xr` ([RaananW](https://github.com/RaananW))
+- better support for custom hand meshes ([RaananW](https://github.com/RaananW))
+- Allow disabling of the WebXRControllerPointerSelection feature as part of the WebXR Default Experience ([rgerd](https://github.com/rgerd))
+
+### Viewer
+
+- Fixed an issue with dual callback binding in case of a forced redraw ([#9608](https://github.com/BabylonJS/Babylon.js/issues/9608)) ([RaananW](https://github.com/RaananW))
 
 ## Bugs
 
@@ -71,6 +85,7 @@
 - Fix issue where PBRSpecularGlossiness materials were excluded from export [#9423](https://github.com/BabylonJS/Babylon.js/issues/9423)([Drigax](https://github.com/drigax))
 - Fix issue when scaling is reapplied with BoundingBoxGizmo and GizmoManager ([CedricGuillemet](https://github.com/CedricGuillemet))
 - Fix direct loading of a glTF string that has base64-encoded URI. ([bghgary](https://github.com/bghgary))
+- Fix capsule impostor size computation for ammojs ([CedricGuillemet](https://github.com/CedricGuillemet))
 - Fix crash of some node materials using instances on iOS ([Popov72](https://github.com/Popov72))
 - Fix the code generated for the NME gradient block ([Popov72](https://github.com/Popov72))
 - Fix ssao2RenderingPipeline for orthographic cameras ([Kesshi](https://github.com/Kesshi))
@@ -90,13 +105,13 @@
 - Fix SSAO2 with PrePass sometimes causing colors brighter than they should be ([CraigFeldspar](https://github.com/CraigFeldspar))
 - Fix PostProcess sharing between cameras/renderTargets, that would create/destroy a texture on every frame ([CraigFeldspar](https://github.com/CraigFeldspar))
 - Fix for DualSense gamepads being incorrectly read as DualShock gamepads ([PolygonalSun](https://github.com/PolygonalSun))
-
+- Fix crash when cloning material in `AssetContainer.instantiateModelsToScene` when mesh is an instanced mesh ([Popov72](https://github.com/Popov72))
 
 ## Breaking changes
 
 - [List of breaking changes introduced by ou compatibility with WebGPU](https://doc.babylonjs.com/advanced_topics/webGPU/webGPUBreakingChanges)
-    - [ReadPixels and ProceduralTexture.getContent are now async](https://doc.babylonjs.com/advanced_topics/webGPU/webGPUBreakingChanges#readpixels-is-now-asynchronous)
-    - [Shader support differences](https://doc.babylonjs.com/advanced_topics/webGPU/webGPUBreakingChanges#shader-code-differences)
+  - [ReadPixels and ProceduralTexture.getContent are now async](https://doc.babylonjs.com/advanced_topics/webGPU/webGPUBreakingChanges#readpixels-is-now-asynchronous)
+  - [Shader support differences](https://doc.babylonjs.com/advanced_topics/webGPU/webGPUBreakingChanges#shader-code-differences)
 - Use both `mesh.visibility` and `material.alpha` values to compute the global alpha value used by the soft transparent shadow rendering code. Formerly was only using `mesh.visibility` ([Popov72](https://github.com/Popov72))
 - Depth renderer: don't render mesh if `infiniteDistance = true` or if `material.disableDepthWrite = true` ([Popov72](https://github.com/Popov72))
 - Mesh.createInstance no longer make a unique Geometry for the Mesh so updating one Geometry can affect more meshes than before. Use Mesh.makeUniqueGeometry for old behaviour. ([breakin](https://github.com/breakin))

+ 1 - 0
guiEditor/README-ES6.md

@@ -0,0 +1 @@
+Gui Editor

+ 18 - 0
guiEditor/README.md

@@ -0,0 +1,18 @@
+# Babylon.js Gui Editor
+
+An extension to easily create or update GUI.
+
+## Usage
+### Online method
+Call the method `Show` of the `BABYLON.GuiEditor` class: 
+```
+BABYLON.GuiEditor.Show({hostElement: document.getElementById("host")});
+```
+This method will retrieve dynamically the library `babylon.guiEditor.js`, download it and add
+it to the html page.
+
+### Offline method
+If you don't have access to internet, the gui editor should be imported manually in your HTML page :
+```
+<script src="babylon.guiEditor.js" />
+``` 

+ 3 - 0
guiEditor/imgs/downArrow.svg

@@ -0,0 +1,3 @@
+<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" width="17" height="17">
+  <path d="M4.21967 8.46967C4.51256 8.17678 4.98744 8.17678 5.28033 8.46967L12 15.1893L18.7197 8.46967C19.0126 8.17678 19.4874 8.17678 19.7803 8.46967C20.0732 8.76256 20.0732 9.23744 19.7803 9.53033L12.5303 16.7803C12.2374 17.0732 11.7626 17.0732 11.4697 16.7803L4.21967 9.53033C3.92678 9.23744 3.92678 8.76256 4.21967 8.46967Z" fill="white" />
+</svg>

+ 43 - 0
guiEditor/public/index-local.html

@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+    <title>Gui Editor - Local Development</title>    
+    <meta name="viewport" content="width=device-width, user-scalable=no">
+    <link rel="shortcut icon" href="https://www.babylonjs.com/favicon.ico">
+
+    <script src="https://code.jquery.com/pep/0.4.2/pep.min.js"></script>
+    <script src="../../Tools/DevLoader/BabylonLoader.js"></script>
+    <link rel="stylesheet" href="https://use.typekit.net/cta4xsb.css"></link>
+
+    <style>
+        html,
+        body {
+            width: 100%;
+            height: 100%;
+            padding: 0;
+            margin: 0;
+            overflow: hidden;
+        }
+
+        #host-element {
+            width: 100%;
+            height: 100%;            
+        }
+    </style>
+</head>
+
+<body>
+    <div id="host-element">
+    </div>
+    <script>
+        // Load the scripts + map file to allow vscode debug.
+        BABYLONDEVTOOLS.Loader
+            .require("index.js")
+            .load(() => {
+                BABYLONDEVTOOLS.Loader.debugShortcut(engine);
+            });
+    </script>
+</body>
+
+</html>

+ 42 - 0
guiEditor/public/index.html

@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+    <title>Babylon.js Gui Editor</title>
+
+    <meta name="viewport" content="width=device-width, user-scalable=no">
+    <link rel="shortcut icon" href="https://www.babylonjs.com/favicon.ico">
+
+    <script src="https://code.jquery.com/pep/0.4.2/pep.min.js"></script>
+    <link rel="stylesheet" href="https://use.typekit.net/cta4xsb.css"></link>
+    <script src="https://preview.babylonjs.com/babylon.js"></script>
+    <script src="https://preview.babylonjs.com/loaders/babylonjs.loaders.min.js"></script>
+    <script src="https://preview.babylonjs.com/guiEditor/babylon.guiEditor.js"></script>    
+    
+    <style>
+        html,
+        body {
+            width: 100%;
+            height: 100%;
+            padding: 0;
+            margin: 0;
+            overflow: hidden;
+        }
+
+        #host-element {
+            width: 100%;
+            height: 100%;               
+            padding: 0;
+            margin: 0;
+            overflow: hidden;         
+        }
+    </style>
+</head>
+
+<body>    
+    <div id="host-element">
+    </div>
+    <script src="index.js"></script>
+</body>
+
+</html>

+ 116 - 0
guiEditor/public/index.js

@@ -0,0 +1,116 @@
+var snippetUrl = "https://snippet.babylonjs.com";
+var currentSnippetToken;
+var previousHash = "";
+
+var customLoadObservable = new BABYLON.Observable();
+var editorDisplayed = false;
+
+var cleanHash = function () {
+    var splits = decodeURIComponent(location.hash.substr(1)).split("#");
+
+    if (splits.length > 2) {
+        splits.splice(2, splits.length - 2);
+    }
+
+    location.hash = splits.join("#");
+}
+
+var checkHash = function () {
+    if (location.hash) {
+        if (previousHash != location.hash) {
+            cleanHash();
+
+            previousHash = location.hash;
+
+            try {
+                var xmlHttp = new XMLHttpRequest();
+                xmlHttp.onreadystatechange = function () {
+                    if (xmlHttp.readyState == 4) {
+                        if (xmlHttp.status == 200) {
+                            
+                            //TODO: Implement
+                            //var snippet = JSON.parse(JSON.parse(xmlHttp.responseText).jsonPayload);
+                            showEditor();
+                        }
+                    }
+                }
+
+                var hash = location.hash.substr(1);
+                currentSnippetToken = hash.split("#")[0];
+                xmlHttp.open("GET", snippetUrl + "/" + hash.replace("#", "/"));
+                xmlHttp.send();
+            } catch (e) {
+
+            }
+        }
+    }
+
+    setTimeout(checkHash, 200);
+}
+
+var showEditor = function() {
+    editorDisplayed = true;
+    var hostElement = document.getElementById("host-element");
+
+    BABYLON.GuiEditor.Show({
+        hostElement: hostElement,
+        customLoadObservable: customLoadObservable,
+        customSave: {
+            label: "Save as unique URL",
+            action: (data) => {
+                return new Promise((resolve, reject) => {
+                    var xmlHttp = new XMLHttpRequest();
+                    xmlHttp.onreadystatechange = function () {
+                        if (xmlHttp.readyState == 4) {
+                            if (xmlHttp.status == 200) {
+                                var baseUrl = location.href.replace(location.hash, "").replace(location.search, "");
+                                var snippet = JSON.parse(xmlHttp.responseText);
+                                var newUrl = baseUrl + "#" + snippet.id;
+                                currentSnippetToken = snippet.id;
+                                if (snippet.version && snippet.version != "0") {
+                                    newUrl += "#" + snippet.version;
+                                }
+                                location.href = newUrl;
+                                resolve();
+                            }
+                            else {
+                                reject(`Unable to save your gui layout. It may be too large (${(dataToSend.payload.length / 1024).toFixed(2)}`);
+                            }
+                        }
+                    }
+        
+                    xmlHttp.open("POST", snippetUrl + (currentSnippetToken ? "/" + currentSnippetToken : ""), true);
+                    xmlHttp.setRequestHeader("Content-Type", "application/json");
+        
+                    var dataToSend = {
+                        payload : JSON.stringify({
+                            guiLayout: data
+                        }),
+                        name: "",
+                        description: "",
+                        tags: ""
+                    };
+        
+                    xmlHttp.send(JSON.stringify(dataToSend));
+                });
+            }
+        }
+    });
+}
+
+// Let's start
+if (BABYLON.Engine.isSupported()) {
+    var canvas = document.createElement("canvas");
+    var engine = new BABYLON.Engine(canvas, false);
+    var scene = new BABYLON.Scene(engine);
+
+    // Set to default
+    if (!location.hash) {
+        showEditor();
+    }
+}
+else {
+    alert('Babylon.js is not supported.')
+}
+
+checkHash();

+ 211 - 0
guiEditor/src/components/guiList/guiList.scss

@@ -0,0 +1,211 @@
+#guiList {
+    background: #333333;
+    height: 100%;
+    margin: 0;
+    padding: 0;
+    display: grid;
+    width: 100%; 
+    overflow: hidden;
+
+    .panes {
+        overflow: hidden;
+        
+        .pane {
+            color: white;
+
+            overflow: hidden;
+            height: 100%;
+
+            -webkit-user-select: none; 
+            -moz-user-select: none;   
+            -ms-user-select: none;    
+            user-select: none;     
+
+            .filter {
+                display: flex;
+                align-items: stretch;
+        
+                input {
+                    width: 100%;
+                    margin: 10px 10px 5px 10px;
+                    display: block;
+                    border: none;
+                    padding: 0;
+                    border-bottom: solid 1px rgb(51, 122, 183);
+                    background: linear-gradient(to bottom, rgba(255, 255, 255, 0) 96%, rgb(51, 122, 183) 4%);
+                    background-position: -1000px 0;
+                    background-size: 1000px 100%;
+                    background-repeat: no-repeat;  
+                    color:white;    
+                }
+        
+                input:focus  {
+                    box-shadow: none;
+                    outline: none;
+                    background-position: 0 0;
+                }
+
+                input::placeholder {
+                    color: gray;
+                }
+            }
+
+            .list-container {
+                overflow-x: hidden;
+                overflow-y: auto;
+                height: calc(100% - 32px);
+
+                .underline {
+                    border-bottom: 0.5px solid rgba(255, 255, 255, 0.5);
+                }
+
+                .draggableLine {
+                    height: 30px;
+                    display: grid;
+                    align-items: center;
+                    justify-items: stretch;
+                    background: #222222;
+                    cursor: grab;
+                    text-align: center;
+                    margin: 0;
+                    box-sizing: border-box;
+
+                    &:hover {
+                        background: rgb(51, 122, 183);
+                        color: white;
+                    }
+                }
+
+                .nonDraggableLine {
+                    height: 30px;
+                    display: grid;
+                    align-items: center;
+                    justify-items: stretch;
+                    background: #222222;
+                    text-align: center;
+                    margin: 0;
+                    box-sizing: border-box;
+                }
+
+                .withButton {
+                    height: 30px;
+                    position: relative;
+                    .icon {
+                        position: absolute;
+                        right: 4px;
+                        top: 5px;
+                        &:hover {
+                            cursor: pointer;
+                        }
+
+                        .img {
+                            height: 17px;
+                            width: 17px;
+                        }
+                    }
+
+                    .buttonLine {
+                        height: 30px;
+                        display: grid;
+                        align-items: center;
+                        justify-items: stretch;
+                        padding-bottom: 5px;
+                        position: absolute;
+                        right: 0px;
+                        top: 2px;
+                        input[type="file"] {
+                            display: none;
+                        }
+                
+                        .file-upload {            
+                            background: transparent;
+                            border: transparent;
+                            padding: 15px 200px;
+                            opacity: 0.9;
+                            cursor: pointer;
+                            text-align: center;
+                        }
+                
+                        .file-upload:hover {
+                            opacity: 1.0;
+                        }
+                
+                        .file-upload:active {
+                            transform: scale(0.98);
+                            transform-origin: 0.5 0.5;
+                        }
+                
+                        button {
+                            background: transparent;
+                            border: transparent;
+                            margin: 5px 10px 5px 10px;
+                            color:white;
+                            padding: 4px 5px;
+                            opacity: 0.9;
+                        }
+                
+                        button:hover {
+                            opacity: 0.0;
+                        }
+                
+                        button:active {
+                            background: transparent;
+                        }   
+                        
+                        button:focus {
+                            border: transparent;
+                            outline: 0px;
+                        }  
+                    }
+                    
+                }                
+
+                .paneContainer {
+                    margin-top: 3px;
+                    display:grid;
+                    grid-template-rows: 100%;
+                    grid-template-columns: 100%;
+
+                    .paneContainer-content {
+                        grid-row: 1;
+                        grid-column: 1;
+
+                        .header {
+                            display: grid;
+                            grid-template-columns: 1fr auto;
+                            background: #555555;    
+                            height: 30px;   
+                            padding-right: 5px;                        
+                            cursor: pointer;
+                            
+                            .title {                                
+                                border-left: 3px solid transparent;
+                                padding-left: 5px;
+                                grid-column: 1;
+                                display: flex;
+                                align-items: center;
+                            }
+
+                            .collapse {
+                                grid-column: 2;
+                                display: flex;
+                                align-items: center;  
+                                justify-items: center;
+                                transform-origin: center;
+
+                                &.closed {
+                                    transform: rotate(180deg);
+                                }
+                            }                        
+                        }
+
+                        .paneList > div:not(:last-child) {
+                            border-bottom: 1px solid rgba(255, 255, 255, 0.3);
+                        }
+                    }
+                }
+            }    
+        }
+    }
+}
+

+ 89 - 0
guiEditor/src/components/guiList/guiListComponent.tsx

@@ -0,0 +1,89 @@
+import * as React from "react";
+import { GlobalState } from "../../globalState";
+import { LineContainerComponent } from "../../sharedUiComponents/lines/lineContainerComponent";
+import { DraggableLineComponent } from "../../sharedUiComponents/lines/draggableLineComponent";
+import { Observer } from "babylonjs/Misc/observable";
+import { Nullable } from "babylonjs/types";
+
+require("./guiList.scss");
+
+interface IGuiListComponentProps {
+    globalState: GlobalState;
+}
+
+export class GuiListComponent extends React.Component<IGuiListComponentProps, { filter: string }> {
+    private _onResetRequiredObserver: Nullable<Observer<void>>;
+
+    private static _Tooltips: { [key: string]: string } = {
+        Button: "A simple button",
+    };
+
+    constructor(props: IGuiListComponentProps) {
+        super(props);
+
+        this.state = { filter: "" };
+
+        this._onResetRequiredObserver = this.props.globalState.onResetRequiredObservable.add(() => {
+            this.forceUpdate();
+        });
+    }
+
+    componentWillUnmount() {
+        this.props.globalState.onResetRequiredObservable.remove(this._onResetRequiredObserver);
+    }
+
+    filterContent(filter: string) {
+        this.setState({ filter: filter });
+    }
+
+    render() {
+        // Block types used to create the menu from
+        const allBlocks: any = {
+            Buttons: ["TextButton", "ImageButton"],
+            Controls: ["Slider", "Checkbox", "ColorPicker", "VisualKeyboard"],
+            Containers: ["DisplayGrid", "Grid", "StackPanel"],
+            Shapes: ["Ellipse", "Image", "Line", "Rectangle"],
+            Inputs: ["Text", "IntputText", "InputPassword"],
+        };
+
+        // Create node menu
+        var blockMenu = [];
+        for (var key in allBlocks) {
+            var blockList = (allBlocks as any)[key]
+                .filter((b: string) => !this.state.filter || b.toLowerCase().indexOf(this.state.filter.toLowerCase()) !== -1)
+                .sort((a: string, b: string) => a.localeCompare(b))
+                .map((block: any, i: number) => {
+                    return <DraggableLineComponent key={block} data={block} tooltip={GuiListComponent._Tooltips[block] || ""} />;
+                });
+
+            if (blockList.length) {
+                blockMenu.push(
+                    <LineContainerComponent key={key + " blocks"} title={key.replace("__", ": ").replace("_", " ")} closed={false}>
+                        {blockList}
+                    </LineContainerComponent>
+                );
+            }
+        }
+
+        return (
+            <div id="guiList">
+                <div className="panes">
+                    <div className="pane">
+                        <div className="filter">
+                            <input
+                                type="text"
+                                placeholder="Filter"
+                                onFocus={() => (this.props.globalState.blockKeyboardEvents = true)}
+                                onBlur={(evt) => {
+                                    this.props.globalState.blockKeyboardEvents = false;
+                                }}
+                                onChange={(evt) => this.filterContent(evt.target.value)}
+                            />
+                        </div>
+                        <div className="list-container">{blockMenu}</div>
+                    </div>
+                </div>
+            </div>
+        );
+    }
+}

+ 20 - 0
guiEditor/src/components/log/log.scss

@@ -0,0 +1,20 @@
+#log-console {
+    background: #333333;
+    height: 120px;
+    box-sizing: border-box;
+    margin: 0;
+    padding: 10px;
+    width: 100%; 
+    overflow: hidden;
+    overflow-y: auto;
+
+    .log {
+        color: white;
+        font-size: 14px;
+        font-family: 'Courier New', Courier, monospace;
+
+        &.error {
+            color:red;
+        }
+    }
+}

+ 63 - 0
guiEditor/src/components/log/logComponent.tsx

@@ -0,0 +1,63 @@
+
+import * as React from "react";
+import { GlobalState } from '../../globalState';
+import * as ReactDOM from 'react-dom';
+
+require("./log.scss");
+
+interface ILogComponentProps {
+    globalState: GlobalState;
+}
+
+export class LogEntry {
+    constructor(public message: string, public isError: boolean) {
+
+    }
+}
+
+export class LogComponent extends React.Component<ILogComponentProps, { logs: LogEntry[] }> {
+
+    constructor(props: ILogComponentProps) {
+        super(props);
+
+        this.state = { logs: [] };
+    }
+
+    componentDidMount() {
+        this.props.globalState.onLogRequiredObservable.add(log => {
+            let newLogArray = this.state.logs.map(number => number);
+            newLogArray.push(log);
+            this.setState({ logs: newLogArray });
+        });
+    }
+
+    componentDidUpdate() {
+        const logConsole = ReactDOM.findDOMNode(this.refs["log-console"]) as HTMLElement;
+        if (!logConsole) {
+            return;
+        }
+
+        logConsole.scrollTop = logConsole.scrollHeight;
+    }
+
+    render() {
+        var today = new Date();
+        var h = today.getHours();
+        var m = today.getMinutes();
+        var s = today.getSeconds();
+
+        return (
+            <div id="log-console" ref={"log-console"} >
+                {
+                    this.state.logs.map((l, i) => {
+                        return (
+                            <div key={i} className={"log" + (l.isError ? " error" : "")}>
+                                {h + ":" + m + ":" + s+ ": " + l.message}
+                            </div>
+                        )
+                    })
+                }
+            </div>
+        );
+    }
+}

+ 798 - 0
guiEditor/src/components/propertyTab/propertyTab.scss

@@ -0,0 +1,798 @@
+#propertyTab {
+    $line-padding-left: 5px;
+    color:white;
+    background: #333333;
+
+      #header {
+        height: 30px;
+        font-size: 16px;
+        color: white;
+        background: #222222;
+        grid-row: 1;
+        text-align: center;
+        display: grid;
+        grid-template-columns: 30px 1fr;        
+        -webkit-user-select: none; 
+        -moz-user-select: none;   
+        -ms-user-select: none;    
+        user-select: none;                
+
+        #logo {
+            position: relative;
+            grid-column: 1; 
+            width: 24px;
+            height: 24px;
+            left:0;
+            display: flex;
+            align-self: center;   
+            justify-self: center;
+        }        
+
+        #title {
+            grid-column: 2; 
+            display: grid;
+            align-items: center;   
+            text-align: center;
+        }
+    }
+
+    .range {
+        -webkit-appearance: none;
+        width: 120px;
+        height: 6px;
+        background: #d3d3d3;
+        border-radius: 5px;
+        outline: none;
+        opacity: 0.7;
+        -webkit-transition: .2s;
+        transition: opacity .2s;
+    }
+    
+    .range:hover {
+        opacity: 1;
+    }
+    
+    .range::-webkit-slider-thumb {
+        -webkit-appearance: none;
+        appearance: none;
+        width: 14px;
+        height: 14px;
+        border-radius: 50%;
+        background: rgb(51, 122, 183);
+        cursor: pointer;
+    }
+    
+    .range::-moz-range-thumb {
+        width: 14px;
+        height: 14px;
+        border-radius: 50%;
+        background: rgb(51, 122, 183);
+        cursor: pointer;
+    }
+
+    input[type="color"] {
+        -webkit-appearance: none;
+        border: 1px solid rgba(255, 255, 255, 0.5);
+        padding: 0;
+        width: 30px;
+        height: 20px;
+    }
+    input[type="color"]::-webkit-color-swatch-wrapper {
+        padding: 0;
+    }
+    input[type="color"]::-webkit-color-swatch {
+        border: none;
+    }
+
+    .sliderLine {
+        padding-left: $line-padding-left;
+        height: 30px;
+        display: grid;
+        grid-template-rows: 100%;
+        grid-template-columns: 1fr 40px;
+
+        .label { 
+            grid-column: 1;
+            display: flex;
+            align-items: center;
+        }
+
+        .slider {
+            grid-column: 3;
+            grid-row: 1;
+            margin-right: 5px;
+            width: 90%;
+            display: flex;
+            align-items: center;
+        }
+
+        .floatLine {
+            padding-left: $line-padding-left;
+    
+            .label {
+                grid-column: 1;
+                display: flex;
+                align-items: center;
+            }
+        
+            .short {
+                grid-column: 1; 
+                display: flex;
+                align-items: center;
+                
+                input {
+                    width: 27px;
+                }
+                
+                input::-webkit-outer-spin-button,
+                input::-webkit-inner-spin-button {
+                  -webkit-appearance: none;
+                  margin: 0;
+                }
+    
+                input[type=number] {
+                    -moz-appearance: textfield;
+                }
+            }
+        }  
+    }     
+
+    .textInputLine {
+        padding-left: $line-padding-left;
+        height: 30px;
+        display: grid;
+        grid-template-columns: 1fr 120px auto;
+
+        .label {
+            grid-column: 1;
+            display: flex;
+            align-items: center;
+        }
+
+        .value {                        
+            display: flex;
+            align-items: center;
+            grid-column: 2;
+            
+            input {
+                width: calc(100% - 5px);
+            }
+        }
+    }
+    
+    .textInputArea {
+        padding-left: $line-padding-left;
+        height: 100%;
+        display: grid;
+        grid-template-columns: 1fr 120px;
+
+        .label {
+            grid-column: 1;
+            display: flex;
+            align-items: center;
+        }
+
+        .value {                        
+            display: flex;
+            align-items: center;
+            grid-column: 2;
+            
+            textarea {
+                width: calc(150% - 5px);
+                margin-left: -50%;
+                height: 40px;
+            }
+        }
+    }
+    
+    .paneContainer {
+        margin-top: 3px;
+        display:grid;
+        grid-template-rows: 100%;
+        grid-template-columns: 100%;
+        
+        .paneList {
+            border-left: 3px solid transparent;
+        }
+
+        &:hover {  
+            .paneList {                      
+                border-left: 3px solid rgba(51, 122, 183, 0.8);
+            }
+
+            .paneContainer-content {
+                .header {
+                    .title {   
+                        border-left: 3px solid rgb(51, 122, 183);
+                    }
+                }
+            }
+        }
+        
+        .paneContainer-highlight-border {
+            grid-row: 1;
+            grid-column: 1;
+            opacity: 1;
+            border: 3px solid red;
+            transition: opacity 250ms;
+            pointer-events: none;
+            
+            &.transparent {
+                opacity: 0;
+            }
+        }
+
+        .paneContainer-content {
+            grid-row: 1;
+            grid-column: 1;
+
+            .header {
+                display: grid;
+                grid-template-columns: 1fr auto;
+                background: #555555;    
+                height: 30px;   
+                padding-right: 5px;                        
+                cursor: pointer;
+                
+                .title {                                
+                    border-left: 3px solid transparent;
+                    padding-left: 5px;
+                    grid-column: 1;
+                    display: flex;
+                    align-items: center;
+                }
+
+                .collapse {
+                    grid-column: 2;
+                    display: flex;
+                    align-items: center;  
+                    justify-items: center;
+                    transform-origin: center;
+
+                    &.closed {
+                        transform: rotate(180deg);
+                    }
+                }                        
+            }
+
+            .paneList > div:not(:last-child) {
+                border-bottom: 0.5px solid rgba(255, 255, 255, 0.1);
+            }
+
+            .fragment > div:not(:last-child)  {
+                border-bottom: 0.5px solid rgba(255, 255, 255, 0.1);
+            }
+        }
+    }
+
+    .color-picker {
+        height: calc(100% - 8px);
+        margin: 4px;
+        width: calc(100% - 8px);
+
+        .color-rect {
+            height: calc(100% - 4px);
+            border: 2px white solid;
+            cursor: pointer;
+            min-height: 18px;
+        }
+
+        .color-picker-cover {
+            position: fixed;
+            top: 0px;
+            right: 0px;
+            bottom: 0px;
+            left: 0px;
+        }
+
+        .color-picker-float {
+            z-index: 2;
+            position: absolute;  
+        }                
+    }
+
+    .gradient-step {
+        display: grid;
+        grid-template-rows: 100%;
+        grid-template-columns: 20px 30px 40px auto 20px 30px;
+        padding-top: 5px;
+        padding-left: 5px;
+        padding-bottom: 5px;
+
+        .step {
+            grid-row: 1;
+            grid-column: 1;
+        }
+            
+        .color {
+            grid-row: 1;
+            grid-column: 2;
+            cursor: pointer;
+        }
+
+        .step-value {       
+            margin-left: 5px;     
+            grid-row: 1;
+            grid-column: 3;
+            text-align: right;
+            margin-right: 5px;
+        }
+
+        .step-slider {            
+            grid-row: 1;
+            grid-column: 4;
+            display: grid;
+            justify-content: stretch;
+            align-content: center;
+            margin-right: -5px;
+            padding-left: 12px;
+
+            input {
+                width: 90%;
+            }
+        }
+
+        .gradient-copy {            
+            grid-row: 1;
+            grid-column: 5;
+            display: grid;
+            align-content: center;
+            justify-content: center;
+ 
+            .img {
+                height: 20px;
+                width: 20px;
+            }
+            .img:hover {
+                cursor: pointer;
+            }
+
+        }
+        .gradient-delete {            
+            grid-row: 1;
+            grid-column: 6;
+            display: grid;
+            align-content: center;
+            justify-content: center;
+            .img {
+                height: 20px;
+                width: 20px;
+            }
+            .img:hover {
+                cursor: pointer;
+            }
+
+        }
+
+    }
+
+    .floatLine {
+        padding-left: $line-padding-left;
+        height: 30px;
+        display: grid;
+        grid-template-columns: 1fr 120px;
+
+
+        .label {
+            grid-column: 1;
+            display: flex;
+            align-items: center;
+        }
+
+        .value {
+            grid-column: 2;
+            
+            display: flex;
+            align-items: center;
+            
+            input {
+                width: 110px;
+            }
+        }
+
+        .short {
+            grid-column: 2;
+            
+            display: flex;
+            align-items: center;
+            
+            input {
+                width: 27px;
+            }
+            
+            input::-webkit-outer-spin-button,
+            input::-webkit-inner-spin-button {
+              -webkit-appearance: none;
+              margin: 0;
+            }
+
+            input[type=number] {
+                -moz-appearance: textfield;
+            }
+        }
+    }
+
+    .vector3Line {
+        padding-left:$line-padding-left;                    
+        display: grid;
+
+        .firstLine {
+            display: grid;
+            grid-template-columns: 1fr auto 20px;
+            height: 30px;
+
+            .label {
+                grid-column: 1;
+                display: flex;
+                align-items: center;
+            }
+
+            .vector {
+                grid-column: 2;
+                display: flex;
+                align-items: center;
+                text-align: right;
+                opacity: 0.8;
+            }
+
+            .expand {
+                grid-column: 3;
+                display: grid;
+                align-items: center;
+                justify-items: center;
+                cursor: pointer;
+            }
+        }
+
+        .secondLine {
+            display: grid;
+            padding-right: 5px;  
+            border-left: 1px solid rgb(51, 122, 183);
+
+            .no-right-margin {
+                margin-right: 0;
+            }
+
+            .numeric {
+                display: grid;
+                grid-template-columns: 1fr auto;
+            }
+
+            .numeric-label {
+                text-align: right;
+                grid-column: 1;
+                display: flex;
+                align-items: center;                            
+                justify-self: right;
+                margin-right: 10px;                          
+            }
+
+            .numeric-value {
+                width: 120px;
+                grid-column: 2;
+                display: flex;
+                align-items: center;  
+                border: 1px solid  rgb(51, 122, 183);
+            }                        
+        }
+    }
+
+    .buttonLine {
+        height: 30px;
+        display: grid;
+        align-items: center;
+        justify-items: stretch;
+        padding-bottom: 5px;
+
+        input[type="file"] {
+            display: none;
+        }
+
+        .file-upload {            
+            background: #222222;
+            border: 1px solid rgb(51, 122, 183);
+            margin: 5px 10px;
+            color:white;
+            padding: 4px 5px;
+            padding-top: 0px;
+            opacity: 0.9;
+            cursor: pointer;
+            text-align: center;
+        }
+
+        .file-upload:hover {
+            opacity: 1.0;
+        }
+
+        .file-upload:active {
+            transform: scale(0.98);
+            transform-origin: 0.5 0.5;
+        }
+
+        button {
+            background: #222222;
+            border: 1px solid rgb(51, 122, 183);
+            margin: 5px 10px 5px 10px;
+            color:white;
+            padding: 4px 5px;
+            opacity: 0.9;
+        }
+
+        button:hover {
+            opacity: 1.0;
+        }
+
+        button:active {
+            background: #282828;
+        }   
+        
+        button:focus {
+            border: 1px solid rgb(51, 122, 183);
+            outline: 0px;
+        }  
+    }
+
+    .numeric {
+        padding-left: $line-padding-left;
+        height: 30px;
+        display: grid;
+        grid-template-columns: 1fr 120px auto;
+
+        .numeric-label {
+            grid-column: 1;
+            display: flex;
+            align-items: center;
+        }
+
+        .number {                        
+            display: flex;
+            align-items: center;
+            grid-column: 2;
+            height: 10px;
+            .input {
+                width: calc(100% - 5px);
+                height: 10px;
+            }
+        }
+    }
+    
+    .checkBoxLine {
+        padding-left: $line-padding-left;
+        height: 30px;
+        display: grid;
+        grid-template-columns: 1fr auto;
+
+        .label {
+            grid-column: 1;
+            display: flex;
+            align-items: center;
+        }
+
+        .checkBox {
+            grid-column: 2;
+            
+            display: flex;
+            align-items: center;
+
+            .lbl {
+                position: relative;
+                display: block;
+                height: 14px;
+                width: 34px;
+                margin-right: 5px;
+                background: #898989;
+                border-radius: 100px;
+                cursor: pointer;
+                transition: all 0.3s ease;
+            }
+
+            .lbl:after {
+                position: absolute;
+                left: 3px;
+                top: 2px;
+                display: block;
+                width: 10px;
+                height: 10px;
+                border-radius: 100px;
+                background: #fff;
+                box-shadow: 0px 3px 3px rgba(0,0,0,0.05);
+                content: '';
+                transition: all 0.15s ease;
+            }
+
+            .lbl:active:after { 
+                transform: scale(1.15, 0.85); 
+            }
+
+            .cbx:checked ~ label { 
+                background: rgb(51, 122, 183);
+            }
+
+            .cbx:checked ~ label:after {
+                left: 20px;
+                background: rgb(22, 73, 117);
+            }
+
+            .cbx:checked ~ label.disabled { 
+                background: rgb(22, 73, 117);
+                cursor: pointer;
+            }
+
+            .cbx:checked ~ label.disabled:after {
+                left: 20px;
+                background: rgb(85, 85, 85);
+                cursor: pointer;
+            }
+
+            .cbx ~ label.disabled {
+                background: rgb(85, 85, 85);
+                cursor: pointer;
+            }
+
+            .hidden { 
+                display: none; 
+            }               
+        }                    
+    }  
+
+    .listLine {
+        padding-left: $line-padding-left;
+        height: 30px;
+        display: grid;
+        grid-template-columns: 1fr auto;
+
+
+        .label {
+            grid-column: 1;
+            display: flex;
+            align-items: center;
+        }
+
+        .options {
+            grid-column: 2;
+            
+            display: flex;
+            align-items: center;   
+            margin-right: 5px;
+
+            select {
+                width: 115px;
+            }
+        }                    
+    }  
+                    
+    .color3Line {
+        padding-left: $line-padding-left;
+        display: grid;
+
+        .firstLine {
+            height: 30px;
+            display: grid;
+            grid-template-columns: 1fr auto 20px 20px;
+
+            .label {
+                grid-column: 1;
+                display: flex;
+                align-items: center;
+            }
+
+            .color3 {
+                grid-column: 2;                
+                width: 50px;
+                
+                display: flex;
+                align-items: center;            
+                
+                input {
+                    margin-right: 5px;
+                }
+            }
+
+            .copy {
+                grid-column: 3;
+                display: grid;
+                align-items: center;
+                justify-items: center;
+                cursor: pointer;
+                
+                img {
+                    height: 100%;
+                    width: 20px;
+                }
+            }
+
+            .expand {
+                grid-column: 4;
+                display: grid;
+                align-items: center;
+                justify-items: center;
+                cursor: pointer;
+
+                img {
+                    height: 100%;
+                    width: 20px;
+                }
+            }
+        }   
+
+        .secondLine {
+            display: grid;
+            padding-right: 5px;  
+            border-left: 1px solid rgb(51, 122, 183);
+
+            .numeric {
+                display: grid;
+                grid-template-columns: 1fr auto;
+            }
+
+            .numeric-label {
+                text-align: right;
+                grid-column: 1;
+                display: flex;
+                align-items: center;                            
+                justify-self: right;
+                margin-right: 10px;                          
+            }
+
+            .numeric-value {
+                width: 120px;
+                grid-column: 2;
+                display: flex;
+                align-items: center;  
+                border: 1px solid  rgb(51, 122, 183);
+            }                        
+        }                  
+    }     
+    
+    .textLine {
+        padding-left: $line-padding-left;
+        height: 30px;
+        display: grid;
+        grid-template-columns: 1fr auto;
+
+        .label {
+            grid-column: 1;
+            display: flex;
+            align-items: center;
+        }
+
+        .link-value {
+            grid-column: 2;
+            white-space: nowrap;
+            text-overflow: ellipsis;
+            overflow: hidden;
+            text-align: end;
+            opacity: 0.8;
+            margin:5px;
+            margin-top: 6px;
+            max-width: 140px;
+            text-decoration: underline;
+            cursor: pointer;
+        }
+
+        .value {
+            grid-column: 2;
+            white-space: nowrap;
+            text-overflow: ellipsis;
+            overflow: hidden;
+            text-align: end;
+            opacity: 0.8;
+            margin:5px;
+            margin-top: 6px;
+            max-width: 200px;
+            -webkit-user-select: text; 
+            -moz-user-select: text;   
+            -ms-user-select: text;    
+            user-select: text;                
+
+            &.check {
+                color: green;
+            }
+
+            &.uncheck {
+                color: red;
+            }  
+        }
+    }    
+
+}

+ 228 - 0
guiEditor/src/components/propertyTab/propertyTabComponent.tsx

@@ -0,0 +1,228 @@
+
+import * as React from "react";
+import { GlobalState } from '../../globalState';
+import { Nullable } from 'babylonjs/types';
+import { ButtonLineComponent } from '../../sharedUiComponents/lines/buttonLineComponent';
+import { LineContainerComponent } from '../../sharedUiComponents/lines/lineContainerComponent';
+import { FileButtonLineComponent } from '../../sharedUiComponents/lines/fileButtonLineComponent';
+import { Tools } from 'babylonjs/Misc/tools';
+import { CheckBoxLineComponent } from '../../sharedUiComponents/lines/checkBoxLineComponent';
+import { DataStorage } from 'babylonjs/Misc/dataStorage';
+import { GUINode } from '../../diagram/guiNode';
+import { Observer } from 'babylonjs/Misc/observable';
+import { TextLineComponent } from "../../sharedUiComponents/lines/textLineComponent";
+import { SerializationTools } from "../../serializationTools";
+import { Engine } from "babylonjs/Engines/engine";
+
+require("./propertyTab.scss");
+
+interface IPropertyTabComponentProps {
+    globalState: GlobalState;
+}
+
+interface IPropertyTabComponentState {
+    currentNode: Nullable<GUINode>;
+ }
+
+export class PropertyTabComponent extends React.Component<IPropertyTabComponentProps, IPropertyTabComponentState> {
+    private _onBuiltObserver: Nullable<Observer<void>>;
+
+    constructor(props: IPropertyTabComponentProps) {
+        super(props);
+
+        this.state = { currentNode: null};
+    }
+
+    componentDidMount() {
+        this.props.globalState.onSelectionChangedObservable.add((selection) => {
+            if (selection instanceof GUINode) {
+                this.setState({ currentNode: selection});
+            } else {
+                this.setState({ currentNode: null });
+            }
+        });
+
+        this._onBuiltObserver = this.props.globalState.onBuiltObservable.add(() => {
+            this.forceUpdate();
+        });
+    }
+
+    componentWillUnmount() {
+        this.props.globalState.onBuiltObservable.remove(this._onBuiltObserver);
+    }
+
+
+    load(file: File) {
+        Tools.ReadFile(file, (data) => {
+            let decoder = new TextDecoder("utf-8");
+            SerializationTools.Deserialize(JSON.parse(decoder.decode(data)), this.props.globalState);
+
+            this.props.globalState.onSelectionChangedObservable.notifyObservers(null);
+        }, undefined, true);
+    }
+
+    loadFrame(file: File) {
+        Tools.ReadFile(file, (data) => {
+            // get Frame Data from file
+            //let decoder = new TextDecoder("utf-8");
+           // const frameData = JSON.parse(decoder.decode(data));
+           // SerializationTools.AddFrameToMaterial(frameData, this.props.globalState, this.props.globalState.nodeMaterial);
+        }, undefined, true);
+    }
+
+    save() {
+        //let json = SerializationTools.Serialize(this.props.globalState.nodeMaterial, this.props.globalState);
+        //StringTools.DownloadAsFile(this.props.globalState.hostDocument, json, "nodeMaterial.json");
+    }
+
+    customSave() {
+        /*this.props.globalState.onLogRequiredObservable.notifyObservers({message: "Saving your material to Babylon.js snippet server...", isError: false});
+        this.props.globalState.customSave!.action(SerializationTools.Serialize(this.props.globalState.nodeMaterial, this.props.globalState)).then(() => {
+            this.props.globalState.onLogRequiredObservable.notifyObservers({message: "Material saved successfully", isError: false});
+        }).catch((err) => {
+            this.props.globalState.onLogRequiredObservable.notifyObservers({message: err, isError: true});
+        });*/
+    }
+
+    saveToSnippetServer() {
+        /*const material = this.props.globalState.nodeMaterial;
+        const xmlHttp = new XMLHttpRequest();
+
+        let json = SerializationTools.Serialize(material, this.props.globalState);
+
+        xmlHttp.onreadystatechange = () => {
+            if (xmlHttp.readyState == 4) {
+                if (xmlHttp.status == 200) {
+                    var snippet = JSON.parse(xmlHttp.responseText);
+                    const oldId = material.snippetId;
+                    material.snippetId = snippet.id;
+                    if (snippet.version && snippet.version != "0") {
+                        material.snippetId += "#" + snippet.version;
+                    }
+
+                    this.forceUpdate();
+                    if (navigator.clipboard) {
+                        navigator.clipboard.writeText(material.snippetId);
+                    }
+
+                    let windowAsAny = window as any;
+
+                    if (windowAsAny.Playground && oldId) {
+                        windowAsAny.Playground.onRequestCodeChangeObservable.notifyObservers({
+                            regex: new RegExp(oldId, "g"),
+                            replace: material.snippetId
+                        });
+                    }
+
+                    this.props.globalState.hostDocument.defaultView!.alert("NodeMaterial saved with ID: " + material.snippetId + " (please note that the id was also saved to your clipboard)");
+
+                }
+                else {
+                    this.props.globalState.hostDocument.defaultView!.alert(`Unable to save your node material. It may be too large (${(dataToSend.payload.length / 1024).toFixed(2)} KB) because of embedded textures. Please reduce texture sizes or point to a specific url instead of embedding them and try again.`);
+                }
+            }
+        };
+
+        xmlHttp.open("POST", NodeMaterial.SnippetUrl + (material.snippetId ? "/" + material.snippetId : ""), true);
+        xmlHttp.setRequestHeader("Content-Type", "application/json");
+
+        var dataToSend = {
+            payload : JSON.stringify({
+                nodeMaterial: json
+            }),
+            name: "",
+            description: "",
+            tags: ""
+        };
+
+        xmlHttp.send(JSON.stringify(dataToSend));*/
+    }
+
+    loadFromSnippet() {
+        /*const material = this.props.globalState.nodeMaterial;
+        const scene = material.getScene();
+
+        let snippedID = window.prompt("Please enter the snippet ID to use");
+
+        if (!snippedID) {
+            return;
+        }
+
+        this.props.globalState.onSelectionChangedObservable.notifyObservers(null);
+
+        NodeMaterial.ParseFromSnippetAsync(snippedID, scene, "", material).then(() => {
+            material.build();
+            if (!this.changeMode(this.props.globalState.nodeMaterial!.mode, true, false)) {
+                this.props.globalState.onResetRequiredObservable.notifyObservers();
+            }
+        }).catch((err) => {
+            this.props.globalState.hostDocument.defaultView!.alert("Unable to load your node material: " + err);
+        });*/
+    }
+
+
+    render() {
+        if (this.state.currentNode) {
+            return (
+                <div id="propertyTab">
+                    <div id="header">
+                        <img id="logo" src="https://www.babylonjs.com/Assets/logo-babylonjs-social-twitter.png" />
+                        <div id="title">
+                            GUI EDITOR
+                        </div>
+                    </div>
+                    {this.state.currentNode?.renderProperties()}
+                </div>
+            );
+        }
+
+        return (
+            <div id="propertyTab">
+                <div id="header">
+                    <img id="logo" src="https://www.babylonjs.com/Assets/logo-babylonjs-social-twitter.png" />
+                    <div id="title">
+                        GUI EDITOR
+                    </div>
+                </div>
+                <div>
+                    <LineContainerComponent title="GENERAL">
+                        <TextLineComponent label="Version" value={Engine.Version}/>
+                        <TextLineComponent label="Help" value="doc.babylonjs.com" underline={true} onLink={() => window.open('https://doc.babylonjs.com', '_blank')}/>
+                        <ButtonLineComponent label="Reset to default" onClick={() => {
+                            this.props.globalState.onResetRequiredObservable.notifyObservers();
+                        }} />
+                    </LineContainerComponent>
+                    <LineContainerComponent title="OPTIONS">
+                        <CheckBoxLineComponent label="Show grid"
+                            isSelected={() => DataStorage.ReadBoolean("ShowGrid", true)}
+                            onSelect={(value: boolean) => {
+                                DataStorage.WriteBoolean("ShowGrid", value);
+                            }}
+                        />
+                    </LineContainerComponent>
+                    <LineContainerComponent title="FILE">
+                        <FileButtonLineComponent label="Load" onClick={(file) => this.load(file)} accept=".json" />
+                        <ButtonLineComponent label="Save" onClick={() => {
+                            this.save();
+                        }} />
+                        {
+                            this.props.globalState.customSave &&
+                            <ButtonLineComponent label={this.props.globalState.customSave!.label} onClick={() => {
+                                this.customSave();
+                            }} />
+                        }
+                    </LineContainerComponent>
+                    {
+                        !this.props.globalState.customSave &&
+                        <LineContainerComponent title="SNIPPET">
+                            <ButtonLineComponent label="Load from snippet server" onClick={() => this.loadFromSnippet()} />
+                            <ButtonLineComponent label="Save to snippet server" onClick={() => {
+                                this.saveToSnippetServer();
+                            }} />
+                        </LineContainerComponent>
+                    }
+                </div>
+            </div>
+        );
+    }
+}

+ 198 - 0
guiEditor/src/diagram/guiNode.ts

@@ -0,0 +1,198 @@
+import { GlobalState } from '../globalState';
+import { Nullable } from 'babylonjs/types';
+import { Observer } from 'babylonjs/Misc/observable';
+import { WorkbenchComponent, FramePortData } from './workbench';
+import { PropertyGuiLedger } from './propertyLedger';
+import * as React from 'react';
+import { GenericPropertyComponent } from './properties/genericNodePropertyComponent';
+import { Control } from 'babylonjs-gui/2D/controls/control';
+import { Vector2 } from 'babylonjs/Maths/math.vector';
+
+export class GUINode {
+    private _x = 0;
+    private _y = 0;
+    private _gridAlignedX = 0;
+    private _gridAlignedY = 0;    
+    private _globalState: GlobalState;
+    private _onSelectionChangedObserver: Nullable<Observer<Nullable<GUINode | FramePortData>>>;  
+    private _onSelectionBoxMovedObserver: Nullable<Observer<ClientRect | DOMRect>>;   
+    private _onUpdateRequiredObserver: Nullable<Observer<void>>;  
+    private _ownerCanvas: WorkbenchComponent; 
+    private _isSelected: boolean;
+    private _isVisible = true;
+    private _enclosingFrameId = -1;
+
+    public get isVisible() {
+        return this._isVisible;
+    }
+
+    public set isVisible(value: boolean) {
+        this._isVisible = value;
+    }
+
+    public get gridAlignedX() {
+        return this._gridAlignedX;
+    }
+
+    public get gridAlignedY() {
+        return this._gridAlignedY;
+    }
+
+    public get x() {
+        return this._x;
+    }
+
+    public set x(value: number) {
+        if (this._x === value) {
+            return;
+        }
+        this._x = value;
+        
+        this._gridAlignedX = this._ownerCanvas.getGridPosition(value);
+    }
+
+    public get y() {
+        return this._y;
+    }
+
+    public set y(value: number) {
+        if (this._y === value) {
+            return;
+        }
+
+        this._y = value;
+
+        this._gridAlignedY = this._ownerCanvas.getGridPosition(value);
+    }
+
+    public get width() {
+        return this.guiControl.widthInPixels;
+    }
+
+    public get height() {
+        return this.guiControl.heightInPixels;
+    }
+
+    public get id() {
+        return this.guiControl.uniqueId;
+    }
+
+    public get name() {
+        return this.guiControl.name;
+    }
+
+    public get isSelected() {
+        return this._isSelected;
+    }
+
+    public get enclosingFrameId() {
+        return this._enclosingFrameId;
+    }
+
+    public set enclosingFrameId(value: number) {
+        this._enclosingFrameId = value;
+    }
+
+    public set isSelected(value: boolean) {
+        this._isSelected = value;
+
+        if (value) {
+            this._globalState.onSelectionChangedObservable.notifyObservers(this);  
+        }
+    }
+
+    public constructor(globalState: GlobalState, public guiControl: Control) {
+        this._globalState = globalState;
+        this._ownerCanvas = this._globalState.workbench;
+        
+        guiControl.onPointerUpObservable.add(evt => {
+            this.clicked = false;
+            console.log("up");
+        });
+
+        guiControl.onPointerDownObservable.add( evt => {
+            this.clicked = true;
+            this.isSelected = true;
+            console.log("down");
+        }
+        );
+
+        guiControl.onPointerEnterObservable.add( evt => {
+            this._ownerCanvas.isOverGUINode = true;
+            console.log("in");
+        }
+        );
+
+        guiControl.onPointerOutObservable.add( evt => {
+            this._ownerCanvas.isOverGUINode = false;
+            console.log("out");
+        }
+        );
+
+        //TODO: Implement
+        this._onSelectionBoxMovedObserver = this._globalState.onSelectionBoxMoved.add(rect1 => {
+        });
+
+    }
+
+    public cleanAccumulation(useCeil = false) {
+        this.x = this._ownerCanvas.getGridPosition(this.x, useCeil);
+        this.y = this._ownerCanvas.getGridPosition(this.y, useCeil);
+    }
+
+    public clicked: boolean;
+    public _onMove(evt: Vector2, startPos: Vector2) {
+       
+        if(!this.clicked) return false;
+        console.log("moving");
+
+        //TODO: Implement move with zoom factor.
+        let newX = (evt.x - startPos.x) ;// / this._ownerCanvas.zoom;
+        let newY = (evt.y - startPos.y) ;// / this._ownerCanvas.zoom;
+
+        this.x += newX;
+        this.y += newY;  
+
+        return true;
+        //evt.stopPropagation();
+    }
+
+    public renderProperties(): Nullable<JSX.Element> {
+        let className = this.guiControl.getClassName();
+        let control = PropertyGuiLedger.RegisteredControls[className];
+        
+        if (!control) {
+            control = GenericPropertyComponent;
+        }
+
+        return React.createElement(control, {
+        globalState: this._globalState,
+        guiControl: this.guiControl
+        });
+    }
+
+    public updateVisual()
+    {
+        this.guiControl.leftInPixels = this.x;
+        this.guiControl.topInPixels = this.y;
+    }
+
+    public dispose() {
+        // notify frame observers that this node is being deleted
+        this._globalState.onGuiNodeRemovalObservable.notifyObservers(this);
+
+        if (this._onSelectionChangedObserver) {
+            this._globalState.onSelectionChangedObservable.remove(this._onSelectionChangedObserver);
+        }
+
+        if (this._onUpdateRequiredObserver) {
+            this._globalState.onUpdateRequiredObservable.remove(this._onUpdateRequiredObserver);
+        }
+
+        if (this._onSelectionBoxMovedObserver) {
+            this._globalState.onSelectionBoxMoved.remove(this._onSelectionBoxMovedObserver);
+        }
+
+        this.guiControl.dispose();   
+    }
+}

+ 113 - 0
guiEditor/src/diagram/properties/genericNodePropertyComponent.tsx

@@ -0,0 +1,113 @@
+
+import * as React from "react";
+import { LineContainerComponent } from '../../sharedUiComponents/lines/lineContainerComponent';
+import { IPropertyComponentProps } from './propertyComponentProps';
+import { CheckBoxLineComponent } from '../../sharedUiComponents/lines/checkBoxLineComponent';
+import { FloatLineComponent } from '../../sharedComponents/floatLineComponent';
+import { SliderLineComponent } from '../../sharedComponents/sliderLineComponent';
+import { PropertyTypeForEdition, IPropertyDescriptionForEdition } from 'babylonjs/Materials/Node/nodeMaterialDecorator';
+
+export class GenericPropertyComponent extends React.Component<IPropertyComponentProps> {
+    constructor(props: IPropertyComponentProps) {
+        super(props);
+    }
+
+    render() {
+        return (
+            <>
+                <GeneralPropertyTabComponent globalState={this.props.globalState} guiControl={this.props.guiControl}/>
+                <GenericPropertyTabComponent globalState={this.props.globalState} guiControl={this.props.guiControl}/>
+            </>
+        );
+    }
+}
+
+export class GeneralPropertyTabComponent extends React.Component<IPropertyComponentProps> {
+    constructor(props: IPropertyComponentProps) {
+        super(props);
+    }
+
+    render() {
+        return (
+            <>
+                <LineContainerComponent title="GENERAL">
+                </LineContainerComponent>
+            </>
+        );
+    }
+}
+
+export class GenericPropertyTabComponent extends React.Component<IPropertyComponentProps> {
+    constructor(props: IPropertyComponentProps) {
+        super(props);
+    }
+
+    forceRebuild(notifiers?: { "rebuild"?: boolean; "update"?: boolean; }) {
+        if (!notifiers || notifiers.update) {
+            this.props.globalState.onUpdateRequiredObservable.notifyObservers();
+        }
+
+        if (!notifiers || notifiers.rebuild) {
+            this.props.globalState.onRebuildRequiredObservable.notifyObservers();
+        }
+    }
+
+    render() {
+        const block = this.props.guiControl,
+              propStore: IPropertyDescriptionForEdition[] = (block as any)._propStore;
+
+        if (!propStore) {
+            return (
+                <>
+                </>
+            );
+        }
+
+        const componentList: { [groupName: string]: JSX.Element[]} = {},
+              groups: string[] = [];
+
+        for (const { propertyName, displayName, type, groupName, options } of propStore) {
+            let components = componentList[groupName];
+
+            if (!components) {
+                components = [];
+                componentList[groupName] = components;
+                groups.push(groupName);
+            }
+
+            switch (type) {
+                case PropertyTypeForEdition.Boolean: {
+                    components.push(
+                        <CheckBoxLineComponent label={displayName} target={this.props.guiControl} propertyName={propertyName} onValueChanged={() => this.forceRebuild(options.notifiers)} />
+                    );
+                    break;
+                }
+                case PropertyTypeForEdition.Float: {
+                    let cantDisplaySlider = (isNaN(options.min as number) || isNaN(options.max as number) || options.min === options.max);
+                    if (cantDisplaySlider) {
+                        components.push(
+                            <FloatLineComponent globalState={this.props.globalState} label={displayName} propertyName={propertyName} target={this.props.guiControl} onChange={() => this.forceRebuild(options.notifiers)} />
+                        );
+                    } else {
+                        components.push(
+                            <SliderLineComponent label={displayName} target={this.props.guiControl} globalState={this.props.globalState} propertyName={propertyName} step={Math.abs((options.max as number) - (options.min as number)) / 100.0} minimum={Math.min(options.min as number, options.max as number)} maximum={options.max as number} onChange={() => this.forceRebuild(options.notifiers)}/>
+                        );
+                    }
+                    break;
+                }
+            }
+        }
+
+        return (
+            <>
+            {
+                groups.map((group) =>
+                    <LineContainerComponent title={group}>
+                        {componentList[group]}
+                    </LineContainerComponent>
+                )
+            }
+            </>
+        );
+    }
+}

+ 7 - 0
guiEditor/src/diagram/properties/propertyComponentProps.ts

@@ -0,0 +1,7 @@
+import { Control } from "babylonjs-gui/2D/controls/control";
+import { GlobalState } from "../../globalState";
+
+export interface IPropertyComponentProps {
+    globalState: GlobalState;
+    guiControl: Control;
+}

+ 55 - 0
guiEditor/src/diagram/properties/sliderGuiPropertyComponent.tsx

@@ -0,0 +1,55 @@
+
+import * as React from "react";
+import { LineContainerComponent } from '../../sharedUiComponents/lines/lineContainerComponent';
+import { IPropertyComponentProps } from './propertyComponentProps';
+import { GeneralPropertyTabComponent } from './genericNodePropertyComponent';
+import { NumericInputComponent } from '../../sharedComponents/numericInputComponent';
+import { Slider } from "babylonjs-gui/2D/controls/sliders/slider";
+import { TextLineComponent } from "../../sharedUiComponents/lines/textLineComponent";
+
+
+export class SliderPropertyTabComponent extends React.Component<IPropertyComponentProps> {
+    constructor(props: IPropertyComponentProps) {
+        super(props);
+        this._slider = this.props.guiControl as Slider;
+    }
+
+    private _slider : Slider;
+
+    render() {
+        return (
+            <>                
+                <GeneralPropertyTabComponent globalState={this.props.globalState} guiControl={this.props.guiControl}/>
+                <LineContainerComponent title="PROPERTIES"> 
+                <NumericInputComponent globalState={this.props.globalState} label="Minimum Value" value={this._slider.minimum}
+                onChange={evt =>{
+                    this._slider.minimum = evt;
+                }}>
+                </NumericInputComponent>
+                <NumericInputComponent globalState={this.props.globalState} label="Maximum Value" value={this._slider.maximum} 
+                onChange={evt =>{
+                   this._slider.maximum = evt;
+                }}>
+
+                </NumericInputComponent>
+                <NumericInputComponent globalState={this.props.globalState} label="Value" value={this._slider.value} 
+                onChange={evt =>{
+                   this._slider.value = evt;
+                }}>
+                </NumericInputComponent>
+                <NumericInputComponent globalState={this.props.globalState} label="Height" value={this._slider.heightInPixels} 
+                onChange={evt =>{
+                   this._slider.height = evt;
+                }}>
+                </NumericInputComponent>
+                <NumericInputComponent globalState={this.props.globalState} label="Width" value={this._slider.widthInPixels} 
+                onChange={evt =>{
+                   this._slider.width = evt;
+                }}>
+                </NumericInputComponent>
+                <TextLineComponent label="Color" value={this._slider.background} />
+                </LineContainerComponent>            
+            </>
+        );
+    }
+}

+ 10 - 0
guiEditor/src/diagram/propertyLedger.ts

@@ -0,0 +1,10 @@
+import { ComponentClass } from 'react';
+
+import { IPropertyComponentProps } from './properties/propertyComponentProps';
+import { SliderPropertyTabComponent } from './properties/sliderGuiPropertyComponent';
+
+export class PropertyGuiLedger {
+    public static RegisteredControls: {[key: string] : ComponentClass<IPropertyComponentProps>} = {};
+}
+
+PropertyGuiLedger.RegisteredControls["Slider"] = SliderPropertyTabComponent;

+ 541 - 0
guiEditor/src/diagram/workbench.tsx

@@ -0,0 +1,541 @@
+import * as React from "react";
+import { GlobalState } from '../globalState';
+import { GUINode } from './guiNode';
+import * as dagre from 'dagre';
+import { Nullable } from 'babylonjs/types';
+
+import { DataStorage } from 'babylonjs/Misc/dataStorage';
+
+import {Control} from 'babylonjs-gui/2D/controls/control';
+import { AdvancedDynamicTexture } from "babylonjs-gui/2D/advancedDynamicTexture";
+import { Vector2, Vector3 } from "babylonjs/Maths/math.vector";
+import { Engine } from "babylonjs/Engines/engine";
+import { Scene } from "babylonjs/scene";
+import { Color4 } from "babylonjs/Maths/math.color";
+import { FreeCamera } from "babylonjs/Cameras/freeCamera";
+
+require("./workbenchCanvas.scss");
+
+export interface IWorkbenchComponentProps {
+    globalState: GlobalState
+}
+
+export type FramePortData = {
+}
+
+export const isFramePortData = (variableToCheck: any): variableToCheck is FramePortData => {
+    if (variableToCheck) {
+        return (variableToCheck as FramePortData) !== undefined;
+    }
+    else return false;
+}
+
+export class WorkbenchComponent extends React.Component<IWorkbenchComponentProps> {
+    private readonly MinZoom = 0.1;
+    private readonly MaxZoom = 4;
+
+    private _hostCanvas: HTMLDivElement;
+    private _gridCanvas: HTMLDivElement;
+    private _selectionContainer: HTMLDivElement;
+    private _frameContainer: HTMLDivElement;
+    private _svgCanvas: HTMLElement;
+    private _rootContainer: HTMLDivElement;
+    private _guiNodes: GUINode[] = [];
+    private _mouseStartPointX: Nullable<number> = null;
+    private _mouseStartPointY: Nullable<number> = null
+    private _selectionStartX = 0;
+    private _selectionStartY = 0;
+    private _x = 0;
+    private _y = 0;
+    private _zoom = 1;
+    private _selectedGuiNodes: GUINode[] = [];
+    private _gridSize = 20;
+    private _selectionBox: Nullable<HTMLDivElement> = null;    
+    private _frameCandidate: Nullable<HTMLDivElement> = null;
+
+    private _altKeyIsPressed = false;
+    private _ctrlKeyIsPressed = false;
+    private _oldY = -1;
+
+    public _frameIsMoving = false;
+    public _isLoading = false;
+    public isOverGUINode = false;
+
+    public get gridSize() {
+        return this._gridSize;
+    }
+
+    public set gridSize(value: number) {
+        this._gridSize = value;
+        
+        this.updateTransform();
+    }
+
+    public get globalState(){
+        return this.props.globalState;
+    }
+
+    public get nodes() {
+        return this._guiNodes;
+    }
+
+    public get zoom() {
+        return this._zoom;
+    }
+
+    public set zoom(value: number) {
+        if (this._zoom === value) {
+            return;
+        }
+
+        this._zoom = value;
+        
+        this.updateTransform();
+    }    
+
+    public get x() {
+        return this._x;
+    }
+
+    public set x(value: number) {
+        this._x = value;
+        
+        this.updateTransform();
+    }
+
+    public get y() {
+        return this._y;
+    }
+
+    public set y(value: number) {
+        this._y = value;
+        
+        this.updateTransform();
+    }
+
+    public get selectedGuiNodes() {
+        return this._selectedGuiNodes;
+    }
+
+    public get canvasContainer() {
+        return this._gridCanvas;
+    }
+
+    public get hostCanvas() {
+        return this._hostCanvas;
+    }
+
+    public get svgCanvas() {
+        return this._svgCanvas;
+    }
+
+    public get selectionContainer() {
+        return this._selectionContainer;
+    }
+
+    public get frameContainer() {
+        return this._frameContainer;
+    }
+
+    constructor(props: IWorkbenchComponentProps) {
+        super(props);
+
+        props.globalState.onSelectionChangedObservable.add(selection => {  
+            console.log(selection);
+            
+            this.selectedGuiNodes.forEach(element => {
+                element.isSelected = false;
+            }); 
+            if (!selection) {
+                this._selectedGuiNodes = [];
+            } 
+            else {
+                if (selection instanceof GUINode){
+                    if (this._ctrlKeyIsPressed) {
+                        if (this._selectedGuiNodes.indexOf(selection) === -1) {
+                            this._selectedGuiNodes.push(selection);
+                        }
+                    } 
+                    else {              
+                        this._selectedGuiNodes = [selection];
+                    }
+                
+                } 
+            }
+        });
+
+
+        this.props.globalState.hostDocument!.addEventListener("keyup", () => this.onKeyUp(), false);
+        this.props.globalState.hostDocument!.addEventListener("keydown", evt => {
+            this._altKeyIsPressed = evt.altKey;            
+            this._ctrlKeyIsPressed = evt.ctrlKey;
+        }, false);
+        this.props.globalState.hostDocument!.defaultView!.addEventListener("blur", () => {
+            this._altKeyIsPressed = false;
+            this._ctrlKeyIsPressed = false;
+        }, false);     
+
+        // Store additional data to serialization object
+        this.props.globalState.storeEditorData = (editorData) => {
+            editorData.x = this.x;
+            editorData.y = this.y;
+            editorData.zoom = this.zoom;
+        }
+        this.props.globalState.workbench = this;
+    }
+
+    public getGridPosition(position: number, useCeil = false) {
+        let gridSize = this.gridSize;
+		if (gridSize === 0) {
+			return position;
+        }
+        if (useCeil) {
+            return gridSize * Math.ceil(position / gridSize);    
+        }
+		return gridSize * Math.floor(position / gridSize);
+    }
+    
+    public getGridPositionCeil(position: number) {
+        let gridSize = this.gridSize;
+		if (gridSize === 0) {
+			return position;
+		}
+		return gridSize * Math.ceil(position / gridSize);
+	}
+
+    updateTransform() {
+        this._rootContainer.style.transform = `translate(${this._x}px, ${this._y}px) scale(${this._zoom})`;
+
+        if (DataStorage.ReadBoolean("ShowGrid", true)) {
+            this._hostCanvas.style.backgroundSize = `${this._gridSize * this._zoom}px ${this._gridSize * this._zoom}px`;
+            this._hostCanvas.style.backgroundPosition = `${this._x}px ${this._y}px`;
+        } else {
+            this._hostCanvas.style.backgroundSize = `0`;
+        }
+    }
+
+    onKeyUp() {        
+        this._altKeyIsPressed = false;
+        this._ctrlKeyIsPressed = false;
+        this._oldY = -1;
+    }
+
+    findNodeFromGuiElement(guiControl: Control) {
+       return this._guiNodes.filter(n => n.guiControl === guiControl)[0];
+    }
+
+    reset() {
+        for (var node of this._guiNodes) {
+            node.dispose();
+        }
+        this._guiNodes = [];
+        this._gridCanvas.innerHTML = "";
+        this._svgCanvas.innerHTML = "";
+    }
+
+    appendBlock(guiElement: Control) {
+        var newGuiNode = new GUINode(this.props.globalState, guiElement);
+        this._guiNodes.push(newGuiNode);
+        this.globalState.guiTexture.addControl(guiElement);  
+        return newGuiNode;
+    }
+
+    distributeGraph() {
+        this.x = 0;
+        this.y = 0;
+        this.zoom = 1;
+
+        let graph = new dagre.graphlib.Graph();
+        graph.setGraph({});
+        graph.setDefaultEdgeLabel(() => ({}));
+        graph.graph().rankdir = "LR";
+
+        // Build dagre graph
+        this._guiNodes.forEach(node => {
+
+
+            graph.setNode(node.id.toString(), {
+                id: node.id,
+                type: "node",
+                width: node.width,
+                height: node.height
+            });
+        });
+
+        // Distribute
+        dagre.layout(graph);
+
+        // Update graph
+        let dagreNodes = graph.nodes().map(node => graph.node(node));
+        dagreNodes.forEach((dagreNode: any) => {
+            if (!dagreNode) {
+                return;
+            }
+            if (dagreNode.type === "node") {
+                for (var node of this._guiNodes) {
+                    if (node.id === dagreNode.id) {
+                        node.x = dagreNode.x - dagreNode.width / 2;
+                        node.y = dagreNode.y - dagreNode.height / 2;
+                        node.cleanAccumulation();
+                        return;
+                    }
+                }
+                return;
+            }
+        });        
+    }
+
+    componentDidMount() {
+        this._hostCanvas = this.props.globalState.hostDocument.getElementById("workbench-canvas") as HTMLDivElement;
+        this._rootContainer = this.props.globalState.hostDocument.getElementById("workbench-container") as HTMLDivElement;
+        this._gridCanvas = this.props.globalState.hostDocument.getElementById("workbench-canvas-container") as HTMLDivElement;
+        this._svgCanvas = this.props.globalState.hostDocument.getElementById("workbench-svg-container") as HTMLElement;        
+        this._selectionContainer = this.props.globalState.hostDocument.getElementById("selection-container") as HTMLDivElement;   
+        this._frameContainer = this.props.globalState.hostDocument.getElementById("frame-container") as HTMLDivElement;        
+        
+        this.gridSize = DataStorage.ReadNumber("GridSize", 20);
+        this.updateTransform();
+    }    
+
+    onMove(evt: React.PointerEvent) {        
+        // Selection box
+        if (this._selectionBox) {
+            const rootRect = this.canvasContainer.getBoundingClientRect();      
+
+            const localX = evt.pageX - rootRect.left;
+            const localY = evt.pageY - rootRect.top;
+
+            if (localX > this._selectionStartX) {
+                this._selectionBox.style.left = `${this._selectionStartX / this.zoom}px`;
+                this._selectionBox.style.width = `${(localX - this._selectionStartX) / this.zoom}px`;
+            } else {
+                this._selectionBox.style.left = `${localX / this.zoom}px`;
+                this._selectionBox.style.width = `${(this._selectionStartX - localX) / this.zoom}px`;
+            }
+
+            if (localY > this._selectionStartY) {                
+                this._selectionBox.style.top = `${this._selectionStartY / this.zoom}px`;
+                this._selectionBox.style.height = `${(localY - this._selectionStartY) / this.zoom}px`;
+            } else {
+                this._selectionBox.style.top = `${localY / this.zoom}px`;
+                this._selectionBox.style.height = `${(this._selectionStartY - localY) / this.zoom}px`;
+            }
+            
+            this.props.globalState.onSelectionBoxMoved.notifyObservers(this._selectionBox.getBoundingClientRect());
+
+            return;
+        }
+
+        
+        // Zoom with mouse + alt
+        if (this._altKeyIsPressed && evt.buttons === 1) {
+            if (this._oldY < 0) {
+                this._oldY = evt.pageY;
+            }
+
+            let zoomDelta = (evt.pageY - this._oldY) / 10;
+            if (Math.abs(zoomDelta) > 5) {
+                const oldZoom = this.zoom;
+                this.zoom = Math.max(Math.min(this.MaxZoom, this.zoom + zoomDelta / 100), this.MinZoom);
+
+                const boundingRect = evt.currentTarget.getBoundingClientRect();
+                const clientWidth = boundingRect.width;
+                const widthDiff = clientWidth * this.zoom - clientWidth * oldZoom;
+                const clientX = evt.clientX - boundingRect.left;
+        
+                const xFactor = (clientX - this.x) / oldZoom / clientWidth;
+        
+                this.x = this.x - widthDiff * xFactor;
+
+                this._oldY = evt.pageY;      
+            }
+            return;
+        }   
+
+        // Move canvas and/or guiNodes
+        if (this._mouseStartPointX != null && this._mouseStartPointY != null) {
+
+            var x = this._mouseStartPointX;
+            var y = this._mouseStartPointY;
+            let selected = false;
+            this.selectedGuiNodes.forEach(element => {
+                selected = element._onMove(new Vector2(evt.clientX, evt.clientY), 
+                new Vector2( x, y)) || selected;
+            });
+
+            if(!selected) {
+                this._rootContainer.style.cursor = "move";
+                this.x += evt.clientX - this._mouseStartPointX;
+                this.y += evt.clientY - this._mouseStartPointY;
+            }
+            this._mouseStartPointX = evt.clientX;
+            this._mouseStartPointY = evt.clientY;
+        }
+    }
+
+    onDown(evt: React.PointerEvent<HTMLElement>) {
+        this._rootContainer.setPointerCapture(evt.pointerId);
+
+        //TODO: Inplement group selection
+        // Selection?
+        /*if (evt.currentTarget === this._hostCanvas && evt.ctrlKey) {
+            this._selectionBox = this.props.globalState.hostDocument.createElement("div");
+            this._selectionBox.classList.add("selection-box");
+            this._selectionContainer.appendChild(this._selectionBox);
+
+            const rootRect = this.canvasContainer.getBoundingClientRect();      
+            this._selectionStartX = (evt.pageX - rootRect.left);
+            this._selectionStartY = (evt.pageY - rootRect.top);
+            this._selectionBox.style.left = `${this._selectionStartX / this.zoom}px`;
+            this._selectionBox.style.top = `${this._selectionStartY / this.zoom}px`;
+            this._selectionBox.style.width = "0px";
+            this._selectionBox.style.height = "0px";
+            return;
+        }*/
+        console.log('workbench click');
+        if(!this.isOverGUINode) {
+            console.log('unclicked');
+            this.props.globalState.onSelectionChangedObservable.notifyObservers(null);
+        }
+        
+        this._mouseStartPointX = evt.clientX;
+        this._mouseStartPointY = evt.clientY;        
+    }
+
+    onUp(evt: React.PointerEvent) {
+        this._mouseStartPointX = null;
+        this._mouseStartPointY = null;
+        this._rootContainer.releasePointerCapture(evt.pointerId);   
+        this._oldY = -1; 
+
+        if (this._selectionBox) {
+           this._selectionBox.parentElement!.removeChild(this._selectionBox);
+           this._selectionBox = null;
+        }
+
+        if (this._frameCandidate) {            
+
+
+            this._frameCandidate.parentElement!.removeChild(this._frameCandidate);
+            this._frameCandidate = null;
+
+        }
+    }
+
+    onWheel(evt: React.WheelEvent) {
+        let delta = evt.deltaY < 0 ? 0.1 : -0.1;
+
+        let oldZoom = this.zoom;
+        this.zoom = Math.min(Math.max(this.MinZoom, this.zoom + delta * this.zoom), this.MaxZoom);
+
+        const boundingRect = evt.currentTarget.getBoundingClientRect();
+        const clientWidth = boundingRect.width;
+        const clientHeight = boundingRect.height;
+        const widthDiff = clientWidth * this.zoom - clientWidth * oldZoom;
+        const heightDiff = clientHeight * this.zoom - clientHeight * oldZoom;
+        const clientX = evt.clientX - boundingRect.left;
+        const clientY = evt.clientY - boundingRect.top;
+
+        const xFactor = (clientX - this.x) / oldZoom / clientWidth;
+        const yFactor = (clientY - this.y) / oldZoom / clientHeight;
+
+        this.x = this.x - widthDiff * xFactor;
+        this.y = this.y - heightDiff * yFactor;
+
+        evt.stopPropagation();
+    }
+
+    zoomToFit() {
+        // Get negative offset
+        let minX = 0;
+        let minY = 0;
+        this._guiNodes.forEach(node => {
+
+            if (node.x < minX) {
+                minX = node.x;
+            }
+            if (node.y < minY) {
+                minY = node.y;
+            }
+        });
+        // Restore to 0
+
+        this._guiNodes.forEach(node => {
+            node.x += -minX;
+            node.y += -minY;            
+            node.cleanAccumulation();
+        });
+
+        // Get correct zoom
+        const xFactor = this._rootContainer.clientWidth / this._rootContainer.scrollWidth;
+        const yFactor = this._rootContainer.clientHeight / this._rootContainer.scrollHeight;
+        const zoomFactor = xFactor < yFactor ? xFactor : yFactor;
+        
+
+        this.zoom = zoomFactor;
+        this.x = 0;
+        this.y = 0;
+    }
+
+
+    public createGUICanvas()
+    {
+        // Get the canvas element from the DOM.
+        const canvas = document.getElementById("workbench-canvas") as HTMLCanvasElement;
+
+        // Associate a Babylon Engine to it.
+        const engine = new Engine(canvas);
+        
+        // Create our first scene.
+        var scene = new Scene(engine);
+        scene.clearColor = new Color4(0.2, 0.2, 0.3, 0.1);
+
+        // This creates and positions a free camera (non-mesh)
+        var camera = new FreeCamera("camera1", new Vector3(0, 5, -10), scene);
+
+        // This targets the camera to scene origin
+        camera.setTarget(Vector3.Zero());
+        
+        // This attaches the camera to the canvas
+        //camera.attachControl(true);
+        
+        // GUI
+        this.globalState.guiTexture = AdvancedDynamicTexture.CreateFullscreenUI("UI");
+        scene.getEngine().onCanvasPointerOutObservable.clear();
+        // Watch for browser/canvas resize events
+        window.addEventListener("resize", function () {
+        engine.resize();
+        });
+
+        engine.runRenderLoop(() => {this.updateGUIs(); scene.render()});
+    }
+    
+    updateGUIs()
+    {
+        this._guiNodes.forEach(element => {
+            element.updateVisual();
+            
+        });
+    }
+ 
+    render() {
+ 
+        return <canvas id="workbench-canvas" 
+        onWheel={evt => this.onWheel(evt)}
+        onPointerMove={evt => this.onMove(evt)}
+        onPointerDown={evt =>  this.onDown(evt)}   
+        onPointerUp={evt =>  this.onUp(evt)} 
+        >   
+        <div id="workbench-container">
+            <div id="workbench-canvas-container">  
+            </div>     
+            <div id="frame-container">                        
+            </div>
+            <svg id="workbench-svg-container">
+            </svg>                    
+            <div id="selection-container">                        
+            </div>
+        </div>
+        </canvas>
+    }
+}

+ 452 - 0
guiEditor/src/diagram/workbenchCanvas.scss

@@ -0,0 +1,452 @@
+#workbench-canvas {
+    width: 100%;
+    height: 100%;
+    margin: 0;
+    padding: 0;            
+    font: 14px "acumin-pro";  
+    user-select: none;
+    overflow: hidden;
+    cursor: move;   
+    background-image:
+        linear-gradient(to right, #4F4E4F 1px, transparent 1px),
+        linear-gradient(to bottom, #4F4E4F 1px, transparent 1px);  
+
+    #selection-container {
+        pointer-events: none;
+        
+        .selection-box {
+            z-index: 10;
+            position: absolute;
+            background: rgba(72, 72, 196, 0.5);
+            border: blue solid 2px;
+        }
+    }
+
+    #workbench-container {
+        width: 100%;
+        height: 100%;
+        left: 0;
+        top: 0;
+        transform-origin: left top;
+        display: grid;
+        grid-template-rows: 100%;          
+        grid-template-columns: 100%;          
+
+        #frame-container {
+            overflow: visible;   
+            grid-row: 1;
+            grid-column: 1;
+            position: relative;
+            width: 100%;
+            height: 100%;             
+        }
+
+        .frame-box {
+            position: absolute;
+            background: rgba(72, 72, 72, 0.7);
+            display: grid;
+            grid-template-rows: 40px calc(100% - 40px);
+            grid-template-columns: 100%;            
+            box-sizing: border-box;
+
+            &.collapsed {
+                height: auto !important;
+                width: 200px !important;
+                z-index: 3;
+
+                .frame-box-header {
+                    font-size: 16px;
+                    grid-template-columns: calc(100% - 37px) 30px 7px;  
+                    
+                    .frame-box-header-collapse {
+                        margin-top: -2px;
+                    }
+                    
+                    .frame-box-header-close {
+                        display: none;
+                    }
+                }
+
+                .frame-comments.has-comments{
+                    .frame-comment-span{
+                        white-space: nowrap;
+                        text-overflow: ellipsis;
+                        overflow: hidden;
+                    }
+                }
+            }
+
+            .frame-box-border {                
+                grid-row: 1 / span 2;
+                grid-column: 1;
+                width: 100%;
+                height: 100%;
+                border: transparent solid 4px;
+                pointer-events: none;
+                box-sizing: border-box;
+            }
+
+            .frame-box-header {
+                grid-row: 1;
+                grid-column: 1;
+                background: rgba(72, 72, 72, 1);    
+                color: white;
+                font-size: 24px;
+                text-align: center;
+                display: grid;
+                grid-template-rows: 100%;  
+                grid-template-columns: calc(100% - 74px) 30px 7px 30px 7px;  
+                align-content: center;
+                overflow: hidden;
+
+                .frame-box-header-button {
+                    cursor: pointer;
+                    align-self: center;
+                    transform-origin: 50% 50%;
+                    transform: scale(1);
+                    stroke: transparent;
+                    fill: white;
+                    display: grid;               
+
+                    &.down {
+                        transform: scale(0.90);
+                    }
+                }
+
+                .frame-box-header-collapse {
+                    grid-column: 2;
+                    grid-row: 1;
+                }
+
+                .frame-box-header-close {
+                    grid-column: 4;
+                    grid-row: 1;
+                }
+
+                .frame-box-header-title {
+                    grid-column: 1;
+                    grid-row: 1;
+                    display: grid;
+                    height: 100%;
+                    width: 100%;
+                    align-self: stretch;
+                    align-items: center;
+                    margin-top: -2px;
+                }
+            }
+
+
+            .frame-comments.has-comments{
+                display: grid;
+                grid-row: 2;
+                grid-column: 1;
+                padding: 0 10px;
+                font-style: italic;
+                word-wrap: break-word;
+            }
+
+            &.selected {
+                .frame-box-border {
+                  border-color: white;
+                }
+            }
+
+            .right-handle {
+                grid-area: 1 / 2 / 3 / 2;
+                width: 4px;
+                background-color: transparent;
+                cursor: ew-resize;
+
+                &::after{
+                    content: "";
+                    width: 8px;
+                    position: absolute;
+                    top: 0;
+                    bottom: 0;
+                    margin-left: -4px;
+                    cursor: ew-resize;
+                    
+                }
+
+                &.collapsed {
+                    cursor: pointer;
+                }
+            }
+
+            .top-right-corner-handle{
+                background-color: transparent;
+                height: 4px;
+                z-index: 21;
+                cursor: ne-resize;
+                width: 4px;
+                margin-left: -6px;
+
+                &::after {
+                    background-color: transparent;
+                    cursor: ne-resize;
+                    margin-left: unset;
+                    top: -4px;
+                    height: 10px;
+                    width: 10px;
+                }
+            }
+
+
+            .bottom-right-corner-handle{
+                background-color: transparent;
+                height: 0px;
+                z-index: 21;
+                cursor: nw-resize;
+                grid-area: 4 / 2 / 4 / 2;;
+                margin-left: -2px;
+
+
+                &::after {
+                    background-color: transparent;
+                    height: 10px;
+                    cursor: nw-resize;
+                    top: unset;
+                    bottom: -4px;
+                    width: 10px;               
+                }
+            }
+
+            .left-handle {
+                grid-area: 1 / 1 / 3 / 1;
+                width: 4px;
+                background-color: transparent;
+                cursor: ew-resize;
+
+                &::before{
+                    content: "";
+                    width: 8px;
+                    position: absolute;
+                    top: 0;
+                    bottom: 0;
+                    margin-left: -4px;
+
+                }
+            }
+
+            .top-left-corner-handle{
+                background-color: transparent;
+                height: 4px;
+                z-index: 21;
+                cursor: nw-resize;
+                width: 4px;
+                margin-left: -4px;
+
+                &::before {
+                    background-color: transparent;
+                    cursor: nw-resize;
+                    margin-left: unset;
+                    top: -4px;
+                    height: 10px;
+                    width: 10px;
+                }
+            }
+
+            .bottom-left-corner-handle{
+                background-color: transparent;
+                height: 0px;
+                z-index: 21;
+                cursor: sw-resize;
+                grid-area: 4 / 1 / 4 / 1;
+
+
+                &::before {
+                    background-color: transparent;
+                    height: 10px;
+                    cursor: sw-resize;
+                    top: unset;
+                    bottom: -4px;
+                    width: 10px;               
+                }
+            }
+
+            .top-handle {
+                grid-area: 1 / 1 / 1 / 1;
+                background-color: transparent;
+                height: 4px;
+                cursor: ns-resize;
+
+                &::before{
+                    content: "";
+                    width: 100%;
+                    position: absolute;
+                    top: -4px;
+                    bottom: 100%;
+                    right: 0;
+                    left: 0;
+                    margin-bottom: -8px;
+                    cursor: ns-resize;
+                    height: 8px;
+                }
+            }
+
+            .bottom-handle {
+                grid-area: 3 / 1 / 3 / 1;
+                background-color: transparent;
+                height: 4px;
+                cursor: ns-resize;
+
+                &::after {
+                    content: "";
+                    width: 100%;
+                    position: absolute;
+                    top: 100%;
+                    bottom: 0;
+                    right: 0;
+                    left: 0;
+                    margin-top: -8px;
+                    cursor: ns-resize;
+                    height: 12px;
+                }
+            }
+            
+            &.collapsed{
+                .top-handle, .top-right-corner-handle, .right-handle, .bottom-right-corner-handle, .bottom-handle, .bottom-left-corner-handle, .left-handle, .top-left-corner-handle {
+                    cursor: default;
+                }
+
+                .right-handle, .bottom-handle, .top-right-corner-handle, .bottom-right-corner-handle{
+                    &::after{
+                        cursor: default;
+                    }
+                }
+
+                .left-handle, .top-handle, .top-left-corner-handle, .bottom-left-corner-handle{
+                    &::before{
+                        cursor: default;
+                    }
+                }
+            }
+        }
+
+        #workbench-svg-container {
+            grid-row: 1;
+            grid-column: 1;
+            position: relative;
+            width: 100%;
+            height: 100%;  
+            overflow: visible; 
+            pointer-events: none;
+            z-index: 2;
+            
+            .link {
+                stroke-width: 4px;    
+                &.selected {                    
+                    stroke: white !important;
+                    stroke-dasharray: 10, 2;
+                }       
+
+                &.hidden {
+                    display: none;
+                }
+            }
+
+            .selection-link {
+                pointer-events: all;
+                stroke-width: 16px;
+                opacity: 0;
+                transition: opacity 75ms;
+                stroke: transparent;                        
+                cursor: pointer;
+
+                &.hidden {
+                    display: none;
+                }
+
+                &:hover, &.selected {
+                    stroke: white !important;
+                    opacity: 0.4;
+                }
+            }
+        }
+
+        #workbench-canvas-container {
+            grid-row: 1;
+            grid-column: 1;
+            position: relative;
+            width: 100%;
+            height: 100%;                  
+
+            .visual {
+                z-index: 4;
+                width: 200px;
+                position: absolute;
+                left: 0;
+                top: 0;
+                background: gray;
+                border: 4px solid black;
+                border-radius: 12px;
+                display: grid;
+                grid-template-rows: 30px auto;
+                grid-template-columns: 100%;
+                color: white;
+
+                &.hidden {
+                    display: none;
+                }
+
+                .comments {
+                    position: absolute;
+                    top: -50px;
+                    width: 200px;
+                    height: 45px;
+                    overflow: hidden;                    
+                    font-style: italic;
+                    opacity: 0.8;
+                    display: grid;
+                    align-items: flex-end;
+                    pointer-events: none;
+                }
+
+                .selection-border {                    
+                    grid-row: 1 / span 3;
+                    grid-column: 1;
+                    margin: -4px;
+
+                    transition: border-color 100ms;
+
+                    border: 4px solid black;
+                    border-radius: 12px;
+                }
+
+                &.selected {
+                    .selection-border {  
+                        border-color: white;
+                    }
+                }
+
+                .header {
+                    grid-row: 1;
+                    grid-column: 1;
+                    border: 4px solid black;
+                    border-top-right-radius: 7px;
+                    border-top-left-radius: 7px;
+                    font-size: 16px;
+                    text-align: center;
+                    margin-top: -1px;
+                    margin-left: -1px;
+                    margin-right: -1px;
+                    white-space: nowrap;
+                    text-overflow: ellipsis;
+                    overflow: hidden;
+                    background: black;
+                    color: white;
+
+                    &.constant {
+                        border-color: #464348;
+                        background: #464348;
+                    }
+            
+                    &.inspector {
+                        border-color: #66491b;
+                        background: #66491b;
+                    }
+                }
+            }
+        }
+    }
+}

+ 42 - 0
guiEditor/src/globalState.ts

@@ -0,0 +1,42 @@
+import { Nullable } from "babylonjs/types";
+import { Observable } from "babylonjs/Misc/observable";
+import { LogEntry } from "./components/log/logComponent";
+import { DataStorage } from "babylonjs/Misc/dataStorage";
+import { Color4 } from "babylonjs/Maths/math.color";
+import { GUINode } from "./diagram/guiNode";
+import { WorkbenchComponent } from "./diagram/workbench";
+import { AdvancedDynamicTexture } from "babylonjs-gui/2D/advancedDynamicTexture";
+
+export class GlobalState {
+    guiTexture: AdvancedDynamicTexture;
+    hostElement: HTMLElement;
+    hostDocument: HTMLDocument;
+    hostWindow: Window;
+    onSelectionChangedObservable = new Observable<Nullable<GUINode>>();
+    onRebuildRequiredObservable = new Observable<void>();
+    onBuiltObservable = new Observable<void>();
+    onResetRequiredObservable = new Observable<void>();
+    onUpdateRequiredObservable = new Observable<void>();
+    onReOrganizedRequiredObservable = new Observable<void>();
+    onLogRequiredObservable = new Observable<LogEntry>();
+    onErrorMessageDialogRequiredObservable = new Observable<string>();
+    onIsLoadingChanged = new Observable<boolean>();
+    onSelectionBoxMoved = new Observable<ClientRect | DOMRect>();
+    onGuiNodeRemovalObservable = new Observable<GUINode>();
+    backgroundColor: Color4;
+    blockKeyboardEvents = false;
+    controlCamera: boolean;
+    workbench: WorkbenchComponent;
+    storeEditorData: (serializationObject: any) => void;
+
+    customSave?: { label: string; action: (data: string) => Promise<void> };
+
+    public constructor() {
+        this.controlCamera = DataStorage.ReadBoolean("ControlCamera", true);
+
+        let r = DataStorage.ReadNumber("BackgroundColorR", 0.12549019607843137);
+        let g = DataStorage.ReadNumber("BackgroundColorG", 0.09803921568627451);
+        let b = DataStorage.ReadNumber("BackgroundColorB", 0.25098039215686274);
+        this.backgroundColor = new Color4(r, g, b, 1.0);
+    }
+}

+ 80 - 0
guiEditor/src/guiEditor.ts

@@ -0,0 +1,80 @@
+import * as React from "react";
+import * as ReactDOM from "react-dom";
+import { GlobalState } from "./globalState";
+import { WorkbenchEditor } from "./workbenchEditor";
+import { Popup } from "./sharedUiComponents/lines/popup";
+import { SerializationTools } from "./serializationTools";
+import { Observable } from "babylonjs/Misc/observable";
+/**
+ * Interface used to specify creation options for the gui editor
+ */
+export interface IGUIEditorOptions {
+    hostElement?: HTMLElement;
+    customSave?: { label: string; action: (data: string) => Promise<void> };
+    customLoadObservable?: Observable<any>;
+}
+
+/**
+ * Class used to create a gui editor
+ */
+export class GUIEditor {
+    private static _CurrentState: GlobalState;
+
+    /**
+     * Show the gui editor
+     * @param options defines the options to use to configure the gui editor
+     */
+    public static Show(options: IGUIEditorOptions) {
+        if (this._CurrentState) {
+            var popupWindow = (Popup as any)["gui-editor"];
+            if (popupWindow) {
+                popupWindow.close();
+            }
+        }
+
+        let hostElement = options.hostElement;
+
+        if (!hostElement) {
+            hostElement = Popup.CreatePopup("BABYLON.JS GUI EDITOR", "gui-editor", 1000, 800)!;
+        }
+
+        let globalState = new GlobalState();
+        globalState.hostElement = hostElement;
+        globalState.hostDocument = hostElement.ownerDocument!;
+        globalState.customSave = options.customSave;
+        globalState.hostWindow = hostElement.ownerDocument!.defaultView!;
+
+        const graphEditor = React.createElement(WorkbenchEditor, {
+            globalState: globalState,
+        });
+
+        ReactDOM.render(graphEditor, hostElement);
+
+        // create the middle workbench canvas
+        if (!globalState.guiTexture) {
+            globalState.workbench.createGUICanvas();
+        }
+
+        if (options.customLoadObservable) {
+            options.customLoadObservable.add((data) => {
+                SerializationTools.Deserialize(data, globalState);
+                globalState.onResetRequiredObservable.notifyObservers();
+                globalState.onBuiltObservable.notifyObservers();
+            });
+        }
+
+        this._CurrentState = globalState;
+
+        // Close the popup window when the page is refreshed or scene is disposed
+        var popupWindow = (Popup as any)["gui-editor"];
+        if (popupWindow) {
+            window.onbeforeunload = () => {
+                var popupWindow = (Popup as any)["gui-editor"];
+                if (popupWindow) {
+                    popupWindow.close();
+                }
+            };
+        }
+        window.addEventListener("beforeunload", () => {});
+    }
+}

+ 54 - 0
guiEditor/src/guiNodeTools.ts

@@ -0,0 +1,54 @@
+import { Button } from "babylonjs-gui/2D/controls/button";
+import { Checkbox } from "babylonjs-gui/2D/controls/checkbox";
+import { ColorPicker } from "babylonjs-gui/2D/controls/colorpicker";
+import { Ellipse } from "babylonjs-gui/2D/controls/ellipse";
+import { Line } from "babylonjs-gui/2D/controls/line";
+import { Rectangle } from "babylonjs-gui/2D/controls/rectangle";
+import { Slider } from "babylonjs-gui/2D/controls/sliders/slider";
+import { TextBlock } from "babylonjs-gui/2D/controls/textBlock";
+
+export class GUINodeTools {
+    public static CreateControlFromString (data: string) {
+        //TODO: Add more elements and create default values for certain types.
+        let element;
+        switch (data) {
+            case "Slider":
+                element = new Slider("Slider");
+                break;
+            case "Checkbox":
+                element = new Checkbox("Checkbox");
+                break;
+            case "ColorPicker":
+                element = new ColorPicker("ColorPicker");
+                break;
+            case "Ellipse":
+                element = new Ellipse("Ellipse");
+                break;
+            case "Rectangle":
+                element = new Rectangle("Rectangle");
+                break;
+            case "Line":
+                element = new Line();
+                element.x1 = 10;
+                element.y1 = 10;
+                element.x2 = 100;
+                element.y2 = 100;
+                element.lineWidth = 5;
+                element.dash = [50, 10];
+                return element;
+            case "Text":
+                element = new TextBlock("Textblock");
+                element.text = "My Text";
+                return element;
+            default:
+                element = Button.CreateSimpleButton("Button", "Click Me");
+                break;
+        }
+
+        element.width = "150px";
+        element.height = "40px";
+        element.color = "#FFFFFFFF";
+        element.isPointerBlocker = true;
+        return element;
+    }
+}

+ 1 - 0
guiEditor/src/index.ts

@@ -0,0 +1 @@
+export * from "./guiEditor";

+ 9 - 0
guiEditor/src/legacy/legacy.ts

@@ -0,0 +1,9 @@
+import { GUIEditor } from "../index";
+
+var globalObject = (typeof global !== 'undefined') ? global : ((typeof window !== 'undefined') ? window : undefined);
+if (typeof globalObject !== "undefined") {
+    (<any>globalObject).BABYLON = (<any>globalObject).BABYLON || {};
+    (<any>globalObject).BABYLON.GuiEditor = GUIEditor;
+}
+
+export * from "../index";

+ 344 - 0
guiEditor/src/main.scss

@@ -0,0 +1,344 @@
+#gui-editor-workbench-root {
+    display: grid;
+    grid-template-rows: calc(100% - 120px) 120px;
+    height: 100%;
+    width: 100%;
+    background: #464646;
+    font: 14px "acumin-pro";   
+}
+
+.wait-screen {
+    display: grid;
+    justify-content: center;
+    align-content: center;
+    height: 100%;
+    width: 100%;
+    background: #464646;
+    opacity: 0.95;
+    color:white;
+    font: 24px "acumin-pro";  
+    position: absolute;
+    top: 0;
+    left: 0; 
+
+    &.hidden {
+        visibility: hidden;
+    }
+}
+
+#leftGrab {
+    grid-row: 1 / span 2;
+    grid-column: 2;
+    cursor: ew-resize;
+}
+
+#rightGrab {
+    grid-row: 1 / span 2;
+    grid-column: 4;
+    cursor: ew-resize;
+}
+
+.diagram-container {
+    grid-row: 1;
+    grid-column: 3;
+    background: #5f5b60;
+    width: 100%;
+    height: 100%;
+
+    .diagram {
+        display: none;
+        width: 100%;
+        height: 100%;
+    }
+}
+
+.right-panel {
+    grid-row: 1 / span 2;
+    grid-column: 5;
+    display: grid;
+    grid-template-rows: 1fr 40px auto 40px;
+    grid-template-columns: 100%;
+    height: 100%;
+    overflow-y: auto;
+
+    #propertyTab {
+        grid-row: 1;
+        grid-column: 1;
+    }        
+    
+    .button {
+        display: grid;
+        justify-content: center;
+        align-content: center;
+        height: auto;
+        width: calc(100% / 7);
+        cursor: pointer;
+
+        &:hover {
+            background: rgb(51, 122, 183);
+            color: white;
+            opacity: 0.8;
+        }
+
+        &.selected {
+            background: rgb(51, 122, 183);
+            color: white;
+        }
+        
+        &.align {
+            justify-content: stretch;
+            text-align: center;
+        }
+    }    
+
+    #preview-mesh-bar {
+        grid-row: 2;
+        grid-column: 1;
+        display: grid;
+        grid-template-columns: auto 1fr 40px 40px 40px;
+        align-items: center;
+        font-size: 18px;
+        background-color: #555555;
+
+        #file-picker {
+            display: none;
+        }
+
+        .listLine {
+            grid-column: 1;
+            height: 40px;
+            display: grid;
+            grid-template-columns: 0px 1fr;  
+    
+            .label {
+                grid-column: 1;
+                display: flex;
+                align-items: center;
+                font-size: 14px;
+            }
+    
+            .options {
+                grid-column: 2;
+                
+                display: flex;
+                align-items: center;   
+                margin-left: 5px;
+    
+                select {
+                    width: 115px;
+                }
+            } 
+        }
+
+        .button{
+            color: #ffffff;
+            width: 40px;
+            height: 40px;
+            transform-origin: 50% 50%;
+            
+            &:active {
+                transform: scale(0.90);
+            }
+
+            &:hover {
+                background: #3f3461;
+            }
+            
+            &.selected {
+                background: #9379e6;
+            } 
+
+            img{
+                height: 40px;
+                width: 100%;
+            }
+        }
+
+
+        #play-button {
+            grid-column: 3;
+        }
+
+        #color-picker-button {
+            grid-column: 4;
+            display: grid;
+            grid-template-columns: 100%;
+            grid-template-rows: 100%;
+
+            img {
+                height: 40px;
+                width: 30px;  
+            }
+            #color-picker-image {                
+                padding-left: 5px;
+                padding-bottom: 38px;
+            }
+
+            #color-picker {
+                transform: scale(0);
+                grid-column: 1;
+                grid-row: 1;
+            }
+
+            #color-picker-label {
+                width: 100%;
+                background: transparent;
+                cursor: pointer;            
+            }
+        }
+
+        #preview-new-window {
+            grid-column: 5;
+        }
+
+        select {
+            background-color: #a3a3a3;
+            color: #333333;
+        }
+    }
+
+    #preview-config-bar {
+        grid-row: 4;
+        grid-column: 1;
+        display: grid;
+        grid-template-columns: 40px 40px 40px 1fr 40px 40px;
+        color: white;
+        align-items: center;
+        font-size: 18px;    
+
+        .button {
+            width: 40px;
+            grid-row: 1;
+            height: 40px;
+            transform-origin: 50% 50%;
+
+            &:hover {
+                background: #3f3461;
+            }
+
+            &.selected {
+                background: #9379e6;
+            } 
+            
+
+            &:active {
+                transform: scale(0.90);
+            }
+
+            img{
+                height: auto;
+                width: 100%;
+            }
+
+            &.back-face {
+                grid-column: 6
+            }
+
+            &.depth-pass {
+                grid-column: 5 / 6
+            }
+
+            &.hemispheric-light{
+                grid-column: 3 / 4
+            }
+            &.direction-light-1{
+                grid-column: 2 / 3
+
+            }
+            &.direction-light-0{
+                grid-column: 1 / 2
+                
+            }
+        }
+    }
+}
+
+.blocker {
+    visibility: hidden;
+    position: absolute;
+    width: calc(100% - 40px);
+    height: 100%;
+    top: 0;
+    left: 0;
+
+    background: rgba(20, 20, 20, 0.95);    
+    font-family: "acumin-pro";
+    color: white;
+    font-size: 24px;
+
+    display: grid;
+    align-content: center;
+    justify-content: center;
+
+    user-select: none;
+
+    padding: 20px;
+    text-align: center;
+}
+
+#log-console {
+    grid-row: 2;
+    grid-column: 3;
+}
+
+.dialog-container {
+    position: absolute;
+    width: 100%;
+    height: 100%;
+    background: rgba(0.1, 0.1, 0.1, 0.6);
+    display: grid;
+    font-family: "acumin-pro";
+    top:0;
+
+    .dialog {
+        align-self: center;
+        justify-self: center;
+        min-height: 140px;
+        max-width: 400px;
+        border-radius: 10px;
+        background: white;
+
+        display: grid;
+        grid-template-columns: 100%;
+        grid-template-rows: calc(100% - 50px) 50px;
+
+        .dialog-message {
+            grid-row: 1;
+            grid-column: 1;
+            margin-top: 20px;
+            padding: 10px;
+            font-size: 18px;
+            color: black;
+        }
+
+        .dialog-buttons {
+            grid-row: 2;
+            grid-column: 1;
+            display: grid;
+            grid-template-rows: 100%;
+            grid-template-columns: 100%;
+            color: white;
+
+            .dialog-button-ok {
+                cursor: pointer;
+                justify-self: center;
+                background:green;
+                min-width: 80px;
+                justify-content: center;
+                display: grid;
+                align-content: center;
+                align-self: center;
+                height: 35px;      
+                border-radius: 10px;
+
+                &:hover {
+                    opacity: 0.8;
+                }
+
+                &.error {
+                    background: red;
+                }
+            }
+        }
+    }
+}

+ 26 - 0
guiEditor/src/nodeLocationInfo.ts

@@ -0,0 +1,26 @@
+export interface INodeLocationInfo {
+    blockId: number;
+    x: number;
+    y: number;
+}
+
+export interface IFrameData {
+    x: number;
+    y: number;
+    width: number;
+    height: number;
+    color: number[];
+    name: string;
+    isCollapsed: boolean;
+    blocks: number[];
+    comments: string;
+}
+
+export interface IEditorData {
+    locations: INodeLocationInfo[];
+    x: number;
+    y: number;
+    zoom: number;
+    frames?: IFrameData[];
+    map?: {[key: number]: number};
+}

+ 17 - 0
guiEditor/src/portal.tsx

@@ -0,0 +1,17 @@
+
+import * as React from "react";
+import { GlobalState } from './globalState';
+import * as ReactDOM from 'react-dom';
+
+interface IPortalProps {
+    globalState: GlobalState;
+}
+
+export class Portal extends React.Component<IPortalProps> {
+    render() {
+        return ReactDOM.createPortal(
+            this.props.children,
+            this.props.globalState.hostElement
+        );
+    }
+}

+ 8 - 0
guiEditor/src/serializationTools.ts

@@ -0,0 +1,8 @@
+import { GlobalState } from './globalState';
+
+export class SerializationTools {
+
+    public static Deserialize(serializationObject: any, globalState: GlobalState) {
+        globalState.onIsLoadingChanged.notifyObservers(true);
+    }
+}

+ 147 - 0
guiEditor/src/sharedComponents/floatLineComponent.tsx

@@ -0,0 +1,147 @@
+import * as React from "react";
+import { Observable } from "babylonjs/Misc/observable";
+import { PropertyChangedEvent } from "./propertyChangedEvent";
+import { GlobalState } from '../globalState';
+
+interface IFloatLineComponentProps {
+    label: string;
+    target: any;
+    propertyName: string;
+    onChange?: (newValue: number) => void;
+    isInteger?: boolean;
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>;
+    additionalClass?: string;
+    step?: string;
+    digits?: number;
+    globalState: GlobalState;
+    min?: number
+    max?: number
+    smallUI?: boolean;
+    onEnter?: (newValue:number) => void;
+}
+
+export class FloatLineComponent extends React.Component<IFloatLineComponentProps, { value: string }> {
+    private _localChange = false;
+    private _store: number;
+    private _regExp: RegExp;
+    private _digits: number;
+    constructor(props: IFloatLineComponentProps) {
+        super(props);
+        let currentValue = this.props.target[this.props.propertyName];
+        
+        this._digits == this.props.digits == undefined ? 2 : this.props.digits;
+    
+        this.state = { value: currentValue ? (this.props.isInteger ? currentValue.toFixed(0) : currentValue.toFixed(this._digits)) : "0" };
+        this._store = currentValue;
+
+        let rexp = "(.*\\.";
+        let numDigits = this._digits;
+        while (numDigits--) {
+            rexp += ".";
+        }
+        rexp += ").+";
+
+        this._regExp = new RegExp(rexp);
+    }
+
+    shouldComponentUpdate(nextProps: IFloatLineComponentProps, nextState: { value: string }) {
+        if (this._localChange) {
+            this._localChange = false;
+            return true;
+        }
+
+        const newValue = nextProps.target[nextProps.propertyName];
+        const newValueString = newValue ? this.props.isInteger ? newValue.toFixed(0) : newValue.toFixed(this._digits) : "0";
+
+        if (newValueString !== nextState.value) {
+            nextState.value = newValueString;
+            return true;
+        }
+        return false;
+    }
+
+    raiseOnPropertyChanged(newValue: number, previousValue: number) {
+        if (this.props.onChange) {
+            this.props.onChange(newValue);
+        }
+
+        if (!this.props.onPropertyChangedObservable) {
+            return;
+        }
+        this.props.onPropertyChangedObservable.notifyObservers({
+            object: this.props.target,
+            property: this.props.propertyName,
+            value: newValue,
+            initialValue: previousValue
+        });
+    }
+
+    updateValue(valueString: string) {
+        if (/[^0-9\.\-]/g.test(valueString)) {
+            return;
+        }
+
+        valueString = valueString.replace(this._regExp, "$1");
+
+        let valueAsNumber: number;
+
+        if (this.props.isInteger) {
+            valueAsNumber = parseInt(valueString);
+        } else {
+            valueAsNumber = parseFloat(valueString);
+        }
+
+        this._localChange = true;
+        this.setState({ value: valueString});
+
+        if (isNaN(valueAsNumber)) {
+            return;
+        }
+        if(this.props.max != undefined && (valueAsNumber > this.props.max)) {
+            valueAsNumber = this.props.max;
+        }
+        if(this.props.min != undefined && (valueAsNumber < this.props.min)) {
+            valueAsNumber = this.props.min;
+        }
+
+        this.props.target[this.props.propertyName] = valueAsNumber;
+        this.raiseOnPropertyChanged(valueAsNumber, this._store);
+
+        this._store = valueAsNumber;
+    }
+
+    render() {
+        let className = this.props.smallUI ? "short": "value";
+
+        return (
+            <div>
+                {
+                    <div className={this.props.additionalClass ? this.props.additionalClass + " floatLine" : "floatLine"}>
+                        <div className="label">
+                            {this.props.label}
+                        </div>
+                        <div className={className}>
+                            <input type="number" step={this.props.step || "0.01"} className="numeric-input"
+                            onBlur={(evt) => {
+                                this.props.globalState.blockKeyboardEvents = false;
+                                if(this.props.onEnter) {
+                                    this.props.onEnter(this._store);
+                                }
+                            }}
+                            onKeyDown={evt => {
+                                if (evt.keyCode !== 13) {
+                                    return;
+                                }
+                                if(this.props.onEnter) {
+                                    this.props.onEnter(this._store);
+                                }
+                            }}
+                            onFocus={() => this.props.globalState.blockKeyboardEvents = true}
+                            value={this.state.value} onChange={(evt) => this.updateValue(evt.target.value)} />
+                        </div>
+                    </div>
+                }
+            </div>
+        );
+    }
+}

+ 52 - 0
guiEditor/src/sharedComponents/lineWithFileButtonComponent.tsx

@@ -0,0 +1,52 @@
+import * as React from "react";
+import { DataStorage } from 'babylonjs/Misc/dataStorage';
+
+interface ILineWithFileButtonComponentProps {
+    title: string;
+    closed?: boolean;
+    label: string;
+    iconImage: any;
+    onIconClick: (file: File) => void;
+    accept: string;
+    uploadName?: string;
+}
+
+export class LineWithFileButtonComponent extends React.Component<ILineWithFileButtonComponentProps, { isExpanded: boolean }> {
+    private uploadRef: React.RefObject<HTMLInputElement>
+    constructor(props: ILineWithFileButtonComponentProps) {
+        super(props);
+
+        let initialState = DataStorage.ReadBoolean(this.props.title, !this.props.closed);
+        this.state = { isExpanded: initialState };
+        this.uploadRef = React.createRef();
+    }
+
+    onChange(evt: any) {
+        var files: File[] = evt.target.files;
+        if (files && files.length) {
+            this.props.onIconClick(files[0]);
+        }
+        evt.target.value = "";
+    }
+
+    switchExpandedState(): void {
+        const newState = !this.state.isExpanded;
+        DataStorage.WriteBoolean(this.props.title, newState);
+        this.setState({ isExpanded: newState });
+    }
+
+    render() {
+        return (
+            <div className="nonDraggableLine withButton">
+                {this.props.label}
+                <div className="icon" title={this.props.title}>
+                <img className="img" src={this.props.iconImage}/>
+                </div>
+                <div className="buttonLine" title={this.props.title}>
+                    <label htmlFor={this.props.uploadName ? this.props.uploadName : "file-upload"} className="file-upload"/>   
+                    <input ref={this.uploadRef} id={this.props.uploadName ? this.props.uploadName : "file-upload"} type="file" accept={this.props.accept} onChange={evt => this.onChange(evt)} />
+                </div>
+            </div>
+        ); 
+    }
+}

+ 42 - 0
guiEditor/src/sharedComponents/messageDialog.tsx

@@ -0,0 +1,42 @@
+import * as React from "react";
+
+import { GlobalState } from '../globalState';
+
+interface IMessageDialogComponentProps {
+    globalState: GlobalState
+}
+
+export class MessageDialogComponent extends React.Component<IMessageDialogComponentProps, { message: string, isError: boolean }> {
+    constructor(props: IMessageDialogComponentProps) {
+        super(props);
+
+        this.state = {message: "", isError: false};
+
+        this.props.globalState.onErrorMessageDialogRequiredObservable.add((message: string) => {
+            this.setState({message: message, isError: true});
+        });
+    }
+
+    render() {
+        if (!this.state.message) {
+            return null;
+        }
+
+        return (
+            <div className="dialog-container">
+                <div className="dialog">
+                    <div className="dialog-message">
+                        {
+                            this.state.message
+                        }
+                    </div>
+                    <div className="dialog-buttons">
+                        <div className={"dialog-button-ok" + (this.state.isError ? " error" : "")} onClick={() => this.setState({message: ""})}>
+                            OK
+                        </div>
+                    </div>
+                </div>
+            </div>
+        );
+    }
+}

+ 76 - 0
guiEditor/src/sharedComponents/numericInputComponent.tsx

@@ -0,0 +1,76 @@
+import * as React from "react";
+import { GlobalState } from '../globalState';
+
+interface INumericInputComponentProps {
+    label: string;
+    value: number;
+    step?: number;
+    onChange: (value: number) => void;
+    globalState: GlobalState;
+}
+
+export class NumericInputComponent extends React.Component<INumericInputComponentProps, { value: string }> {
+
+    static defaultProps = {
+        step: 1,
+    };
+
+    private _localChange = false;
+    constructor(props: INumericInputComponentProps) {
+        super(props);
+
+        this.state = { value: this.props.value.toFixed(3) }
+    }
+
+    shouldComponentUpdate(nextProps: INumericInputComponentProps, nextState: { value: string }) {
+        if (this._localChange) {
+            this._localChange = false;
+            return true;
+        }
+
+        if (nextProps.value.toString() !== nextState.value) {
+            nextState.value = nextProps.value.toFixed(3);
+            return true;
+        }
+        return false;
+    }
+
+    updateValue(evt: any) {
+        let value = evt.target.value;
+
+        if (/[^0-9\.\-]/g.test(value)) {
+            return;
+        }
+
+        let valueAsNumber = parseFloat(value);
+
+        this._localChange = true;
+        this.setState({ value: value });
+
+        if (isNaN(valueAsNumber)) {
+            return;
+        }
+
+        this.props.onChange(valueAsNumber);
+    }
+
+
+    render() {
+        return (
+            <div className="numeric">
+                {
+                    this.props.label &&
+                    <div className="numeric-label">
+                        {`${this.props.label}: `}
+                    </div>
+                }
+                <input type="number" 
+                    onFocus={() => this.props.globalState.blockKeyboardEvents = true}
+                    onBlur={evt => {
+                        this.props.globalState.blockKeyboardEvents = false;
+                    }}
+                    step={this.props.step} className="numeric-input" value={this.state.value} onChange={evt => this.updateValue(evt)} />
+            </div>
+        )
+    }
+}

+ 0 - 0
guiEditor/src/sharedComponents/propertyChangedEvent.ts


برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است