sceneExplorerComponent.tsx 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. import * as React from "react";
  2. import { Nullable } from "babylonjs/types";
  3. import { Observer } from "babylonjs/Misc/observable";
  4. import { IExplorerExtensibilityGroup } from "babylonjs/Debug/debugLayer";
  5. import { Scene } from "babylonjs/scene";
  6. import { EngineStore } from "babylonjs/Engines/engineStore";
  7. import { TreeItemComponent } from "./treeItemComponent";
  8. import Resizable from "re-resizable";
  9. import { HeaderComponent } from "../headerComponent";
  10. import { SceneTreeItemComponent } from "./entities/sceneTreeItemComponent";
  11. import { Tools } from "../../tools";
  12. import { GlobalState } from "../../components/globalState";
  13. import { DefaultRenderingPipeline } from 'babylonjs/PostProcesses/RenderPipeline/Pipelines/defaultRenderingPipeline';
  14. import { Vector3 } from 'babylonjs/Maths/math';
  15. import { PointLight } from 'babylonjs/Lights/pointLight';
  16. import { FreeCamera } from 'babylonjs/Cameras/freeCamera';
  17. import { DirectionalLight } from 'babylonjs/Lights/directionalLight';
  18. import { SSAORenderingPipeline } from 'babylonjs/PostProcesses/RenderPipeline/Pipelines/ssaoRenderingPipeline';
  19. import { NodeMaterial } from 'babylonjs/Materials/Node/nodeMaterial';
  20. import { ParticleHelper } from 'babylonjs/Particles/particleHelper';
  21. import { GPUParticleSystem } from 'babylonjs/Particles/gpuParticleSystem';
  22. import { SSAO2RenderingPipeline } from 'babylonjs/PostProcesses/RenderPipeline/Pipelines/ssao2RenderingPipeline';
  23. import { StandardMaterial } from 'babylonjs/Materials/standardMaterial';
  24. import { PBRMaterial } from 'babylonjs/Materials/PBR/pbrMaterial';
  25. require("./sceneExplorer.scss");
  26. interface ISceneExplorerFilterComponentProps {
  27. onFilter: (filter: string) => void;
  28. }
  29. export class SceneExplorerFilterComponent extends React.Component<ISceneExplorerFilterComponentProps> {
  30. constructor(props: ISceneExplorerFilterComponentProps) {
  31. super(props);
  32. }
  33. render() {
  34. return (
  35. <div className="filter">
  36. <input type="text" placeholder="Filter" onChange={(evt) => this.props.onFilter(evt.target.value)} />
  37. </div>
  38. );
  39. }
  40. }
  41. interface ISceneExplorerComponentProps {
  42. scene: Scene;
  43. noCommands?: boolean;
  44. noHeader?: boolean;
  45. noExpand?: boolean;
  46. noClose?: boolean;
  47. extensibilityGroups?: IExplorerExtensibilityGroup[];
  48. globalState: GlobalState;
  49. popupMode?: boolean;
  50. onPopup?: () => void;
  51. onClose?: () => void;
  52. }
  53. export class SceneExplorerComponent extends React.Component<ISceneExplorerComponentProps, { filter: Nullable<string>, selectedEntity: any, scene: Scene }> {
  54. private _onSelectionChangeObserver: Nullable<Observer<any>>;
  55. private _onSelectionRenamedObserver: Nullable<Observer<void>>;
  56. private _onNewSceneAddedObserver: Nullable<Observer<Scene>>;
  57. private _onNewSceneObserver: Nullable<Observer<Scene>>;
  58. private sceneExplorerRef: React.RefObject<Resizable>;
  59. private _once = true;
  60. private _hooked = false;
  61. private sceneMutationFunc: () => void;
  62. constructor(props: ISceneExplorerComponentProps) {
  63. super(props);
  64. this.state = { filter: null, selectedEntity: null, scene: this.props.scene };
  65. this.sceneMutationFunc = this.processMutation.bind(this);
  66. this.sceneExplorerRef = React.createRef();
  67. this._onNewSceneObserver = this.props.globalState.onNewSceneObservable.add((scene: Scene) => {
  68. this.setState({
  69. ...this.state,
  70. scene
  71. });
  72. })
  73. }
  74. processMutation() {
  75. if (this.props.globalState.blockMutationUpdates) {
  76. return;
  77. }
  78. setTimeout(() => this.forceUpdate());
  79. }
  80. componentDidMount() {
  81. this._onSelectionChangeObserver = this.props.globalState.onSelectionChangedObservable.add((entity) => {
  82. if (this.state.selectedEntity !== entity) {
  83. this.setState({ selectedEntity: entity });
  84. }
  85. });
  86. this._onSelectionRenamedObserver = this.props.globalState.onSelectionRenamedObservable.add(() => {
  87. this.forceUpdate();
  88. })
  89. }
  90. componentWillUnmount() {
  91. if (this._onSelectionChangeObserver) {
  92. this.props.globalState.onSelectionChangedObservable.remove(this._onSelectionChangeObserver);
  93. }
  94. if (this._onSelectionRenamedObserver) {
  95. this.props.globalState.onSelectionRenamedObservable.remove(this._onSelectionRenamedObserver);
  96. }
  97. if (this._onNewSceneAddedObserver) {
  98. EngineStore.LastCreatedEngine!.onNewSceneAddedObservable.remove(this._onNewSceneAddedObserver);
  99. }
  100. if(this._onNewSceneObserver){
  101. this.props.globalState.onNewSceneObservable.remove(this._onNewSceneObserver);
  102. }
  103. const scene = this.state.scene;
  104. scene.onNewSkeletonAddedObservable.removeCallback(this.sceneMutationFunc);
  105. scene.onNewCameraAddedObservable.removeCallback(this.sceneMutationFunc);
  106. scene.onNewLightAddedObservable.removeCallback(this.sceneMutationFunc);
  107. scene.onNewMaterialAddedObservable.removeCallback(this.sceneMutationFunc);
  108. scene.onNewMeshAddedObservable.removeCallback(this.sceneMutationFunc);
  109. scene.onNewTextureAddedObservable.removeCallback(this.sceneMutationFunc);
  110. scene.onNewTransformNodeAddedObservable.removeCallback(this.sceneMutationFunc);
  111. scene.onSkeletonRemovedObservable.removeCallback(this.sceneMutationFunc);
  112. scene.onMeshRemovedObservable.removeCallback(this.sceneMutationFunc);
  113. scene.onCameraRemovedObservable.removeCallback(this.sceneMutationFunc);
  114. scene.onLightRemovedObservable.removeCallback(this.sceneMutationFunc);
  115. scene.onMaterialRemovedObservable.removeCallback(this.sceneMutationFunc);
  116. scene.onTransformNodeRemovedObservable.removeCallback(this.sceneMutationFunc);
  117. scene.onTextureRemovedObservable.removeCallback(this.sceneMutationFunc);
  118. }
  119. filterContent(filter: string) {
  120. this.setState({ filter: filter });
  121. }
  122. findSiblings(parent: any, items: any[], target: any, goNext: boolean, data: { previousOne?: any, found?: boolean }): boolean {
  123. if (!items) {
  124. return false;
  125. }
  126. const sortedItems = Tools.SortAndFilter(parent, items);
  127. if (!items || sortedItems.length === 0) {
  128. return false;
  129. }
  130. for (var item of sortedItems) {
  131. if (item === target) { // found the current selection!
  132. data.found = true;
  133. if (!goNext) {
  134. if (data.previousOne) {
  135. this.props.globalState.onSelectionChangedObservable.notifyObservers(data.previousOne);
  136. }
  137. return true;
  138. }
  139. } else {
  140. if (data.found) {
  141. this.props.globalState.onSelectionChangedObservable.notifyObservers(item);
  142. return true;
  143. }
  144. data.previousOne = item;
  145. }
  146. if (item.getChildren && item.reservedDataStore && item.reservedDataStore.isExpanded) {
  147. if (this.findSiblings(item, item.getChildren(), target, goNext, data)) {
  148. return true;
  149. }
  150. }
  151. }
  152. return false;
  153. }
  154. processKeys(keyEvent: React.KeyboardEvent<HTMLDivElement>) {
  155. if (!this.state.selectedEntity) {
  156. return;
  157. }
  158. const scene = this.state.scene;
  159. let search = false;
  160. let goNext = false;
  161. if (keyEvent.keyCode === 38) { // up
  162. search = true;
  163. } else if (keyEvent.keyCode === 40) { // down
  164. goNext = true;
  165. search = true;
  166. } else if (keyEvent.keyCode === 13 || keyEvent.keyCode === 39) { // enter or right
  167. var reservedDataStore = this.state.selectedEntity.reservedDataStore;
  168. if (reservedDataStore && reservedDataStore.setExpandedState) {
  169. reservedDataStore.setExpandedState(true);
  170. }
  171. keyEvent.preventDefault();
  172. return;
  173. } else if (keyEvent.keyCode === 37) { // left
  174. var reservedDataStore = this.state.selectedEntity.reservedDataStore;
  175. if (reservedDataStore && reservedDataStore.setExpandedState) {
  176. reservedDataStore.setExpandedState(false);
  177. }
  178. keyEvent.preventDefault();
  179. return;
  180. }
  181. if (!search) {
  182. return;
  183. }
  184. keyEvent.preventDefault();
  185. let data = {};
  186. if (!this.findSiblings(null, scene.rootNodes, this.state.selectedEntity, goNext, data)) {
  187. if (!this.findSiblings(null, scene.materials, this.state.selectedEntity, goNext, data)) {
  188. this.findSiblings(null, scene.textures, this.state.selectedEntity, goNext, data);
  189. }
  190. }
  191. }
  192. renderContent() {
  193. const scene = this.state.scene;
  194. if (!scene) {
  195. this._onNewSceneAddedObserver = EngineStore.LastCreatedEngine!.onNewSceneAddedObservable.addOnce((scene) => this.setState({ scene: scene }));
  196. return null;
  197. }
  198. if (!this._hooked) {
  199. this._hooked = true;
  200. scene.onNewSkeletonAddedObservable.add(this.sceneMutationFunc);
  201. scene.onNewCameraAddedObservable.add(this.sceneMutationFunc);
  202. scene.onNewLightAddedObservable.add(this.sceneMutationFunc);
  203. scene.onNewMaterialAddedObservable.add(this.sceneMutationFunc);
  204. scene.onNewMeshAddedObservable.add(this.sceneMutationFunc);
  205. scene.onNewTextureAddedObservable.add(this.sceneMutationFunc);
  206. scene.onNewTransformNodeAddedObservable.add(this.sceneMutationFunc);
  207. scene.onSkeletonRemovedObservable.add(this.sceneMutationFunc);
  208. scene.onMeshRemovedObservable.add(this.sceneMutationFunc);
  209. scene.onCameraRemovedObservable.add(this.sceneMutationFunc);
  210. scene.onLightRemovedObservable.add(this.sceneMutationFunc);
  211. scene.onMaterialRemovedObservable.add(this.sceneMutationFunc);
  212. scene.onTransformNodeRemovedObservable.add(this.sceneMutationFunc);
  213. scene.onTextureRemovedObservable.add(this.sceneMutationFunc);
  214. }
  215. let guiElements = scene.textures.filter((t) => t.getClassName() === "AdvancedDynamicTexture");
  216. let textures = scene.textures.filter((t) => t.getClassName() !== "AdvancedDynamicTexture");
  217. let postProcessses = scene.postProcesses;
  218. let pipelines = scene.postProcessRenderPipelineManager.supportedPipelines;
  219. // Context menus
  220. let pipelineContextMenus: { label: string, action: () => void }[] = [];
  221. if (scene.activeCamera) {
  222. if (!pipelines.some(p => p.getClassName() === "DefaultRenderingPipeline")) {
  223. pipelineContextMenus.push({
  224. label: "Add new Default Rendering Pipeline",
  225. action: () => {
  226. let newPipeline = new DefaultRenderingPipeline("Default rendering pipeline", true, scene, [scene.activeCamera!]);
  227. this.props.globalState.onSelectionChangedObservable.notifyObservers(newPipeline);
  228. }
  229. });
  230. }
  231. if (!pipelines.some(p => p.getClassName() === "SSAORenderingPipeline")) {
  232. pipelineContextMenus.push({
  233. label: "Add new SSAO Rendering Pipeline",
  234. action: () => {
  235. let newPipeline = new SSAORenderingPipeline("SSAO rendering pipeline", scene, 1, [scene.activeCamera!]);
  236. this.props.globalState.onSelectionChangedObservable.notifyObservers(newPipeline);
  237. }
  238. });
  239. }
  240. if (scene.getEngine().webGLVersion > 1 && !pipelines.some(p => p.getClassName() === "SSAORenderingPipeline")) {
  241. pipelineContextMenus.push({
  242. label: "Add new SSAO2 Rendering Pipeline",
  243. action: () => {
  244. let newPipeline = new SSAO2RenderingPipeline("SSAO2 rendering pipeline", scene, 1, [scene.activeCamera!]);
  245. this.props.globalState.onSelectionChangedObservable.notifyObservers(newPipeline);
  246. }
  247. });
  248. }
  249. }
  250. let nodeContextMenus: { label: string, action: () => void }[] = [];
  251. nodeContextMenus.push({
  252. label: "Add new point light",
  253. action: () => {
  254. let newPointLight = new PointLight("point light", Vector3.Zero(), scene);
  255. this.props.globalState.onSelectionChangedObservable.notifyObservers(newPointLight);
  256. }
  257. });
  258. nodeContextMenus.push({
  259. label: "Add new directional light",
  260. action: () => {
  261. let newDirectionalLight = new DirectionalLight("directional light", new Vector3(-1, -1, -0.5), scene);
  262. this.props.globalState.onSelectionChangedObservable.notifyObservers(newDirectionalLight);
  263. }
  264. });
  265. nodeContextMenus.push({
  266. label: "Add new free camera",
  267. action: () => {
  268. let newFreeCamera = new FreeCamera("free camera", scene.activeCamera ? scene.activeCamera.globalPosition : new Vector3(0, 0, -5), scene);
  269. this.props.globalState.onSelectionChangedObservable.notifyObservers(newFreeCamera);
  270. }
  271. });
  272. // Materials
  273. let materialsContextMenus: { label: string, action: () => void }[] = [];
  274. materialsContextMenus.push({
  275. label: "Add new standard material",
  276. action: () => {
  277. let newStdMaterial = new StandardMaterial("Standard material", scene);
  278. this.props.globalState.onSelectionChangedObservable.notifyObservers(newStdMaterial);
  279. }
  280. });
  281. materialsContextMenus.push({
  282. label: "Add new PBR material",
  283. action: () => {
  284. let newPBRMaterial = new PBRMaterial("PBR material", scene);
  285. this.props.globalState.onSelectionChangedObservable.notifyObservers(newPBRMaterial);
  286. }
  287. });
  288. materialsContextMenus.push({
  289. label: "Add new node material",
  290. action: () => {
  291. let newNodeMaterial = new NodeMaterial("node material", scene);
  292. newNodeMaterial.setToDefault();
  293. newNodeMaterial.build();
  294. this.props.globalState.onSelectionChangedObservable.notifyObservers(newNodeMaterial);
  295. }
  296. });
  297. let materials = [];
  298. materials.push(...scene.materials);
  299. if (scene.multiMaterials && scene.multiMaterials.length) {
  300. materials.push(...scene.multiMaterials);
  301. }
  302. // Particle systems
  303. let particleSystemsContextMenus: { label: string, action: () => void }[] = [];
  304. particleSystemsContextMenus.push({
  305. label: "Add new CPU particle system",
  306. action: () => {
  307. let newSystem = ParticleHelper.CreateDefault(Vector3.Zero(), 10000, scene);
  308. newSystem.name = "CPU particle system";
  309. newSystem.start();
  310. this.props.globalState.onSelectionChangedObservable.notifyObservers(newSystem);
  311. }
  312. });
  313. if (GPUParticleSystem.IsSupported) {
  314. particleSystemsContextMenus.push({
  315. label: "Add new GPU particle system",
  316. action: () => {
  317. let newSystem = ParticleHelper.CreateDefault(Vector3.Zero(), 10000, scene, true);
  318. newSystem.name = "GPU particle system";
  319. newSystem.start();
  320. this.props.globalState.onSelectionChangedObservable.notifyObservers(newSystem);
  321. }
  322. });
  323. }
  324. return (
  325. <div id="tree" onContextMenu={e => e.preventDefault()}>
  326. <SceneExplorerFilterComponent onFilter={(filter) => this.filterContent(filter)} />
  327. <SceneTreeItemComponent globalState={this.props.globalState}
  328. extensibilityGroups={this.props.extensibilityGroups} selectedEntity={this.state.selectedEntity} scene={scene} onRefresh={() => this.forceUpdate()} onSelectionChangedObservable={this.props.globalState.onSelectionChangedObservable} />
  329. <TreeItemComponent globalState={this.props.globalState}
  330. contextMenuItems={nodeContextMenus}
  331. extensibilityGroups={this.props.extensibilityGroups} selectedEntity={this.state.selectedEntity} items={scene.rootNodes} label="Nodes" offset={1} filter={this.state.filter} />
  332. {
  333. scene.skeletons.length > 0 &&
  334. <TreeItemComponent globalState={this.props.globalState} extensibilityGroups={this.props.extensibilityGroups} selectedEntity={this.state.selectedEntity} items={scene.skeletons} label="Skeletons" offset={1} filter={this.state.filter} />
  335. }
  336. <TreeItemComponent globalState={this.props.globalState} extensibilityGroups={this.props.extensibilityGroups} selectedEntity={this.state.selectedEntity} items={materials}
  337. contextMenuItems={materialsContextMenus}
  338. label="Materials" offset={1} filter={this.state.filter} />
  339. <TreeItemComponent globalState={this.props.globalState} extensibilityGroups={this.props.extensibilityGroups} selectedEntity={this.state.selectedEntity} items={textures} label="Textures" offset={1} filter={this.state.filter} />
  340. {
  341. postProcessses.length > 0 &&
  342. <TreeItemComponent globalState={this.props.globalState} extensibilityGroups={this.props.extensibilityGroups} selectedEntity={this.state.selectedEntity} items={postProcessses} label="Post-processes" offset={1} filter={this.state.filter} />
  343. }
  344. <TreeItemComponent globalState={this.props.globalState} extensibilityGroups={this.props.extensibilityGroups}
  345. contextMenuItems={pipelineContextMenus}
  346. selectedEntity={this.state.selectedEntity} items={pipelines} label="Rendering pipelines" offset={1} filter={this.state.filter} />
  347. <TreeItemComponent globalState={this.props.globalState}
  348. contextMenuItems={particleSystemsContextMenus}
  349. extensibilityGroups={this.props.extensibilityGroups} selectedEntity={this.state.selectedEntity} items={scene.particleSystems} label="Particle systems" offset={1} filter={this.state.filter} />
  350. {
  351. guiElements && guiElements.length > 0 &&
  352. <TreeItemComponent globalState={this.props.globalState} extensibilityGroups={this.props.extensibilityGroups} selectedEntity={this.state.selectedEntity} items={guiElements} label="GUI" offset={1} filter={this.state.filter} />
  353. }
  354. {
  355. scene.animationGroups.length > 0 &&
  356. <TreeItemComponent globalState={this.props.globalState} extensibilityGroups={this.props.extensibilityGroups} selectedEntity={this.state.selectedEntity} items={scene.animationGroups} label="Animation groups" offset={1} filter={this.state.filter} />
  357. }
  358. </div>
  359. );
  360. }
  361. onClose() {
  362. if (!this.props.onClose) {
  363. return;
  364. }
  365. this.props.onClose();
  366. }
  367. onPopup() {
  368. if (!this.props.onPopup) {
  369. return;
  370. }
  371. this.props.onPopup();
  372. }
  373. render() {
  374. if (this.props.popupMode) {
  375. return (
  376. <div id="sceneExplorer">
  377. {
  378. !this.props.noHeader &&
  379. <HeaderComponent title="SCENE EXPLORER" noClose={this.props.noClose} noExpand={this.props.noExpand} noCommands={this.props.noCommands} onClose={() => this.onClose()} onPopup={() => this.onPopup()} />
  380. }
  381. {this.renderContent()}
  382. </div>
  383. );
  384. }
  385. if (this._once) {
  386. this._once = false;
  387. // A bit hacky but no other way to force the initial width to 300px and not auto
  388. setTimeout(() => {
  389. const element = document.getElementById("sceneExplorer");
  390. if (!element) {
  391. return;
  392. }
  393. element.style.width = "300px";
  394. }, 150);
  395. }
  396. return (
  397. <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)}>
  398. {
  399. !this.props.noHeader &&
  400. <HeaderComponent title="SCENE EXPLORER" noClose={this.props.noClose} noExpand={this.props.noExpand} noCommands={this.props.noCommands} onClose={() => this.onClose()} onPopup={() => this.onPopup()} />
  401. }
  402. {this.renderContent()}
  403. </Resizable>
  404. );
  405. }
  406. }