sceneExplorerComponent.tsx 23 KB

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