sceneExplorerComponent.tsx 20 KB

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