浏览代码

Merge pull request #7750 from BabylonJS/master

Nightly
mergify[bot] 5 年之前
父节点
当前提交
16d2277c01
共有 41 个文件被更改,包括 847 次插入141 次删除
  1. 5 0
      dist/preview release/babylon.d.ts
  2. 1 1
      dist/preview release/babylon.js
  3. 63 63
      dist/preview release/babylon.max.js
  4. 1 1
      dist/preview release/babylon.max.js.map
  5. 10 0
      dist/preview release/babylon.module.d.ts
  6. 5 0
      dist/preview release/documentation.d.ts
  7. 6 6
      dist/preview release/inspector/babylon.inspector.bundle.js
  8. 249 16
      dist/preview release/inspector/babylon.inspector.bundle.max.js
  9. 1 1
      dist/preview release/inspector/babylon.inspector.bundle.max.js.map
  10. 35 0
      dist/preview release/inspector/babylon.inspector.d.ts
  11. 79 0
      dist/preview release/inspector/babylon.inspector.module.d.ts
  12. 3 0
      dist/preview release/nodeEditor/babylon.nodeEditor.d.ts
  13. 4 4
      dist/preview release/nodeEditor/babylon.nodeEditor.js
  14. 11 6
      dist/preview release/nodeEditor/babylon.nodeEditor.max.js
  15. 1 1
      dist/preview release/nodeEditor/babylon.nodeEditor.max.js.map
  16. 6 0
      dist/preview release/nodeEditor/babylon.nodeEditor.module.d.ts
  17. 10 0
      dist/preview release/viewer/babylon.module.d.ts
  18. 7 7
      dist/preview release/viewer/babylon.viewer.js
  19. 1 1
      dist/preview release/viewer/babylon.viewer.max.js
  20. 5 0
      dist/preview release/what's new.md
  21. 4 1
      inspector/src/components/actionTabs/lines/fileButtonLineComponent.tsx
  22. 4 1
      inspector/src/components/actionTabs/lines/fileMultipleButtonLineComponent.tsx
  23. 6 2
      inspector/src/components/actionTabs/lines/textureLineComponent.tsx
  24. 10 0
      inspector/src/components/actionTabs/tabs/propertyGridTabComponent.tsx
  25. 5 2
      inspector/src/components/actionTabs/tabs/propertyGrids/animationGroupPropertyGridComponent.tsx
  26. 5 1
      inspector/src/components/actionTabs/tabs/propertyGrids/animationPropertyGridComponent.tsx
  27. 6 2
      inspector/src/components/actionTabs/tabs/propertyGrids/materials/texturePropertyGridComponent.tsx
  28. 175 0
      inspector/src/components/actionTabs/tabs/propertyGrids/particleSystems/particleSystemPropertyGridComponent.tsx
  29. 15 8
      inspector/src/components/embedHost/embedHostComponent.tsx
  30. 30 0
      inspector/src/components/sceneExplorer/entities/particleSystemTreeItemComponent.tsx
  31. 3 1
      inspector/src/components/sceneExplorer/extensionsComponent.tsx
  32. 12 0
      inspector/src/components/sceneExplorer/sceneExplorer.scss
  33. 21 2
      inspector/src/components/sceneExplorer/sceneExplorerComponent.tsx
  34. 6 0
      inspector/src/components/sceneExplorer/treeItemSpecializedComponent.tsx
  35. 4 2
      nodeEditor/src/components/preview/previewMeshControlComponent.tsx
  36. 5 1
      nodeEditor/src/sharedComponents/fileButtonLineComponent.tsx
  37. 11 7
      nodeEditor/src/sharedComponents/textureLineComponent.tsx
  38. 14 2
      src/Cameras/targetCamera.ts
  39. 6 0
      src/Particles/IParticleSystem.ts
  40. 1 1
      src/Particles/particleSystem.ts
  41. 1 1
      src/Shaders/ShadersInclude/bumpFragmentFunctions.fx

+ 5 - 0
dist/preview release/babylon.d.ts

