sceneExplorerComponent.tsx 16 KB

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