import * as React from "react"; import { Nullable } from "babylonjs/types"; import { Observer } from "babylonjs/Misc/observable"; import { IExplorerExtensibilityGroup } from "babylonjs/Debug/debugLayer"; import { Scene } from "babylonjs/scene"; import { EngineStore } from "babylonjs/Engines/engineStore"; import { TreeItemComponent } from "./treeItemComponent"; import Resizable from "re-resizable"; import { HeaderComponent } from "../headerComponent"; import { SceneTreeItemComponent } from "./entities/sceneTreeItemComponent"; import { Tools } from "../../tools"; import { GlobalState } from "../../components/globalState"; import { DefaultRenderingPipeline } from 'babylonjs/PostProcesses/RenderPipeline/Pipelines/defaultRenderingPipeline'; import { Vector3 } from 'babylonjs/Maths/math'; import { PointLight } from 'babylonjs/Lights/pointLight'; import { FreeCamera } from 'babylonjs/Cameras/freeCamera'; import { DirectionalLight } from 'babylonjs/Lights/directionalLight'; import { SSAORenderingPipeline } from 'babylonjs/PostProcesses/RenderPipeline/Pipelines/ssaoRenderingPipeline'; import { NodeMaterial } from 'babylonjs/Materials/Node/nodeMaterial'; import { ParticleHelper } from 'babylonjs/Particles/particleHelper'; import { GPUParticleSystem } from 'babylonjs/Particles/gpuParticleSystem'; import { SSAO2RenderingPipeline } from 'babylonjs/PostProcesses/RenderPipeline/Pipelines/ssao2RenderingPipeline'; import { StandardMaterial } from 'babylonjs/Materials/standardMaterial'; import { PBRMaterial } from 'babylonjs/Materials/PBR/pbrMaterial'; import { SpriteManager } from 'babylonjs/Sprites/spriteManager'; import { TargetCamera } from 'babylonjs/Cameras/targetCamera'; require("./sceneExplorer.scss"); interface ISceneExplorerFilterComponentProps { onFilter: (filter: string) => void; } export class SceneExplorerFilterComponent extends React.Component { constructor(props: ISceneExplorerFilterComponentProps) { super(props); } render() { return (
this.props.onFilter(evt.target.value)} />
); } } interface ISceneExplorerComponentProps { scene: Scene; noCommands?: boolean; noHeader?: boolean; noExpand?: boolean; noClose?: boolean; extensibilityGroups?: IExplorerExtensibilityGroup[]; globalState: GlobalState; popupMode?: boolean; onPopup?: () => void; onClose?: () => void; } export class SceneExplorerComponent extends React.Component, selectedEntity: any, scene: Scene }> { private _onSelectionChangeObserver: Nullable>; private _onSelectionRenamedObserver: Nullable>; private _onNewSceneAddedObserver: Nullable>; private _onNewSceneObserver: Nullable>; private sceneExplorerRef: React.RefObject; private _once = true; private _hooked = false; private sceneMutationFunc: () => void; constructor(props: ISceneExplorerComponentProps) { super(props); this.state = { filter: null, selectedEntity: null, scene: this.props.scene }; this.sceneMutationFunc = this.processMutation.bind(this); this.sceneExplorerRef = React.createRef(); this._onNewSceneObserver = this.props.globalState.onNewSceneObservable.add((scene: Scene) => { this.setState({ scene }); }); } processMutation() { if (this.props.globalState.blockMutationUpdates) { return; } setTimeout(() => this.forceUpdate()); } componentDidMount() { this._onSelectionChangeObserver = this.props.globalState.onSelectionChangedObservable.add((entity) => { if (this.state.selectedEntity !== entity) { this.setState({ selectedEntity: entity }); } }); this._onSelectionRenamedObserver = this.props.globalState.onSelectionRenamedObservable.add(() => { this.forceUpdate(); }); } componentWillUnmount() { if (this._onSelectionChangeObserver) { this.props.globalState.onSelectionChangedObservable.remove(this._onSelectionChangeObserver); } if (this._onSelectionRenamedObserver) { this.props.globalState.onSelectionRenamedObservable.remove(this._onSelectionRenamedObserver); } if (this._onNewSceneAddedObserver) { EngineStore.LastCreatedEngine!.onNewSceneAddedObservable.remove(this._onNewSceneAddedObserver); } if (this._onNewSceneObserver) { this.props.globalState.onNewSceneObservable.remove(this._onNewSceneObserver); } const scene = this.state.scene; scene.onNewSkeletonAddedObservable.removeCallback(this.sceneMutationFunc); scene.onNewCameraAddedObservable.removeCallback(this.sceneMutationFunc); scene.onNewLightAddedObservable.removeCallback(this.sceneMutationFunc); scene.onNewMaterialAddedObservable.removeCallback(this.sceneMutationFunc); scene.onNewMeshAddedObservable.removeCallback(this.sceneMutationFunc); scene.onNewTextureAddedObservable.removeCallback(this.sceneMutationFunc); scene.onNewTransformNodeAddedObservable.removeCallback(this.sceneMutationFunc); scene.onSkeletonRemovedObservable.removeCallback(this.sceneMutationFunc); scene.onMeshRemovedObservable.removeCallback(this.sceneMutationFunc); scene.onCameraRemovedObservable.removeCallback(this.sceneMutationFunc); scene.onLightRemovedObservable.removeCallback(this.sceneMutationFunc); scene.onMaterialRemovedObservable.removeCallback(this.sceneMutationFunc); scene.onTransformNodeRemovedObservable.removeCallback(this.sceneMutationFunc); scene.onTextureRemovedObservable.removeCallback(this.sceneMutationFunc); } filterContent(filter: string) { this.setState({ filter: filter }); } findSiblings(parent: any, items: any[], target: any, goNext: boolean, data: { previousOne?: any, found?: boolean }): boolean { if (!items) { return false; } const sortedItems = Tools.SortAndFilter(parent, items); if (!items || sortedItems.length === 0) { return false; } for (var item of sortedItems) { if (item === target) { // found the current selection! data.found = true; if (!goNext) { if (data.previousOne) { this.props.globalState.onSelectionChangedObservable.notifyObservers(data.previousOne); } return true; } } else { if (data.found) { this.props.globalState.onSelectionChangedObservable.notifyObservers(item); return true; } data.previousOne = item; } if (item.getChildren && item.reservedDataStore && item.reservedDataStore.isExpanded) { if (this.findSiblings(item, item.getChildren(), target, goNext, data)) { return true; } } } return false; } processKeys(keyEvent: React.KeyboardEvent) { if (!this.state.selectedEntity) { return; } const scene = this.state.scene; let search = false; let goNext = false; if (keyEvent.keyCode === 38) { // up search = true; } else if (keyEvent.keyCode === 40) { // down goNext = true; search = true; } else if (keyEvent.keyCode === 13 || keyEvent.keyCode === 39) { // enter or right var reservedDataStore = this.state.selectedEntity.reservedDataStore; if (reservedDataStore && reservedDataStore.setExpandedState) { reservedDataStore.setExpandedState(true); } keyEvent.preventDefault(); return; } else if (keyEvent.keyCode === 37) { // left var reservedDataStore = this.state.selectedEntity.reservedDataStore; if (reservedDataStore && reservedDataStore.setExpandedState) { reservedDataStore.setExpandedState(false); } keyEvent.preventDefault(); return; } else if (keyEvent.keyCode === 46) { // delete this.state.selectedEntity.dispose(); } if (!search) { return; } keyEvent.preventDefault(); let data = {}; if (!this.findSiblings(null, scene.rootNodes, this.state.selectedEntity, goNext, data)) { if (!this.findSiblings(null, scene.materials, this.state.selectedEntity, goNext, data)) { this.findSiblings(null, scene.textures, this.state.selectedEntity, goNext, data); } } } renderContent() { const scene = this.state.scene; if (!scene) { this._onNewSceneAddedObserver = EngineStore.LastCreatedEngine!.onNewSceneAddedObservable.addOnce((scene) => this.setState({ scene: scene })); return null; } if (!this._hooked) { this._hooked = true; scene.onNewSkeletonAddedObservable.add(this.sceneMutationFunc); scene.onNewCameraAddedObservable.add(this.sceneMutationFunc); scene.onNewLightAddedObservable.add(this.sceneMutationFunc); scene.onNewMaterialAddedObservable.add(this.sceneMutationFunc); scene.onNewMeshAddedObservable.add(this.sceneMutationFunc); scene.onNewTextureAddedObservable.add(this.sceneMutationFunc); scene.onNewTransformNodeAddedObservable.add(this.sceneMutationFunc); scene.onSkeletonRemovedObservable.add(this.sceneMutationFunc); scene.onMeshRemovedObservable.add(this.sceneMutationFunc); scene.onCameraRemovedObservable.add(this.sceneMutationFunc); scene.onLightRemovedObservable.add(this.sceneMutationFunc); scene.onMaterialRemovedObservable.add(this.sceneMutationFunc); scene.onTransformNodeRemovedObservable.add(this.sceneMutationFunc); scene.onTextureRemovedObservable.add(this.sceneMutationFunc); } let guiElements = scene.textures.filter((t) => t.getClassName() === "AdvancedDynamicTexture"); let textures = scene.textures.filter((t) => t.getClassName() !== "AdvancedDynamicTexture"); let postProcessses = scene.postProcesses; let pipelines = scene.postProcessRenderPipelineManager.supportedPipelines; // Context menus let pipelineContextMenus: { label: string, action: () => void }[] = []; if (scene.activeCamera) { if (!pipelines.some((p) => p.getClassName() === "DefaultRenderingPipeline")) { pipelineContextMenus.push({ label: "Add new Default Rendering Pipeline", action: () => { let newPipeline = new DefaultRenderingPipeline("Default rendering pipeline", true, scene, scene.cameras); this.props.globalState.onSelectionChangedObservable.notifyObservers(newPipeline); } }); } if (!pipelines.some((p) => p.getClassName() === "SSAORenderingPipeline")) { pipelineContextMenus.push({ label: "Add new SSAO Rendering Pipeline", action: () => { let newPipeline = new SSAORenderingPipeline("SSAO rendering pipeline", scene, 1, scene.cameras); this.props.globalState.onSelectionChangedObservable.notifyObservers(newPipeline); } }); } if (scene.getEngine().getCaps().drawBuffersExtension && !pipelines.some((p) => p.getClassName() === "SSAORenderingPipeline")) { pipelineContextMenus.push({ label: "Add new SSAO2 Rendering Pipeline", action: () => { let newPipeline = new SSAO2RenderingPipeline("SSAO2 rendering pipeline", scene, 1, scene.cameras); this.props.globalState.onSelectionChangedObservable.notifyObservers(newPipeline); } }); } } let nodeContextMenus: { label: string, action: () => void }[] = []; nodeContextMenus.push({ label: "Add new point light", action: () => { let newPointLight = new PointLight("point light", Vector3.Zero(), scene); this.props.globalState.onSelectionChangedObservable.notifyObservers(newPointLight); } }); nodeContextMenus.push({ label: "Add new directional light", action: () => { let newDirectionalLight = new DirectionalLight("directional light", new Vector3(-1, -1, -0.5), scene); this.props.globalState.onSelectionChangedObservable.notifyObservers(newDirectionalLight); } }); nodeContextMenus.push({ label: "Add new free camera", action: () => { let newFreeCamera = new FreeCamera("free camera", scene.activeCamera ? scene.activeCamera.globalPosition : new Vector3(0, 0, -5), scene); if (scene.activeCamera) { newFreeCamera.minZ = scene.activeCamera.minZ; newFreeCamera.maxZ = scene.activeCamera.maxZ; if ((scene.activeCamera as any).getTarget) { newFreeCamera.setTarget((scene.activeCamera as TargetCamera).getTarget()); } } this.props.globalState.onSelectionChangedObservable.notifyObservers(newFreeCamera); } }); // Materials let materialsContextMenus: { label: string, action: () => void }[] = []; materialsContextMenus.push({ label: "Add new standard material", action: () => { let newStdMaterial = new StandardMaterial("Standard material", scene); this.props.globalState.onSelectionChangedObservable.notifyObservers(newStdMaterial); } }); materialsContextMenus.push({ label: "Add new PBR material", action: () => { let newPBRMaterial = new PBRMaterial("PBR material", scene); this.props.globalState.onSelectionChangedObservable.notifyObservers(newPBRMaterial); } }); materialsContextMenus.push({ label: "Add new node material", action: () => { let newNodeMaterial = new NodeMaterial("node material", scene); newNodeMaterial.setToDefault(); newNodeMaterial.build(); this.props.globalState.onSelectionChangedObservable.notifyObservers(newNodeMaterial); } }); let materials = []; materials.push(...scene.materials); if (scene.multiMaterials && scene.multiMaterials.length) { materials.push(...scene.multiMaterials); } // Sprite Managers let spriteManagersContextMenus: { label: string, action: () => void }[] = []; spriteManagersContextMenus.push({ label: "Add new sprite manager", action: () => { let newSpriteManager = new SpriteManager("Default sprite manager", "//playground.babylonjs.com/textures/player.png", 500, 64, scene); this.props.globalState.onSelectionChangedObservable.notifyObservers(newSpriteManager); } }); // Particle systems let particleSystemsContextMenus: { label: string, action: () => void }[] = []; particleSystemsContextMenus.push({ label: "Add new CPU particle system", action: () => { let newSystem = ParticleHelper.CreateDefault(Vector3.Zero(), 10000, scene); newSystem.name = "CPU particle system"; newSystem.start(); this.props.globalState.onSelectionChangedObservable.notifyObservers(newSystem); } }); if (GPUParticleSystem.IsSupported) { particleSystemsContextMenus.push({ label: "Add new GPU particle system", action: () => { let newSystem = ParticleHelper.CreateDefault(Vector3.Zero(), 10000, scene, true); newSystem.name = "GPU particle system"; newSystem.start(); this.props.globalState.onSelectionChangedObservable.notifyObservers(newSystem); } }); } return (
e.preventDefault()}> this.filterContent(filter)} /> this.forceUpdate()} onSelectionChangedObservable={this.props.globalState.onSelectionChangedObservable} /> { scene.skeletons.length > 0 && } { postProcessses.length > 0 && } { guiElements && guiElements.length > 0 && } { scene.animationGroups.length > 0 && } { scene.mainSoundTrack && scene.mainSoundTrack.soundCollection.length > 0 && }
); } onClose() { if (!this.props.onClose) { return; } this.props.onClose(); } onPopup() { if (!this.props.onPopup) { return; } this.props.onPopup(); } render() { if (this.props.popupMode) { return (
this.processKeys(keyEvent)}> { !this.props.noHeader && this.onClose()} onPopup={() => this.onPopup()} /> } {this.renderContent()}
); } if (this._once) { this._once = false; // A bit hacky but no other way to force the initial width to 300px and not auto setTimeout(() => { const element = document.getElementById("sceneExplorer"); if (!element) { return; } element.style.width = "300px"; }, 150); } return ( this.processKeys(keyEvent)}> { !this.props.noHeader && this.onClose()} onPopup={() => this.onPopup()} /> } {this.renderContent()} ); } }