@@ -13160,6 +13160,11 @@ declare module BABYLON {
          */
         isReady(): boolean;
         /**
+         * Returns the string "ParticleSystem"
+         * @returns a string containing the class name
+         */
+        getClassName(): string;
+        /**
          * Adds a new color gradient
          * @param gradient defines the gradient to use (between 0 and 1)
          * @param color1 defines the color to affect to the specified gradient

文件差异内容过多而无法显示
+ 1 - 1
dist/preview release/babylon.js


文件差异内容过多而无法显示
+ 63 - 63
dist/preview release/babylon.max.js


文件差异内容过多而无法显示
+ 1 - 1
dist/preview release/babylon.max.js.map


+ 10 - 0
dist/preview release/babylon.module.d.ts

@@ -13486,6 +13486,11 @@ declare module "babylonjs/Particles/IParticleSystem" {
          */
         isReady(): boolean;
         /**
+         * Returns the string "ParticleSystem"
+         * @returns a string containing the class name
+         */
+        getClassName(): string;
+        /**
          * Adds a new color gradient
          * @param gradient defines the gradient to use (between 0 and 1)
          * @param color1 defines the color to affect to the specified gradient
@@ -86568,6 +86573,11 @@ declare module BABYLON {
          */
         isReady(): boolean;
         /**
+         * Returns the string "ParticleSystem"
+         * @returns a string containing the class name
+         */
+        getClassName(): string;
+        /**
          * Adds a new color gradient
          * @param gradient defines the gradient to use (between 0 and 1)
          * @param color1 defines the color to affect to the specified gradient

+ 5 - 0
dist/preview release/documentation.d.ts

@@ -13160,6 +13160,11 @@ declare module BABYLON {
          */
         isReady(): boolean;
         /**
+         * Returns the string "ParticleSystem"
+         * @returns a string containing the class name
+         */
+        getClassName(): string;
+        /**
          * Adds a new color gradient
          * @param gradient defines the gradient to use (between 0 and 1)
          * @param color1 defines the color to affect to the specified gradient

文件差异内容过多而无法显示
+ 6 - 6
dist/preview release/inspector/babylon.inspector.bundle.js


文件差异内容过多而无法显示
+ 249 - 16
dist/preview release/inspector/babylon.inspector.bundle.max.js


文件差异内容过多而无法显示
+ 1 - 1
dist/preview release/inspector/babylon.inspector.bundle.max.js.map


+ 35 - 0
dist/preview release/inspector/babylon.inspector.d.ts

@@ -461,6 +461,7 @@ declare module INSPECTOR {
     export class FileButtonLineComponent extends React.Component<IFileButtonLineComponentProps> {
         private static _IDGenerator;
         private _id;
+        private uploadInputRef;
         constructor(props: IFileButtonLineComponentProps);
         onChange(evt: any): void;
         render(): JSX.Element;
@@ -524,6 +525,7 @@ declare module INSPECTOR {
         channel: ChannelToDisplay;
         face: number;
     }> {
+        private canvasRef;
         constructor(props: ITextureLineComponentProps);
         shouldComponentUpdate(nextProps: ITextureLineComponentProps, nextState: {
             channel: ChannelToDisplay;
@@ -575,6 +577,7 @@ declare module INSPECTOR {
     }
     export class TexturePropertyGridComponent extends React.Component<ITexturePropertyGridComponentProps> {
         private _adtInstrumentation;
+        private textureLineRef;
         constructor(props: ITexturePropertyGridComponentProps);
         componentWillUnmount(): void;
         updateTexture(file: File): void;
@@ -898,6 +901,7 @@ declare module INSPECTOR {
         private _onAnimationGroupPlayObserver;
         private _onAnimationGroupPauseObserver;
         private _onBeforeRenderObserver;
+        private timelineRef;
         constructor(props: IAnimationGroupGridComponentProps);
         disconnect(animationGroup: BABYLON.AnimationGroup): void;
         connect(animationGroup: BABYLON.AnimationGroup): void;
@@ -1175,6 +1179,7 @@ declare module INSPECTOR {
         private _runningAnimatable;
         private _onBeforeRenderObserver;
         private _isPlaying;
+        private timelineRef;
         constructor(props: IAnimationGridComponentProps);
         playOrPause(): void;
         componentDidMount(): void;
@@ -1346,6 +1351,19 @@ declare module INSPECTOR {
     }
 }
 declare module INSPECTOR {
+    interface IParticleSystemPropertyGridComponentProps {
+        globalState: GlobalState;
+        system: BABYLON.IParticleSystem;
+        lockObject: LockObject;
+        onSelectionChangedObservable?: BABYLON.Observable<any>;
+        onPropertyChangedObservable?: BABYLON.Observable<PropertyChangedEvent>;
+    }
+    export class ParticleSystemPropertyGridComponent extends React.Component<IParticleSystemPropertyGridComponentProps> {
+        constructor(props: IParticleSystemPropertyGridComponentProps);
+        render(): JSX.Element;
+    }
+}
+declare module INSPECTOR {
     export class PropertyGridTabComponent extends PaneComponent {
         private _timerIntervalId;
         private _lockObject;
@@ -1415,6 +1433,7 @@ declare module INSPECTOR {
     export class FileMultipleButtonLineComponent extends React.Component<IFileMultipleButtonLineComponentProps> {
         private static _IDGenerator;
         private _id;
+        private uploadInputRef;
         constructor(props: IFileMultipleButtonLineComponentProps);
         onChange(evt: any): void;
         render(): JSX.Element;
@@ -1499,6 +1518,7 @@ declare module INSPECTOR {
         popupVisible: boolean;
     }> {
         private _popup;
+        private extensionRef;
         constructor(props: IExtensionsComponentProps);
         showPopup(): void;
         componentDidMount(): void;
@@ -1679,6 +1699,17 @@ declare module INSPECTOR {
     }
 }
 declare module INSPECTOR {
+    interface IParticleSystemTreeItemComponentProps {
+        system: BABYLON.IParticleSystem;
+        extensibilityGroups?: BABYLON.IExplorerExtensibilityGroup[];
+        onClick: () => void;
+    }
+    export class ParticleSystemTreeItemComponent extends React.Component<IParticleSystemTreeItemComponentProps> {
+        constructor(props: IParticleSystemTreeItemComponentProps);
+        render(): JSX.Element;
+    }
+}
+declare module INSPECTOR {
     interface ITreeItemSpecializedComponentProps {
         label: string;
         entity?: any;
@@ -1819,6 +1850,7 @@ declare module INSPECTOR {
     }> {
         private _onSelectionChangeObserver;
         private _onNewSceneAddedObserver;
+        private sceneExplorerRef;
         private _once;
         private _hooked;
         private sceneMutationFunc;
@@ -1852,6 +1884,9 @@ declare module INSPECTOR {
     }
     export class EmbedHostComponent extends React.Component<IEmbedHostComponentProps> {
         private _once;
+        private splitRef;
+        private topPartRef;
+        private bottomPartRef;
         constructor(props: IEmbedHostComponentProps);
         componentDidMount(): void;
         renderContent(): JSX.Element;

+ 79 - 0
dist/preview release/inspector/babylon.inspector.module.d.ts

@@ -535,6 +535,7 @@ declare module "babylonjs-inspector/components/actionTabs/lines/fileButtonLineCo
     export class FileButtonLineComponent extends React.Component<IFileButtonLineComponentProps> {
         private static _IDGenerator;
         private _id;
+        private uploadInputRef;
         constructor(props: IFileButtonLineComponentProps);
         onChange(evt: any): void;
         render(): JSX.Element;
@@ -612,6 +613,7 @@ declare module "babylonjs-inspector/components/actionTabs/lines/textureLineCompo
         channel: ChannelToDisplay;
         face: number;
     }> {
+        private canvasRef;
         constructor(props: ITextureLineComponentProps);
         shouldComponentUpdate(nextProps: ITextureLineComponentProps, nextState: {
             channel: ChannelToDisplay;
@@ -673,6 +675,7 @@ declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/mat
     }
     export class TexturePropertyGridComponent extends React.Component<ITexturePropertyGridComponentProps> {
         private _adtInstrumentation;
+        private textureLineRef;
         constructor(props: ITexturePropertyGridComponentProps);
         componentWillUnmount(): void;
         updateTexture(file: File): void;
@@ -1119,6 +1122,7 @@ declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/ani
         private _onAnimationGroupPlayObserver;
         private _onAnimationGroupPauseObserver;
         private _onBeforeRenderObserver;
+        private timelineRef;
         constructor(props: IAnimationGroupGridComponentProps);
         disconnect(animationGroup: AnimationGroup): void;
         connect(animationGroup: AnimationGroup): void;
@@ -1523,6 +1527,7 @@ declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/ani
         private _runningAnimatable;
         private _onBeforeRenderObserver;
         private _isPlaying;
+        private timelineRef;
         constructor(props: IAnimationGridComponentProps);
         playOrPause(): void;
         componentDidMount(): void;
@@ -1745,6 +1750,25 @@ declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/mat
         render(): JSX.Element;
     }
 }
+declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/particleSystems/particleSystemPropertyGridComponent" {
+    import * as React from "react";
+    import { Observable } from "babylonjs/Misc/observable";
+    import { PropertyChangedEvent } from "babylonjs-inspector/components/propertyChangedEvent";
+    import { LockObject } from "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/lockObject";
+    import { GlobalState } from "babylonjs-inspector/components/globalState";
+    import { IParticleSystem } from 'babylonjs/Particles/IParticleSystem';
+    interface IParticleSystemPropertyGridComponentProps {
+        globalState: GlobalState;
+        system: IParticleSystem;
+        lockObject: LockObject;
+        onSelectionChangedObservable?: Observable<any>;
+        onPropertyChangedObservable?: Observable<PropertyChangedEvent>;
+    }
+    export class ParticleSystemPropertyGridComponent extends React.Component<IParticleSystemPropertyGridComponentProps> {
+        constructor(props: IParticleSystemPropertyGridComponentProps);
+        render(): JSX.Element;
+    }
+}
 declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGridTabComponent" {
     import { PaneComponent, IPaneComponentProps } from "babylonjs-inspector/components/actionTabs/paneComponent";
     export class PropertyGridTabComponent extends PaneComponent {
@@ -1823,6 +1847,7 @@ declare module "babylonjs-inspector/components/actionTabs/lines/fileMultipleButt
     export class FileMultipleButtonLineComponent extends React.Component<IFileMultipleButtonLineComponentProps> {
         private static _IDGenerator;
         private _id;
+        private uploadInputRef;
         constructor(props: IFileMultipleButtonLineComponentProps);
         onChange(evt: any): void;
         render(): JSX.Element;
@@ -1917,6 +1942,7 @@ declare module "babylonjs-inspector/components/sceneExplorer/extensionsComponent
         popupVisible: boolean;
     }> {
         private _popup;
+        private extensionRef;
         constructor(props: IExtensionsComponentProps);
         showPopup(): void;
         componentDidMount(): void;
@@ -2138,6 +2164,20 @@ declare module "babylonjs-inspector/components/sceneExplorer/entities/boneTreeIt
         render(): JSX.Element;
     }
 }
+declare module "babylonjs-inspector/components/sceneExplorer/entities/particleSystemTreeItemComponent" {
+    import { IExplorerExtensibilityGroup } from "babylonjs/Debug/debugLayer";
+    import * as React from 'react';
+    import { IParticleSystem } from 'babylonjs/Particles/IParticleSystem';
+    interface IParticleSystemTreeItemComponentProps {
+        system: IParticleSystem;
+        extensibilityGroups?: IExplorerExtensibilityGroup[];
+        onClick: () => void;
+    }
+    export class ParticleSystemTreeItemComponent extends React.Component<IParticleSystemTreeItemComponentProps> {
+        constructor(props: IParticleSystemTreeItemComponentProps);
+        render(): JSX.Element;
+    }
+}
 declare module "babylonjs-inspector/components/sceneExplorer/treeItemSpecializedComponent" {
     import { IExplorerExtensibilityGroup } from "babylonjs/Debug/debugLayer";
     import * as React from "react";
@@ -2300,6 +2340,7 @@ declare module "babylonjs-inspector/components/sceneExplorer/sceneExplorerCompon
     }> {
         private _onSelectionChangeObserver;
         private _onNewSceneAddedObserver;
+        private sceneExplorerRef;
         private _once;
         private _hooked;
         private sceneMutationFunc;
@@ -2337,6 +2378,9 @@ declare module "babylonjs-inspector/components/embedHost/embedHostComponent" {
     }
     export class EmbedHostComponent extends React.Component<IEmbedHostComponentProps> {
         private _once;
+        private splitRef;
+        private topPartRef;
+        private bottomPartRef;
         constructor(props: IEmbedHostComponentProps);
         componentDidMount(): void;
         renderContent(): JSX.Element;
@@ -2851,6 +2895,7 @@ declare module INSPECTOR {
     export class FileButtonLineComponent extends React.Component<IFileButtonLineComponentProps> {
         private static _IDGenerator;
         private _id;
+        private uploadInputRef;
         constructor(props: IFileButtonLineComponentProps);
         onChange(evt: any): void;
         render(): JSX.Element;
@@ -2914,6 +2959,7 @@ declare module INSPECTOR {
         channel: ChannelToDisplay;
         face: number;
     }> {
+        private canvasRef;
         constructor(props: ITextureLineComponentProps);
         shouldComponentUpdate(nextProps: ITextureLineComponentProps, nextState: {
             channel: ChannelToDisplay;
@@ -2965,6 +3011,7 @@ declare module INSPECTOR {
     }
     export class TexturePropertyGridComponent extends React.Component<ITexturePropertyGridComponentProps> {
         private _adtInstrumentation;
+        private textureLineRef;
         constructor(props: ITexturePropertyGridComponentProps);
         componentWillUnmount(): void;
         updateTexture(file: File): void;
@@ -3288,6 +3335,7 @@ declare module INSPECTOR {
         private _onAnimationGroupPlayObserver;
         private _onAnimationGroupPauseObserver;
         private _onBeforeRenderObserver;
+        private timelineRef;
         constructor(props: IAnimationGroupGridComponentProps);
         disconnect(animationGroup: BABYLON.AnimationGroup): void;
         connect(animationGroup: BABYLON.AnimationGroup): void;
@@ -3565,6 +3613,7 @@ declare module INSPECTOR {
         private _runningAnimatable;
         private _onBeforeRenderObserver;
         private _isPlaying;
+        private timelineRef;
         constructor(props: IAnimationGridComponentProps);
         playOrPause(): void;
         componentDidMount(): void;
@@ -3736,6 +3785,19 @@ declare module INSPECTOR {
     }
 }
 declare module INSPECTOR {
+    interface IParticleSystemPropertyGridComponentProps {
+        globalState: GlobalState;
+        system: BABYLON.IParticleSystem;
+        lockObject: LockObject;
+        onSelectionChangedObservable?: BABYLON.Observable<any>;
+        onPropertyChangedObservable?: BABYLON.Observable<PropertyChangedEvent>;
+    }
+    export class ParticleSystemPropertyGridComponent extends React.Component<IParticleSystemPropertyGridComponentProps> {
+        constructor(props: IParticleSystemPropertyGridComponentProps);
+        render(): JSX.Element;
+    }
+}
+declare module INSPECTOR {
     export class PropertyGridTabComponent extends PaneComponent {
         private _timerIntervalId;
         private _lockObject;
@@ -3805,6 +3867,7 @@ declare module INSPECTOR {
     export class FileMultipleButtonLineComponent extends React.Component<IFileMultipleButtonLineComponentProps> {
         private static _IDGenerator;
         private _id;
+        private uploadInputRef;
         constructor(props: IFileMultipleButtonLineComponentProps);
         onChange(evt: any): void;
         render(): JSX.Element;
@@ -3889,6 +3952,7 @@ declare module INSPECTOR {
         popupVisible: boolean;
     }> {
         private _popup;
+        private extensionRef;
         constructor(props: IExtensionsComponentProps);
         showPopup(): void;
         componentDidMount(): void;
@@ -4069,6 +4133,17 @@ declare module INSPECTOR {
     }
 }
 declare module INSPECTOR {
+    interface IParticleSystemTreeItemComponentProps {
+        system: BABYLON.IParticleSystem;
+        extensibilityGroups?: BABYLON.IExplorerExtensibilityGroup[];
+        onClick: () => void;
+    }
+    export class ParticleSystemTreeItemComponent extends React.Component<IParticleSystemTreeItemComponentProps> {
+        constructor(props: IParticleSystemTreeItemComponentProps);
+        render(): JSX.Element;
+    }
+}
+declare module INSPECTOR {
     interface ITreeItemSpecializedComponentProps {
         label: string;
         entity?: any;
@@ -4209,6 +4284,7 @@ declare module INSPECTOR {
     }> {
         private _onSelectionChangeObserver;
         private _onNewSceneAddedObserver;
+        private sceneExplorerRef;
         private _once;
         private _hooked;
         private sceneMutationFunc;
@@ -4242,6 +4318,9 @@ declare module INSPECTOR {
     }
     export class EmbedHostComponent extends React.Component<IEmbedHostComponentProps> {
         private _once;
+        private splitRef;
+        private topPartRef;
+        private bottomPartRef;
         constructor(props: IEmbedHostComponentProps);
         componentDidMount(): void;
         renderContent(): JSX.Element;

+ 3 - 0
dist/preview release/nodeEditor/babylon.nodeEditor.d.ts

@@ -892,6 +892,7 @@ declare module NODEEDITOR {
         accept: string;
     }
     export class FileButtonLineComponent extends React.Component<IFileButtonLineComponentProps> {
+        private uploadRef;
         constructor(props: IFileButtonLineComponentProps);
         onChange(evt: any): void;
         render(): JSX.Element;
@@ -1005,6 +1006,7 @@ declare module NODEEDITOR {
         face: number;
     }
     export class TextureLineComponent extends React.Component<ITextureLineComponentProps, ITextureLineComponentState> {
+        private canvasRef;
         constructor(props: ITextureLineComponentProps);
         shouldComponentUpdate(nextProps: ITextureLineComponentProps, nextState: {
             displayRed: boolean;
@@ -1267,6 +1269,7 @@ declare module NODEEDITOR {
     }
     export class PreviewMeshControlComponent extends React.Component<IPreviewMeshControlComponent> {
         private colorInputRef;
+        private filePickerRef;
         constructor(props: IPreviewMeshControlComponent);
         changeMeshType(newOne: PreviewMeshType): void;
         useCustomMesh(evt: any): void;

文件差异内容过多而无法显示
+ 4 - 4
dist/preview release/nodeEditor/babylon.nodeEditor.js


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

@@ -52357,6 +52357,7 @@ var PreviewMeshControlComponent = /** @class */ (function (_super) {
     function PreviewMeshControlComponent(props) {
         var _this = _super.call(this, props) || this;
         _this.colorInputRef = react__WEBPACK_IMPORTED_MODULE_1__["createRef"]();
+        _this.filePickerRef = react__WEBPACK_IMPORTED_MODULE_1__["createRef"]();
         return _this;
     }
     PreviewMeshControlComponent.prototype.changeMeshType = function (newOne) {
@@ -52417,17 +52418,18 @@ var PreviewMeshControlComponent = /** @class */ (function (_super) {
         }
         return (react__WEBPACK_IMPORTED_MODULE_1__["createElement"]("div", { id: "preview-mesh-bar" },
             react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_sharedComponents_optionsLineComponent__WEBPACK_IMPORTED_MODULE_4__["OptionsLineComponent"], { label: "", options: meshTypeOptions, target: this.props.globalState, propertyName: "previewMeshType", noDirectUpdate: true, onSelect: function (value) {
+                    var _a;
                     if (value !== _previewMeshType__WEBPACK_IMPORTED_MODULE_3__["PreviewMeshType"].Custom + 1) {
                         _this.changeMeshType(value);
                     }
                     else {
-                        react_dom__WEBPACK_IMPORTED_MODULE_5__["findDOMNode"](_this.refs["file-picker"]).click();
+                        (_a = _this.filePickerRef.current) === null || _a === void 0 ? void 0 : _a.click();
                     }
                 } }),
             react__WEBPACK_IMPORTED_MODULE_1__["createElement"]("div", { style: {
                     display: "none"
                 }, title: "Preview with a custom mesh" },
-                react__WEBPACK_IMPORTED_MODULE_1__["createElement"]("input", { ref: "file-picker", id: "file-picker", type: "file", onChange: function (evt) { return _this.useCustomMesh(evt); }, accept: ".gltf, .glb, .babylon, .obj" })),
+                react__WEBPACK_IMPORTED_MODULE_1__["createElement"]("input", { ref: this.filePickerRef, id: "file-picker", type: "file", onChange: function (evt) { return _this.useCustomMesh(evt); }, accept: ".gltf, .glb, .babylon, .obj" })),
             react__WEBPACK_IMPORTED_MODULE_1__["createElement"]("div", { title: "Turn-table animation", onClick: function () { return _this.changeAnimation(); }, className: "button", id: "play-button" }, this.props.globalState.rotatePreview ? react__WEBPACK_IMPORTED_MODULE_1__["createElement"]("img", { src: pauseIcon, alt: "" }) : react__WEBPACK_IMPORTED_MODULE_1__["createElement"]("img", { src: playIcon, alt: "" })),
             react__WEBPACK_IMPORTED_MODULE_1__["createElement"]("div", { id: "color-picker-button", title: "Background color", className: "button align", onClick: function (_) { return _this.changeBackgroundClick(); } },
                 react__WEBPACK_IMPORTED_MODULE_1__["createElement"]("img", { src: colorPicker, alt: "" }),
@@ -58708,7 +58710,9 @@ __webpack_require__.r(__webpack_exports__);
 var FileButtonLineComponent = /** @class */ (function (_super) {
     Object(tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"])(FileButtonLineComponent, _super);
     function FileButtonLineComponent(props) {
-        return _super.call(this, props) || this;
+        var _this = _super.call(this, props) || this;
+        _this.uploadRef = react__WEBPACK_IMPORTED_MODULE_1__["createRef"]();
+        return _this;
     }
     FileButtonLineComponent.prototype.onChange = function (evt) {
         var files = evt.target.files;
@@ -58721,7 +58725,7 @@ var FileButtonLineComponent = /** @class */ (function (_super) {
         var _this = this;
         return (react__WEBPACK_IMPORTED_MODULE_1__["createElement"]("div", { className: "buttonLine" },
             react__WEBPACK_IMPORTED_MODULE_1__["createElement"]("label", { htmlFor: "file-upload", className: "file-upload" }, this.props.label),
-            react__WEBPACK_IMPORTED_MODULE_1__["createElement"]("input", { ref: "upload", id: "file-upload", type: "file", accept: this.props.accept, onChange: function (evt) { return _this.onChange(evt); } })));
+            react__WEBPACK_IMPORTED_MODULE_1__["createElement"]("input", { ref: this.uploadRef, id: "file-upload", type: "file", accept: this.props.accept, onChange: function (evt) { return _this.onChange(evt); } })));
     };
     return FileButtonLineComponent;
 }(react__WEBPACK_IMPORTED_MODULE_1__["Component"]));
@@ -59567,6 +59571,7 @@ var TextureLineComponent = /** @class */ (function (_super) {
             displayAlpha: true,
             face: 0
         };
+        _this.canvasRef = react__WEBPACK_IMPORTED_MODULE_1__["createRef"]();
         return _this;
     }
     TextureLineComponent.prototype.shouldComponentUpdate = function (nextProps, nextState) {
@@ -59579,7 +59584,7 @@ var TextureLineComponent = /** @class */ (function (_super) {
         this.updatePreview();
     };
     TextureLineComponent.prototype.updatePreview = function () {
-        TextureLineComponent.UpdatePreview(this.refs.canvas, this.props.texture, this.props.width, this.state, undefined, this.props.globalState);
+        TextureLineComponent.UpdatePreview(this.canvasRef.current, this.props.texture, this.props.width, this.state, undefined, this.props.globalState);
     };
     TextureLineComponent.UpdatePreview = function (previewCanvas, texture, width, options, onReady, globalState) {
         if (!texture.isReady() && texture._texture) {
@@ -59699,7 +59704,7 @@ var TextureLineComponent = /** @class */ (function (_super) {
                     react__WEBPACK_IMPORTED_MODULE_1__["createElement"]("button", { className: this.state.displayBlue && !this.state.displayAlpha ? "blue command selected" : "blue command", onClick: function () { return _this.setState({ displayRed: false, displayGreen: false, displayBlue: true, displayAlpha: false }); } }, "B"),
                     react__WEBPACK_IMPORTED_MODULE_1__["createElement"]("button", { className: this.state.displayAlpha && !this.state.displayRed ? "alpha command selected" : "alpha command", onClick: function () { return _this.setState({ displayRed: false, displayGreen: false, displayBlue: false, displayAlpha: true }); } }, "A"),
                     react__WEBPACK_IMPORTED_MODULE_1__["createElement"]("button", { className: this.state.displayRed && this.state.displayGreen ? "all command selected" : "all command", onClick: function () { return _this.setState({ displayRed: true, displayGreen: true, displayBlue: true, displayAlpha: true }); } }, "ALL")),
-            react__WEBPACK_IMPORTED_MODULE_1__["createElement"]("canvas", { ref: "canvas", className: "preview" })));
+            react__WEBPACK_IMPORTED_MODULE_1__["createElement"]("canvas", { ref: this.canvasRef, className: "preview" })));
     };
     return TextureLineComponent;
 }(react__WEBPACK_IMPORTED_MODULE_1__["Component"]));

文件差异内容过多而无法显示
+ 1 - 1
dist/preview release/nodeEditor/babylon.nodeEditor.max.js.map


+ 6 - 0
dist/preview release/nodeEditor/babylon.nodeEditor.module.d.ts

@@ -1086,6 +1086,7 @@ declare module "babylonjs-node-editor/sharedComponents/fileButtonLineComponent"
         accept: string;
     }
     export class FileButtonLineComponent extends React.Component<IFileButtonLineComponentProps> {
+        private uploadRef;
         constructor(props: IFileButtonLineComponentProps);
         onChange(evt: any): void;
         render(): JSX.Element;
@@ -1221,6 +1222,7 @@ declare module "babylonjs-node-editor/sharedComponents/textureLineComponent" {
         face: number;
     }
     export class TextureLineComponent extends React.Component<ITextureLineComponentProps, ITextureLineComponentState> {
+        private canvasRef;
         constructor(props: ITextureLineComponentProps);
         shouldComponentUpdate(nextProps: ITextureLineComponentProps, nextState: {
             displayRed: boolean;
@@ -1527,6 +1529,7 @@ declare module "babylonjs-node-editor/components/preview/previewMeshControlCompo
     }
     export class PreviewMeshControlComponent extends React.Component<IPreviewMeshControlComponent> {
         private colorInputRef;
+        private filePickerRef;
         constructor(props: IPreviewMeshControlComponent);
         changeMeshType(newOne: PreviewMeshType): void;
         useCustomMesh(evt: any): void;
@@ -2562,6 +2565,7 @@ declare module NODEEDITOR {
         accept: string;
     }
     export class FileButtonLineComponent extends React.Component<IFileButtonLineComponentProps> {
+        private uploadRef;
         constructor(props: IFileButtonLineComponentProps);
         onChange(evt: any): void;
         render(): JSX.Element;
@@ -2675,6 +2679,7 @@ declare module NODEEDITOR {
         face: number;
     }
     export class TextureLineComponent extends React.Component<ITextureLineComponentProps, ITextureLineComponentState> {
+        private canvasRef;
         constructor(props: ITextureLineComponentProps);
         shouldComponentUpdate(nextProps: ITextureLineComponentProps, nextState: {
             displayRed: boolean;
@@ -2937,6 +2942,7 @@ declare module NODEEDITOR {
     }
     export class PreviewMeshControlComponent extends React.Component<IPreviewMeshControlComponent> {
         private colorInputRef;
+        private filePickerRef;
         constructor(props: IPreviewMeshControlComponent);
         changeMeshType(newOne: PreviewMeshType): void;
         useCustomMesh(evt: any): void;

+ 10 - 0
dist/preview release/viewer/babylon.module.d.ts

@@ -13486,6 +13486,11 @@ declare module "babylonjs/Particles/IParticleSystem" {
          */
         isReady(): boolean;
         /**
+         * Returns the string "ParticleSystem"
+         * @returns a string containing the class name
+         */
+        getClassName(): string;
+        /**
          * Adds a new color gradient
          * @param gradient defines the gradient to use (between 0 and 1)
          * @param color1 defines the color to affect to the specified gradient
@@ -86568,6 +86573,11 @@ declare module BABYLON {
          */
         isReady(): boolean;
         /**
+         * Returns the string "ParticleSystem"
+         * @returns a string containing the class name
+         */
+        getClassName(): string;
+        /**
          * Adds a new color gradient
          * @param gradient defines the gradient to use (between 0 and 1)
          * @param color1 defines the color to affect to the specified gradient

文件差异内容过多而无法显示
+ 7 - 7
dist/preview release/viewer/babylon.viewer.js


文件差异内容过多而无法显示
+ 1 - 1
dist/preview release/viewer/babylon.viewer.max.js


+ 5 - 0
dist/preview release/what's new.md

@@ -6,11 +6,16 @@
 
 ### General
 - NME Frames are now resizable from the corners ([Kyle Belfort](https://github.com/belfortk)
+- Refactored React refs from old string API to React.createRef() API ([Kyle Belfort](https://github.com/belfortk)
 
 ### Engine
 
 - Allow logging of shader code when a compilation error occurs ([Popov72](https://github.com/Popov72))
 
+### Cameras
+
+- Added flag to TargetCamera to invert rotation direction and multiplier to adjust speed ([Exolun](https://github.com/Exolun))
+
 ### Physics
 
 - Ammo.js IDL exposed property update and raycast vehicle stablization support ([MackeyK24](https://github.com/MackeyK24))

+ 4 - 1
inspector/src/components/actionTabs/lines/fileButtonLineComponent.tsx

@@ -9,9 +9,12 @@ interface IFileButtonLineComponentProps {
 export class FileButtonLineComponent extends React.Component<IFileButtonLineComponentProps> {
     private static _IDGenerator = 0;
     private _id = FileButtonLineComponent._IDGenerator++;
+    private uploadInputRef: React.RefObject<HTMLInputElement>;
+
 
     constructor(props: IFileButtonLineComponentProps) {
         super(props);
+        this.uploadInputRef = React.createRef();
     }
 
     onChange(evt: any) {
@@ -29,7 +32,7 @@ export class FileButtonLineComponent extends React.Component<IFileButtonLineComp
                 <label htmlFor={"file-upload" + this._id} className="file-upload">
                     {this.props.label}
                 </label>
-                <input ref="upload" id={"file-upload" + this._id} type="file" accept={this.props.accept} onChange={evt => this.onChange(evt)} />
+                <input ref={this.uploadInputRef} id={"file-upload" + this._id} type="file" accept={this.props.accept} onChange={evt => this.onChange(evt)} />
             </div>
         );
     }

+ 4 - 1
inspector/src/components/actionTabs/lines/fileMultipleButtonLineComponent.tsx

@@ -9,9 +9,12 @@ interface IFileMultipleButtonLineComponentProps {
 export class FileMultipleButtonLineComponent extends React.Component<IFileMultipleButtonLineComponentProps> {
     private static _IDGenerator = 0;
     private _id = FileMultipleButtonLineComponent._IDGenerator++;
+    private uploadInputRef: React.RefObject<HTMLInputElement>;
+
 
     constructor(props: IFileMultipleButtonLineComponentProps) {
         super(props);
+        this.uploadInputRef = React.createRef();
     }
 
     onChange(evt: any) {
@@ -29,7 +32,7 @@ export class FileMultipleButtonLineComponent extends React.Component<IFileMultip
                 <label htmlFor={"file-upload" + this._id} className="file-upload">
                     {this.props.label}
                 </label>
-                <input ref="upload" id={"file-upload" + this._id} type="file" accept={this.props.accept} onChange={evt => this.onChange(evt)} multiple />
+                <input ref={this.uploadInputRef} id={"file-upload" + this._id} type="file" accept={this.props.accept} onChange={evt => this.onChange(evt)} multiple />
             </div>
         );
     }

+ 6 - 2
inspector/src/components/actionTabs/lines/textureLineComponent.tsx

@@ -27,6 +27,8 @@ enum ChannelToDisplay {
 }
 
 export class TextureLineComponent extends React.Component<ITextureLineComponentProps, { channel: ChannelToDisplay, face: number }> {
+    private canvasRef: React.RefObject<HTMLCanvasElement>;
+
     constructor(props: ITextureLineComponentProps) {
         super(props);
 
@@ -34,6 +36,8 @@ export class TextureLineComponent extends React.Component<ITextureLineComponentP
             channel: ChannelToDisplay.All,
             face: 0
         };
+
+        this.canvasRef = React.createRef();
     }
 
     shouldComponentUpdate(nextProps: ITextureLineComponentProps, nextState: { channel: ChannelToDisplay, face: number }): boolean {
@@ -82,7 +86,7 @@ export class TextureLineComponent extends React.Component<ITextureLineComponentP
             return;
         }
 
-        const previewCanvas = this.refs.canvas as HTMLCanvasElement;
+        const previewCanvas = this.canvasRef.current as HTMLCanvasElement;
 
         if (this.props.globalState) {
             this.props.globalState.blockMutationUpdates = true;
@@ -208,7 +212,7 @@ export class TextureLineComponent extends React.Component<ITextureLineComponentP
                             <button className={this.state.channel === ChannelToDisplay.All ? "all command selected" : "all command"} onClick={() => this.setState({ channel: ChannelToDisplay.All })}>ALL</button>
                         </div>
                     }
-                    <canvas ref="canvas" className="preview" />
+                    <canvas ref={this.canvasRef} className="preview" />
                 </div>
                 {
                     texture.isRenderTarget &&

+ 10 - 0
inspector/src/components/actionTabs/tabs/propertyGridTabComponent.tsx

@@ -89,6 +89,8 @@ import { NodeMaterial } from 'babylonjs/Materials/Node/nodeMaterial';
 import { NodeMaterialPropertyGridComponent } from './propertyGrids/materials/nodeMaterialPropertyGridComponent';
 import { MultiMaterial } from 'babylonjs/Materials/multiMaterial';
 import { MultiMaterialPropertyGridComponent } from './propertyGrids/materials/multiMaterialPropertyGridComponent';
+import { ParticleSystemPropertyGridComponent } from './propertyGrids/particleSystems/particleSystemPropertyGridComponent';
+import { IParticleSystem } from 'babylonjs/Particles/IParticleSystem';
 
 export class PropertyGridTabComponent extends PaneComponent {
     private _timerIntervalId: number;
@@ -149,6 +151,14 @@ export class PropertyGridTabComponent extends PaneComponent {
                 }
             }
 
+            if (className.indexOf("ParticleSystem") !== -1) {
+                const particleSystem = entity as IParticleSystem;
+                return (<ParticleSystemPropertyGridComponent globalState={this.props.globalState} system={particleSystem}
+                    lockObject={this._lockObject}
+                    onSelectionChangedObservable={this.props.onSelectionChangedObservable}
+                    onPropertyChangedObservable={this.props.onPropertyChangedObservable} />);
+            }
+
             if (className.indexOf("FreeCamera") !== -1 || className.indexOf("UniversalCamera") !== -1
             || className.indexOf("WebXRCamera") !== -1  || className.indexOf("DeviceOrientationCamera") !== -1) {
                 const freeCamera = entity as FreeCamera;

+ 5 - 2
inspector/src/components/actionTabs/tabs/propertyGrids/animationGroupPropertyGridComponent.tsx

@@ -25,6 +25,7 @@ export class AnimationGroupGridComponent extends React.Component<IAnimationGroup
     private _onAnimationGroupPlayObserver: Nullable<Observer<AnimationGroup>>;
     private _onAnimationGroupPauseObserver: Nullable<Observer<AnimationGroup>>;
     private _onBeforeRenderObserver: Nullable<Observer<Scene>>;
+    private timelineRef: React.RefObject<SliderLineComponent>;
 
     constructor(props: IAnimationGroupGridComponentProps) {
         super(props);
@@ -36,7 +37,9 @@ export class AnimationGroupGridComponent extends React.Component<IAnimationGroup
 
         this._onBeforeRenderObserver = this.props.scene.onBeforeRenderObservable.add(() => {
             this.updateCurrentFrame(this.props.animationGroup);
-        });        
+        });
+
+        this.timelineRef = React.createRef();
     }
 
     disconnect(animationGroup: AnimationGroup) {
@@ -130,7 +133,7 @@ export class AnimationGroupGridComponent extends React.Component<IAnimationGroup
                 <LineContainerComponent globalState={this.props.globalState} title="CONTROLS">
                     <ButtonLineComponent label={playButtonText} onClick={() => this.playOrPause()} />
                     <SliderLineComponent label="Speed ratio" minimum={0} maximum={10} step={0.1} target={animationGroup} propertyName="speedRatio" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                    <SliderLineComponent ref="timeline" label="Current frame" minimum={animationGroup.from} maximum={animationGroup.to} step={(animationGroup.to - animationGroup.from) / 1000.0} directValue={this.state.currentFrame} onInput={value => this.onCurrentFrameChange(value)} />
+                    <SliderLineComponent ref={this.timelineRef} label="Current frame" minimum={animationGroup.from} maximum={animationGroup.to} step={(animationGroup.to - animationGroup.from) / 1000.0} directValue={this.state.currentFrame} onInput={value => this.onCurrentFrameChange(value)} />
                 </LineContainerComponent>
                 <LineContainerComponent globalState={this.props.globalState} title="INFOS">
                     <TextLineComponent label="Animation count" value={animationGroup.targetedAnimations.length.toString()} />

+ 5 - 1
inspector/src/components/actionTabs/tabs/propertyGrids/animationPropertyGridComponent.tsx

@@ -38,6 +38,8 @@ export class AnimationGridComponent extends React.Component<IAnimationGridCompon
     private _runningAnimatable: Nullable<Animatable>;
     private _onBeforeRenderObserver: Nullable<Observer<Scene>>;
     private _isPlaying = false;
+    private timelineRef: React.RefObject<SliderLineComponent>;
+
 
     constructor(props: IAnimationGridComponentProps) {
         super(props);
@@ -74,6 +76,8 @@ export class AnimationGridComponent extends React.Component<IAnimationGridCompon
                 });
             }
         }
+
+        this.timelineRef = React.createRef();
     }
 
     playOrPause() {
@@ -182,7 +186,7 @@ export class AnimationGridComponent extends React.Component<IAnimationGridCompon
                         <ButtonLineComponent label={this._isPlaying ? "Stop" : "Play"} onClick={() => this.playOrPause()} />
                         {
                             this._isPlaying &&
-                            <SliderLineComponent ref="timeline" label="Current frame" minimum={this._animationControl.from} maximum={this._animationControl.to}
+                            <SliderLineComponent ref={this.timelineRef} label="Current frame" minimum={this._animationControl.from} maximum={this._animationControl.to}
                                 step={(this._animationControl.to - this._animationControl.from) / 1000.0} directValue={this.state.currentFrame}
                                 onInput={value => this.onCurrentFrameChange(value)}
                             />

+ 6 - 2
inspector/src/components/actionTabs/tabs/propertyGrids/materials/texturePropertyGridComponent.tsx

@@ -36,12 +36,16 @@ interface ITexturePropertyGridComponentProps {
 export class TexturePropertyGridComponent extends React.Component<ITexturePropertyGridComponentProps> {
 
     private _adtInstrumentation: Nullable<AdvancedDynamicTextureInstrumentation>;
+    private textureLineRef: React.RefObject<TextureLineComponent>;
+    
 
     constructor(props: ITexturePropertyGridComponentProps) {
         super(props);
 
         const texture = this.props.texture;
 
+        this.textureLineRef = React.createRef();
+
         if (!texture || !(texture as any).rootContainer) {
             return;
         }
@@ -84,7 +88,7 @@ export class TexturePropertyGridComponent extends React.Component<ITextureProper
 
     foreceRefresh() {
         this.forceUpdate();
-        (this.refs["textureLine"] as TextureLineComponent).updatePreview();
+        (this.textureLineRef.current as TextureLineComponent).updatePreview();
     }
 
     render() {
@@ -128,7 +132,7 @@ export class TexturePropertyGridComponent extends React.Component<ITextureProper
         return (
             <div className="pane">
                 <LineContainerComponent globalState={this.props.globalState} title="PREVIEW">
-                    <TextureLineComponent ref="textureLine" texture={texture} width={256} height={256} globalState={this.props.globalState} />
+                    <TextureLineComponent ref={this.textureLineRef} texture={texture} width={256} height={256} globalState={this.props.globalState} />
                     <FileButtonLineComponent label="Load texture from file" onClick={(file) => this.updateTexture(file)} accept=".jpg, .png, .tga, .dds, .env" />
                     <TextInputLineComponent label="URL" value={textureUrl} lockObject={this.props.lockObject} onChange={url => {
                         (texture as Texture).updateURL(url);

+ 175 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/particleSystems/particleSystemPropertyGridComponent.tsx

@@ -0,0 +1,175 @@
+import * as React from "react";
+
+import { Observable } from "babylonjs/Misc/observable";
+
+import { PropertyChangedEvent } from "../../../../propertyChangedEvent";
+import { LineContainerComponent } from "../../../lineContainerComponent";
+import { TextLineComponent } from "../../../lines/textLineComponent";
+import { LockObject } from "../lockObject";
+import { GlobalState } from '../../../../globalState';
+import { CustomPropertyGridComponent } from '../customPropertyGridComponent';
+import { IParticleSystem } from 'babylonjs/Particles/IParticleSystem';
+import { FloatLineComponent } from '../../../lines/floatLineComponent';
+import { ButtonLineComponent } from '../../../lines/buttonLineComponent';
+import { TextureLinkLineComponent } from '../../../lines/textureLinkLineComponent';
+import { OptionsLineComponent } from '../../../lines/optionsLineComponent';
+import { ParticleSystem } from 'babylonjs/Particles/particleSystem';
+import { Color4LineComponent } from '../../../lines/color4LineComponent';
+import { Vector3LineComponent } from '../../../lines/vector3LineComponent';
+import { CheckBoxLineComponent } from '../../../lines/checkBoxLineComponent';
+import { SliderLineComponent } from '../../../lines/sliderLineComponent';
+import { BoxParticleEmitter } from 'babylonjs/Particles/EmitterTypes/boxParticleEmitter';
+import { ConeParticleEmitter } from 'babylonjs/Particles/EmitterTypes/coneParticleEmitter';
+import { CylinderParticleEmitter } from 'babylonjs/Particles/EmitterTypes/cylinderParticleEmitter';
+import { HemisphericParticleEmitter } from 'babylonjs/Particles/EmitterTypes/hemisphericParticleEmitter';
+import { PointParticleEmitter } from 'babylonjs/Particles/EmitterTypes/pointParticleEmitter';
+import { SphereParticleEmitter } from 'babylonjs/Particles/EmitterTypes/sphereParticleEmitter';
+
+interface IParticleSystemPropertyGridComponentProps {
+    globalState: GlobalState;
+    system: IParticleSystem,
+    lockObject: LockObject,
+    onSelectionChangedObservable?: Observable<any>,
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>
+}
+
+export class ParticleSystemPropertyGridComponent extends React.Component<IParticleSystemPropertyGridComponentProps> {
+    constructor(props: IParticleSystemPropertyGridComponentProps) {
+        super(props);
+    }
+
+    render() {
+        const system = this.props.system;
+
+        var blendModeOptions = [
+            { label: "Add", value: ParticleSystem.BLENDMODE_ADD },
+            { label: "Multiply", value: ParticleSystem.BLENDMODE_MULTIPLY },
+            { label: "Multiply Add", value: ParticleSystem.BLENDMODE_MULTIPLYADD },
+            { label: "OneOne", value: ParticleSystem.BLENDMODE_ONEONE },
+            { label: "Standard", value: ParticleSystem.BLENDMODE_STANDARD },
+        ];   
+        
+        var particleEmitterTypeOptions = [
+            { label: "Box", value: 0 },
+            { label: "Cone", value: 1 },
+            { label: "Cylinder", value: 2 },
+            { label: "Hemispheric", value: 3 },
+            { label: "Point", value: 4 },
+            { label: "Sphere", value: 5 },
+        ];
+
+        return (
+            <div className="pane">
+                <CustomPropertyGridComponent globalState={this.props.globalState} target={system}
+                    lockObject={this.props.lockObject}
+                    onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                <LineContainerComponent globalState={this.props.globalState} title="GENERAL">
+                    <TextLineComponent label="ID" value={system.id} />
+                    <TextLineComponent label="Class" value={system.getClassName()} />  
+                    <TextureLinkLineComponent label="Texture" texture={system.particleTexture} onSelectionChangedObservable={this.props.onSelectionChangedObservable}/>
+                    <OptionsLineComponent label="Blend mode" options={blendModeOptions} target={system} propertyName="blendMode" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <Vector3LineComponent label="Gravity" target={system} propertyName="gravity"
+                        onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <CheckBoxLineComponent label="Is billboard" target={system} propertyName="isBillboardBased" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <CheckBoxLineComponent label="Is local" target={system} propertyName="isLocal" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <SliderLineComponent label="Update speed" target={system} propertyName="updateSpeed" minimum={0} maximum={1} step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                </LineContainerComponent>                      
+                <LineContainerComponent globalState={this.props.globalState} title="OPTIONS">
+                    <ButtonLineComponent label={system.isStarted() ? "Stop" : "Start"} onClick={() => {
+                        if (system.isStarted()) {
+                            system.stop();
+                        } else {
+                            system.start();
+                        }
+                    }} />
+                </LineContainerComponent>
+                <LineContainerComponent globalState={this.props.globalState} title="Emitter">
+                    <OptionsLineComponent 
+                        label="Type" 
+                        options={particleEmitterTypeOptions} 
+                        target={system}
+                        propertyName="particleEmitterType"
+                        noDirectUpdate={true}
+                        onSelect={(value: number) => {
+                            switch(value) {
+                                case 0:
+                                    system.particleEmitterType = new BoxParticleEmitter();
+                                    break;
+                                    
+                                case 1:
+                                    system.particleEmitterType = new ConeParticleEmitter();
+                                    break;
+                                    
+                                case 2:
+                                    system.particleEmitterType = new CylinderParticleEmitter();
+                                    break;
+                                    
+                                case 3:
+                                    system.particleEmitterType = new HemisphericParticleEmitter();
+                                    break;
+                                    
+                                case 4:
+                                    system.particleEmitterType = new PointParticleEmitter();
+                                    break;   
+
+                                case 5:
+                                    system.particleEmitterType = new SphereParticleEmitter();
+                                    break;
+                            }
+                        }}
+                        extractValue={() => {
+                            switch(system.particleEmitterType?.getClassName()) {
+                                case "BoxParticleEmitter":
+                                    return 0;
+                                case "ConeParticleEmitter":
+                                    return 1;
+                                case "CylinderParticleEmitter":
+                                    return 2;        
+                                case "HemisphericParticleEmitter":
+                                    return 3;
+                                case "PointParticleEmitter":
+                                    return 4;
+                                case "SphereParticleEmitter":
+                                    return 5;                                                                                                          
+                            }
+
+                            return 0;
+                        }}
+                        onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+
+                </LineContainerComponent>
+                <LineContainerComponent globalState={this.props.globalState} title="SIZE">
+                    <FloatLineComponent lockObject={this.props.lockObject} label="Min size" target={system} propertyName="minSize" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <FloatLineComponent lockObject={this.props.lockObject} label="Max size" target={system} propertyName="maxSize" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <FloatLineComponent lockObject={this.props.lockObject} label="Min scale X" target={system} propertyName="minScaleX" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <FloatLineComponent lockObject={this.props.lockObject} label="Max scale X" target={system} propertyName="maxScaleX" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <FloatLineComponent lockObject={this.props.lockObject} label="Min scale Y" target={system} propertyName="minScaleY" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <FloatLineComponent lockObject={this.props.lockObject} label="Max scale Y" target={system} propertyName="maxScaleY" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                </LineContainerComponent>          
+                <LineContainerComponent globalState={this.props.globalState} title="LIFETIME">
+                    <FloatLineComponent lockObject={this.props.lockObject} label="Min lifetime" target={system} propertyName="minLifeTime" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <FloatLineComponent lockObject={this.props.lockObject} label="Max lifetime" target={system} propertyName="maxLifeTime" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                </LineContainerComponent>           
+                <LineContainerComponent globalState={this.props.globalState} title="EMISSION">
+                    <FloatLineComponent lockObject={this.props.lockObject} label="Rate" target={system} propertyName="emitRate" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <FloatLineComponent lockObject={this.props.lockObject} label="Min emit power" target={system} propertyName="minEmitPower" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <FloatLineComponent lockObject={this.props.lockObject} label="Max emit power" target={system} propertyName="maxEmitPower" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                </LineContainerComponent>  
+                <LineContainerComponent globalState={this.props.globalState} title="COLORS">
+                    <Color4LineComponent label="Color 1" target={system} propertyName="color1" 
+                        onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <Color4LineComponent label="Color 2" target={system} propertyName="color2" 
+                        onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <Color4LineComponent label="Color dead" target={system} propertyName="colorDead" 
+                        onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                </LineContainerComponent>                     
+                <LineContainerComponent globalState={this.props.globalState} title="ROTATION">
+                    <FloatLineComponent lockObject={this.props.lockObject} label="Min angular speed" target={system} propertyName="minAngularSpeed" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <FloatLineComponent lockObject={this.props.lockObject} label="Max angular speed" target={system} propertyName="maxAngularSpeed" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <FloatLineComponent lockObject={this.props.lockObject} label="Min initial rotation" target={system} propertyName="minInitialRotation" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <FloatLineComponent lockObject={this.props.lockObject} label="Max initial rotation" target={system} propertyName="maxInitialRotation" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                </LineContainerComponent>  
+            </div>
+        );
+    }
+}

+ 15 - 8
inspector/src/components/embedHost/embedHostComponent.tsx

@@ -25,23 +25,30 @@ interface IEmbedHostComponentProps {
 
 export class EmbedHostComponent extends React.Component<IEmbedHostComponentProps> {
     private _once = true;
+    private splitRef: React.RefObject<HTMLDivElement>;
+    private topPartRef: React.RefObject<HTMLDivElement>;
+    private bottomPartRef: React.RefObject<HTMLDivElement>;
 
     constructor(props: IEmbedHostComponentProps) {
-        super(props);
+        super(props);        
+
+        this.splitRef = React.createRef();
+        this.topPartRef = React.createRef();
+        this.bottomPartRef = React.createRef();
     }
 
     componentDidMount() {
-        const container = this.refs.split;
+        const container = this.splitRef.current;
 
         if (!container) {
             return;
         }
 
-        Split([this.refs.topPart, this.refs.bottomPart], {
+        Split([this.topPartRef.current, this.bottomPartRef.current], {
             direction: "vertical",
             minSize: [200, 200],
             gutterSize: 4
-        })
+        });
     }
 
     renderContent() {
@@ -66,15 +73,15 @@ export class EmbedHostComponent extends React.Component<IEmbedHostComponentProps
         }
 
         return (
-            <div ref="split" id="split" className="noPopup">
-                <div id="topPart" ref="topPart">
+            <div ref={this.splitRef} id="split" className="noPopup">
+                <div id="topPart" ref={this.topPartRef}>
                     <SceneExplorerComponent scene={this.props.scene}
                         extensibilityGroups={this.props.extensibilityGroups}
                         globalState={this.props.globalState}
                         popupMode={true}
                         noHeader={true} />
                 </div>
-                <div id="bottomPart" ref="bottomPart" style={{ marginTop: "4px", overflow: "hidden" }}>
+                <div id="bottomPart" ref={this.bottomPartRef} style={{ marginTop: "4px", overflow: "hidden" }}>
                     <ActionTabsComponent scene={this.props.scene}
                         globalState={this.props.globalState}
                         popupMode={true}
@@ -82,7 +89,7 @@ export class EmbedHostComponent extends React.Component<IEmbedHostComponentProps
                         initialTab={this.props.initialTab} />
                 </div>
             </div>
-        )
+        );
     }
 
     render() {

+ 30 - 0
inspector/src/components/sceneExplorer/entities/particleSystemTreeItemComponent.tsx

@@ -0,0 +1,30 @@
+import { IExplorerExtensibilityGroup } from "babylonjs/Debug/debugLayer";
+
+import { faBraille } from '@fortawesome/free-solid-svg-icons';
+import { TreeItemLabelComponent } from "../treeItemLabelComponent";
+import { ExtensionsComponent } from "../extensionsComponent";
+import * as React from 'react';
+import { IParticleSystem } from 'babylonjs/Particles/IParticleSystem';
+
+interface IParticleSystemTreeItemComponentProps {
+    system: IParticleSystem,
+    extensibilityGroups?: IExplorerExtensibilityGroup[],
+    onClick: () => void
+}
+
+export class ParticleSystemTreeItemComponent extends React.Component<IParticleSystemTreeItemComponentProps> {
+    constructor(props: IParticleSystemTreeItemComponentProps) {
+        super(props);
+    }
+
+    render() {
+        return (
+            <div className="particleSystemTools">
+                <TreeItemLabelComponent label={this.props.system.name || "Particle system"} onClick={() => this.props.onClick()} icon={faBraille} color="crimson" />
+                {
+                    <ExtensionsComponent target={this.props.system} extensibilityGroups={this.props.extensibilityGroups} />
+                }
+            </div>
+        )
+    }
+}

+ 3 - 1
inspector/src/components/sceneExplorer/extensionsComponent.tsx

@@ -11,11 +11,13 @@ interface IExtensionsComponentProps {
 
 export class ExtensionsComponent extends React.Component<IExtensionsComponentProps, { popupVisible: boolean }> {
     private _popup: Nullable<HTMLDivElement>;
+    private extensionRef: React.RefObject<HTMLDivElement>;
 
     constructor(props: IExtensionsComponentProps) {
         super(props);
 
         this.state = { popupVisible: false };
+        this.extensionRef = React.createRef();
     }
 
     showPopup() {
@@ -54,7 +56,7 @@ export class ExtensionsComponent extends React.Component<IExtensionsComponentPro
         }
 
         return (
-            <div ref="extensions" className="extensions" onClick={() => this.showPopup()}>
+            <div ref={this.extensionRef} className="extensions" onClick={() => this.showPopup()}>
                 <div title="Additional options" className="icon">
                     <FontAwesomeIcon icon={faEllipsisH} />
                 </div>

+ 12 - 0
inspector/src/components/sceneExplorer/sceneExplorer.scss

@@ -429,6 +429,18 @@
             }
         }
 
+        .particleSystemTools {
+            grid-column: 2;
+            display: grid;
+            grid-template-columns: 1fr auto 5px;
+            align-items: center;
+
+            .extensions {
+                width: 20px;
+                grid-column: 2;
+            }
+        }
+
         .postProcessTools {
             grid-column: 2;
             display: grid;

+ 21 - 2
inspector/src/components/sceneExplorer/sceneExplorerComponent.tsx

@@ -19,6 +19,7 @@ import { FreeCamera } from 'babylonjs/Cameras/freeCamera';
 import { DirectionalLight } from 'babylonjs/Lights/directionalLight';
 import { SSAORenderingPipeline } from 'babylonjs/PostProcesses/RenderPipeline/Pipelines/ssaoRenderingPipeline';
 import { NodeMaterial } from 'babylonjs/Materials/Node/nodeMaterial';
+import { ParticleHelper } from 'babylonjs/Particles/particleHelper';
 
 require("./sceneExplorer.scss");
 
@@ -57,6 +58,8 @@ interface ISceneExplorerComponentProps {
 export class SceneExplorerComponent extends React.Component<ISceneExplorerComponentProps, { filter: Nullable<string>, selectedEntity: any, scene: Scene }> {
     private _onSelectionChangeObserver: Nullable<Observer<any>>;
     private _onNewSceneAddedObserver: Nullable<Observer<Scene>>;
+    private sceneExplorerRef: React.RefObject<Resizable>;
+
 
     private _once = true;
     private _hooked = false;
@@ -69,6 +72,8 @@ export class SceneExplorerComponent extends React.Component<ISceneExplorerCompon
         this.state = { filter: null, selectedEntity: null, scene: this.props.scene };
 
         this.sceneMutationFunc = this.processMutation.bind(this);
+
+        this.sceneExplorerRef = React.createRef();
     }
 
     processMutation() {
@@ -293,7 +298,7 @@ export class SceneExplorerComponent extends React.Component<ISceneExplorerCompon
             }
         });
 
-        
+        // Materials
         let materialsContextMenus: { label: string, action: () => void }[] = [];
         materialsContextMenus.push({
             label: "Add new node material",
@@ -313,6 +318,17 @@ export class SceneExplorerComponent extends React.Component<ISceneExplorerCompon
             materials.push(...scene.multiMaterials);
         }
 
+        // Particle systems
+        let particleSystemsContextMenus: { label: string, action: () => void }[] = [];
+        particleSystemsContextMenus.push({
+            label: "Add new particle system",
+            action: () => {
+                let newSystem = ParticleHelper.CreateDefault(Vector3.Zero(), 1000, scene);
+                newSystem.start();
+                this.props.globalState.onSelectionChangedObservable.notifyObservers(newSystem);
+            }
+        });
+
         return (
             <div id="tree" onContextMenu={e => e.preventDefault()}>
                 <SceneExplorerFilterComponent onFilter={(filter) => this.filterContent(filter)} />
@@ -336,6 +352,9 @@ export class SceneExplorerComponent extends React.Component<ISceneExplorerCompon
                 <TreeItemComponent globalState={this.props.globalState} extensibilityGroups={this.props.extensibilityGroups}
                     contextMenuItems={pipelineContextMenus}
                     selectedEntity={this.state.selectedEntity} items={pipelines} label="Rendering pipelines" offset={1} filter={this.state.filter} />
+                <TreeItemComponent globalState={this.props.globalState} 
+                    contextMenuItems={particleSystemsContextMenus} 
+                    extensibilityGroups={this.props.extensibilityGroups} selectedEntity={this.state.selectedEntity} items={scene.particleSystems} label="Particle systems" offset={1} filter={this.state.filter} />
                 {
                     guiElements && guiElements.length > 0 &&
                     <TreeItemComponent globalState={this.props.globalState} extensibilityGroups={this.props.extensibilityGroups} selectedEntity={this.state.selectedEntity} items={guiElements} label="GUI" offset={1} filter={this.state.filter} />
@@ -390,7 +409,7 @@ export class SceneExplorerComponent extends React.Component<ISceneExplorerCompon
         }
 
         return (
-            <Resizable tabIndex={-1} id="sceneExplorer" ref="sceneExplorer" size={{ height: "100%" }} minWidth={300} maxWidth={600} minHeight="100%" enable={{ top: false, right: true, bottom: false, left: false, topRight: false, bottomRight: false, bottomLeft: false, topLeft: false }} onKeyDown={(keyEvent) => this.processKeys(keyEvent)}>
+            <Resizable tabIndex={-1} id="sceneExplorer" ref={this.sceneExplorerRef} size={{ height: "100%" }} minWidth={300} maxWidth={600} minHeight="100%" enable={{ top: false, right: true, bottom: false, left: false, topRight: false, bottomRight: false, bottomLeft: false, topLeft: false }} onKeyDown={(keyEvent) => this.processKeys(keyEvent)}>
                 {
                     !this.props.noHeader &&
                     <HeaderComponent title="SCENE EXPLORER" noClose={this.props.noClose} noExpand={this.props.noExpand} noCommands={this.props.noCommands} onClose={() => this.onClose()} onPopup={() => this.onPopup()} />

+ 6 - 0
inspector/src/components/sceneExplorer/treeItemSpecializedComponent.tsx

@@ -30,6 +30,8 @@ import { SkeletonTreeItemComponent } from './entities/skeletonTreeItemComponent'
 import { Skeleton } from 'babylonjs/Bones/skeleton';
 import { BoneTreeItemComponent } from './entities/boneTreeItemComponent';
 import { Bone } from 'babylonjs/Bones/bone';
+import { ParticleSystemTreeItemComponent } from './entities/particleSystemTreeItemComponent';
+import { IParticleSystem } from 'babylonjs/Particles/IParticleSystem';
 
 
 interface ITreeItemSpecializedComponentProps {
@@ -92,6 +94,10 @@ export class TreeItemSpecializedComponent extends React.Component<ITreeItemSpeci
                 return (<MaterialTreeItemComponent extensibilityGroups={this.props.extensibilityGroups} material={entity as Material} onClick={() => this.onClick()} />);
             }
 
+            if (className.indexOf("ParticleSystem") !== -1) {
+                return (<ParticleSystemTreeItemComponent extensibilityGroups={this.props.extensibilityGroups} system={entity as IParticleSystem} onClick={() => this.onClick()} />);
+            }
+
             if (className === "AdvancedDynamicTexture") {
                 return (<AdvancedDynamicTextureTreeItemComponent onSelectionChangedObservable={this.props.globalState.onSelectionChangedObservable} extensibilityGroups={this.props.extensibilityGroups} texture={entity as AdvancedDynamicTexture} onClick={() => this.onClick()} />);
             }

+ 4 - 2
nodeEditor/src/components/preview/previewMeshControlComponent.tsx

@@ -19,10 +19,12 @@ interface IPreviewMeshControlComponent {
 
 export class PreviewMeshControlComponent extends React.Component<IPreviewMeshControlComponent> {
     private colorInputRef: React.RefObject<HTMLInputElement>;
+    private filePickerRef: React.RefObject<HTMLInputElement>;
 
     constructor(props: IPreviewMeshControlComponent) {
         super(props);
         this.colorInputRef = React.createRef();
+        this.filePickerRef = React.createRef();
     }
 
     changeMeshType(newOne: PreviewMeshType) {
@@ -103,13 +105,13 @@ export class PreviewMeshControlComponent extends React.Component<IPreviewMeshCon
                                 if (value !== PreviewMeshType.Custom + 1) {
                                     this.changeMeshType(value);
                                 } else {
-                                    (ReactDOM.findDOMNode(this.refs["file-picker"]) as HTMLElement).click();
+                                    this.filePickerRef.current?.click();
                                 }
                             }} />
                 <div style={{
                     display: "none"
                 }} title="Preview with a custom mesh" >
-                    <input ref="file-picker" id="file-picker" type="file" onChange={evt => this.useCustomMesh(evt)} accept=".gltf, .glb, .babylon, .obj"/>
+                    <input ref={this.filePickerRef} id="file-picker" type="file" onChange={evt => this.useCustomMesh(evt)} accept=".gltf, .glb, .babylon, .obj"/>
                 </div>
                 <div
                     title="Turn-table animation"

+ 5 - 1
nodeEditor/src/sharedComponents/fileButtonLineComponent.tsx

@@ -7,8 +7,12 @@ interface IFileButtonLineComponentProps {
 }
 
 export class FileButtonLineComponent extends React.Component<IFileButtonLineComponentProps> {
+    private uploadRef: React.RefObject<HTMLInputElement>;
+
     constructor(props: IFileButtonLineComponentProps) {
         super(props);
+
+        this.uploadRef = React.createRef();
     }
 
     onChange(evt: any) {
@@ -26,7 +30,7 @@ export class FileButtonLineComponent extends React.Component<IFileButtonLineComp
                 <label htmlFor="file-upload" className="file-upload">
                     {this.props.label}
                 </label>
-                <input ref="upload" id="file-upload" type="file" accept={this.props.accept} onChange={evt => this.onChange(evt)} />
+                <input ref={this.uploadRef} id="file-upload" type="file" accept={this.props.accept} onChange={evt => this.onChange(evt)} />
             </div>
         );
     }

+ 11 - 7
nodeEditor/src/sharedComponents/textureLineComponent.tsx

@@ -16,14 +16,16 @@ interface ITextureLineComponentProps {
 }
 
 export interface ITextureLineComponentState {
-    displayRed: boolean, 
-    displayGreen: boolean, 
-    displayBlue: boolean, 
-    displayAlpha: boolean, 
-    face: number
+    displayRed: boolean;
+    displayGreen: boolean;
+    displayBlue: boolean;
+    displayAlpha: boolean;
+    face: number;
 }
 
 export class TextureLineComponent extends React.Component<ITextureLineComponentProps, ITextureLineComponentState> {
+    private canvasRef: React.RefObject<HTMLCanvasElement>;
+
     constructor(props: ITextureLineComponentProps) {
         super(props);
 
@@ -34,6 +36,8 @@ export class TextureLineComponent extends React.Component<ITextureLineComponentP
             displayAlpha: true,
             face: 0
         };
+
+        this.canvasRef = React.createRef();
     }
 
     shouldComponentUpdate(nextProps: ITextureLineComponentProps, nextState: { displayRed: boolean, displayGreen: boolean, displayBlue: boolean, displayAlpha: boolean, face: number }): boolean {
@@ -49,7 +53,7 @@ export class TextureLineComponent extends React.Component<ITextureLineComponentP
     }
 
     public updatePreview() {
-        TextureLineComponent.UpdatePreview(this.refs.canvas as HTMLCanvasElement, this.props.texture, this.props.width, this.state, undefined, this.props.globalState);
+        TextureLineComponent.UpdatePreview(this.canvasRef.current as HTMLCanvasElement, this.props.texture, this.props.width, this.state, undefined, this.props.globalState);
     }
 
     public static UpdatePreview(previewCanvas: HTMLCanvasElement, texture: BaseTexture, width: number, options: ITextureLineComponentState, onReady?: ()=> void, globalState?: any) {
@@ -206,7 +210,7 @@ export class TextureLineComponent extends React.Component<ITextureLineComponentP
                         <button className={this.state.displayRed && this.state.displayGreen ? "all command selected" : "all command"} onClick={() => this.setState({ displayRed: true, displayGreen: true, displayBlue: true, displayAlpha: true })}>ALL</button>
                     </div>
                 }
-                <canvas ref="canvas" className="preview" />
+                <canvas ref={this.canvasRef} className="preview" />
             </div>
         );
     }

+ 14 - 2
src/Cameras/targetCamera.ts

@@ -53,6 +53,17 @@ export class TargetCamera extends Camera {
     public noRotationConstraint = false;
 
     /**
+     * Reverses mouselook direction to 'natural' panning as opposed to traditional direct
+     * panning
+     */
+    public invertRotation = false;
+
+    /**
+     * Speed multiplier for inverse camera panning
+     */
+    public inverseRotationSpeed = 0.2;
+
+    /**
      * Define the current target of the camera as an object or a position.
      */
     @serializeAsMeshReference("lockedTargetId")
@@ -290,6 +301,7 @@ export class TargetCamera extends Camera {
 
     /** @hidden */
     public _checkInputs(): void {
+        var directionMultiplier = this.invertRotation ? -this.inverseRotationSpeed : 1.0;
         var needToMove = this._decideIfNeedsToMove();
         var needToRotate = Math.abs(this.cameraRotation.x) > 0 || Math.abs(this.cameraRotation.y) > 0;
 
@@ -300,8 +312,8 @@ export class TargetCamera extends Camera {
 
         // Rotate
         if (needToRotate) {
-            this.rotation.x += this.cameraRotation.x;
-            this.rotation.y += this.cameraRotation.y;
+            this.rotation.x += this.cameraRotation.x * directionMultiplier;
+            this.rotation.y += this.cameraRotation.y * directionMultiplier;
 
             //rotate, if quaternion is set and rotation was used
             if (this.rotationQuaternion) {

+ 6 - 0
src/Particles/IParticleSystem.ts

@@ -312,6 +312,12 @@ export interface IParticleSystem {
      */
     isReady(): boolean;
     /**
+     * Returns the string "ParticleSystem"
+     * @returns a string containing the class name
+     */
+    getClassName(): string;
+
+    /**
      * Adds a new color gradient
      * @param gradient defines the gradient to use (between 0 and 1)
      * @param color1 defines the color to affect to the specified gradient

+ 1 - 1
src/Particles/particleSystem.ts

@@ -344,7 +344,7 @@ export class ParticleSystem extends BaseParticleSystem implements IDisposable, I
                     });
                 }
 
-                if (this.isLocal) {
+                if (this.isLocal && particle._localPosition) {
                     particle._localPosition!.addInPlace(this._scaledDirection);
                     Vector3.TransformCoordinatesToRef(particle._localPosition!, this._emitterWorldMatrix, particle.position);
                 } else {

+ 1 - 1
src/Shaders/ShadersInclude/bumpFragmentFunctions.fx

@@ -62,7 +62,7 @@
 	}
 #endif
 
-#if defined(BUMP) || defined(CLEARCOAT_BUMP)
+#if defined(BUMP)
 	vec3 perturbNormal(mat3 cotangentFrame, vec3 color)
 	{
 		return perturbNormal(cotangentFrame, color, vBumpInfos.y);