sceneExplorerComponent.tsx 20 KB

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