sceneTreeItemComponent.tsx 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. import { Nullable } from "babylonjs/types";
  2. import { Observer, Observable } from "babylonjs/Misc/observable";
  3. import { PointerInfo, PointerEventTypes } from "babylonjs/Events/pointerEvents";
  4. import { IExplorerExtensibilityGroup } from "babylonjs/Debug/debugLayer";
  5. import { GizmoManager } from "babylonjs/Gizmos/gizmoManager";
  6. import { Scene } from "babylonjs/scene";
  7. import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
  8. import { faSyncAlt, faImage, faCrosshairs, faArrowsAlt, faCompress, faRedoAlt, faVectorSquare } from '@fortawesome/free-solid-svg-icons';
  9. import { ExtensionsComponent } from "../extensionsComponent";
  10. import * as React from "react";
  11. import { GlobalState } from "../../globalState";
  12. import { UtilityLayerRenderer } from "babylonjs/Rendering/utilityLayerRenderer";
  13. import { PropertyChangedEvent } from '../../../components/propertyChangedEvent';
  14. import { LightGizmo } from 'babylonjs/Gizmos/lightGizmo';
  15. import { Tmp, Vector3 } from 'babylonjs/Maths/math';
  16. interface ISceneTreeItemComponentProps {
  17. scene: Scene;
  18. onRefresh: () => void;
  19. selectedEntity?: any;
  20. extensibilityGroups?: IExplorerExtensibilityGroup[];
  21. onSelectionChangedObservable?: Observable<any>;
  22. globalState: GlobalState;
  23. }
  24. export class SceneTreeItemComponent extends React.Component<ISceneTreeItemComponentProps, { isSelected: boolean, isInPickingMode: boolean, gizmoMode: number }> {
  25. private _gizmoLayerOnPointerObserver: Nullable<Observer<PointerInfo>>;
  26. private _onPointerObserver: Nullable<Observer<PointerInfo>>;
  27. private _onSelectionChangeObserver: Nullable<Observer<any>>;
  28. private _selectedEntity: any;
  29. private _posDragEnd: Nullable<Observer<PropertyChangedEvent>> = null;
  30. private _scaleDragEnd: Nullable<Observer<PropertyChangedEvent>> = null;
  31. private _rotateDragEnd: Nullable<Observer<PropertyChangedEvent>> = null;
  32. constructor(props: ISceneTreeItemComponentProps) {
  33. super(props);
  34. const scene = this.props.scene;
  35. let gizmoMode = 0;
  36. if (scene.reservedDataStore && scene.reservedDataStore.gizmoManager) {
  37. const manager: GizmoManager = scene.reservedDataStore.gizmoManager;
  38. if (manager.positionGizmoEnabled) {
  39. gizmoMode = 1;
  40. } else if (manager.rotationGizmoEnabled) {
  41. gizmoMode = 2;
  42. } else if (manager.scaleGizmoEnabled) {
  43. gizmoMode = 3;
  44. } else if (manager.boundingBoxGizmoEnabled) {
  45. gizmoMode = 4;
  46. }
  47. }
  48. this.state = { isSelected: false, isInPickingMode: false, gizmoMode: gizmoMode };
  49. }
  50. shouldComponentUpdate(nextProps: ISceneTreeItemComponentProps, nextState: { isSelected: boolean, isInPickingMode: boolean }) {
  51. if (nextProps.selectedEntity) {
  52. if (nextProps.scene === nextProps.selectedEntity) {
  53. nextState.isSelected = true;
  54. return true;
  55. } else {
  56. nextState.isSelected = false;
  57. }
  58. }
  59. return true;
  60. }
  61. componentWillMount() {
  62. if (!this.props.onSelectionChangedObservable) {
  63. return;
  64. }
  65. const scene = this.props.scene;
  66. this._onSelectionChangeObserver = this.props.onSelectionChangedObservable.add((entity) => {
  67. this._selectedEntity = entity;
  68. if (scene.reservedDataStore && scene.reservedDataStore.gizmoManager) {
  69. const manager: GizmoManager = scene.reservedDataStore.gizmoManager;
  70. const className = entity.getClassName();
  71. if (className === "TransformNode" || className.indexOf("Mesh") !== -1) {
  72. manager.attachToMesh(entity);
  73. } else if (className.indexOf("Light") !== -1) {
  74. if (!this._selectedEntity.reservedDataStore || !this._selectedEntity.reservedDataStore.lightGizmo) {
  75. this.props.globalState.enableLightGizmo(this._selectedEntity, true);
  76. this.forceUpdate();
  77. }
  78. manager.attachToMesh(this._selectedEntity.reservedDataStore.lightGizmo.attachedMesh);
  79. } else {
  80. manager.attachToMesh(null);
  81. }
  82. }
  83. });
  84. }
  85. componentWillUnmount() {
  86. const scene = this.props.scene;
  87. if (this._onPointerObserver) {
  88. scene.onPointerObservable.remove(this._onPointerObserver);
  89. this._onPointerObserver = null;
  90. }
  91. if (this._gizmoLayerOnPointerObserver) {
  92. scene.onPointerObservable.remove(this._gizmoLayerOnPointerObserver);
  93. this._gizmoLayerOnPointerObserver = null;
  94. }
  95. if (this._onSelectionChangeObserver && this.props.onSelectionChangedObservable) {
  96. this.props.onSelectionChangedObservable.remove(this._onSelectionChangeObserver);
  97. }
  98. }
  99. onSelect() {
  100. if (!this.props.onSelectionChangedObservable) {
  101. return;
  102. }
  103. const scene = this.props.scene;
  104. this.props.onSelectionChangedObservable.notifyObservers(scene);
  105. }
  106. onPickingMode() {
  107. const scene = this.props.scene;
  108. if (this._onPointerObserver) {
  109. scene.onPointerObservable.remove(this._onPointerObserver);
  110. this._onPointerObserver = null;
  111. }
  112. if (!this.state.isInPickingMode) {
  113. this._onPointerObserver = scene.onPointerObservable.add(() => {
  114. const pickPosition = scene.unTranslatedPointer;
  115. const pickInfo = scene.pick(pickPosition.x, pickPosition.y, (mesh) => mesh.isEnabled() && mesh.isVisible && mesh.getTotalVertices() > 0, false,
  116. undefined, (p0, p1, p2, ray) => {
  117. if (!this.props.globalState.ignoreBackfacesForPicking) {
  118. return true;
  119. }
  120. let p0p1 = Tmp.Vector3[0];
  121. let p1p2 = Tmp.Vector3[1];
  122. let normal = Tmp.Vector3[2];
  123. p1.subtractToRef(p0, p0p1);
  124. p2.subtractToRef(p1, p1p2);
  125. normal = Vector3.Cross(p0p1, p1p2);
  126. return Vector3.Dot(normal, ray.direction) < 0;
  127. });
  128. // Pick light gizmos first
  129. if (this.props.globalState.lightGizmos.length > 0) {
  130. var gizmoScene = this.props.globalState.lightGizmos[0].gizmoLayer.utilityLayerScene;
  131. let pickInfo = gizmoScene.pick(pickPosition.x, pickPosition.y, (m: any) => {
  132. for (var g of (this.props.globalState.lightGizmos as any)) {
  133. if (g.attachedMesh == m) {
  134. return true;
  135. }
  136. }
  137. return false;
  138. });
  139. if (pickInfo && pickInfo.hit && this.props.onSelectionChangedObservable) {
  140. this.props.onSelectionChangedObservable.notifyObservers(pickInfo.pickedMesh);
  141. return;
  142. }
  143. }
  144. if (pickInfo && pickInfo.hit && this.props.onSelectionChangedObservable) {
  145. this.props.onSelectionChangedObservable.notifyObservers(pickInfo.pickedMesh);
  146. }
  147. }, PointerEventTypes.POINTERTAP);
  148. }
  149. this.setState({ isInPickingMode: !this.state.isInPickingMode });
  150. }
  151. setGizmoMode(mode: number) {
  152. const scene = this.props.scene;
  153. if (!scene.reservedDataStore) {
  154. scene.reservedDataStore = {};
  155. }
  156. if (this._gizmoLayerOnPointerObserver) {
  157. scene.onPointerObservable.remove(this._gizmoLayerOnPointerObserver);
  158. this._gizmoLayerOnPointerObserver = null;
  159. }
  160. if (!scene.reservedDataStore.gizmoManager) {
  161. scene.reservedDataStore.gizmoManager = new GizmoManager(scene);
  162. }
  163. const manager: GizmoManager = scene.reservedDataStore.gizmoManager;
  164. // Allow picking of light gizmo when a gizmo mode is selected
  165. this._gizmoLayerOnPointerObserver = UtilityLayerRenderer.DefaultUtilityLayer.utilityLayerScene.onPointerObservable.add((pointerInfo) => {
  166. if (pointerInfo.type == PointerEventTypes.POINTERDOWN) {
  167. if (pointerInfo.pickInfo && pointerInfo.pickInfo.pickedMesh) {
  168. var node: Nullable<any> = pointerInfo.pickInfo.pickedMesh;
  169. // Attach to the most parent node
  170. while (node && node.parent != null) {
  171. node = node.parent;
  172. }
  173. for (var gizmo of this.props.globalState.lightGizmos) {
  174. if (gizmo._rootMesh == node) {
  175. manager.attachToMesh(gizmo.attachedMesh);
  176. }
  177. }
  178. }
  179. }
  180. })
  181. manager.boundingBoxGizmoEnabled = false;
  182. manager.positionGizmoEnabled = false;
  183. manager.rotationGizmoEnabled = false;
  184. manager.scaleGizmoEnabled = false;
  185. if (this.state.gizmoMode === mode) {
  186. mode = 0;
  187. manager.dispose();
  188. scene.reservedDataStore.gizmoManager = null;
  189. } else {
  190. switch (mode) {
  191. case 1:
  192. manager.positionGizmoEnabled = true;
  193. if (!this._posDragEnd) {
  194. // Record movement for generating replay code
  195. this._posDragEnd = manager.gizmos.positionGizmo!.onDragEndObservable.add(() => {
  196. if (manager.gizmos.positionGizmo && manager.gizmos.positionGizmo.attachedMesh) {
  197. var lightGizmo: Nullable<LightGizmo> = manager.gizmos.positionGizmo.attachedMesh.reservedDataStore.lightGizmo;
  198. var obj: any = (lightGizmo && lightGizmo.light) ? lightGizmo.light : manager.gizmos.positionGizmo.attachedMesh;
  199. if (obj.position) {
  200. var e = new PropertyChangedEvent();
  201. e.object = obj
  202. e.property = "position"
  203. e.value = obj.position;
  204. this.props.globalState.onPropertyChangedObservable.notifyObservers(e)
  205. }
  206. }
  207. })
  208. }
  209. break;
  210. case 2:
  211. manager.rotationGizmoEnabled = true;
  212. if (!this._rotateDragEnd) {
  213. // Record movement for generating replay code
  214. this._rotateDragEnd = manager.gizmos.rotationGizmo!.onDragEndObservable.add(() => {
  215. if (manager.gizmos.rotationGizmo && manager.gizmos.rotationGizmo.attachedMesh) {
  216. var lightGizmo: Nullable<LightGizmo> = manager.gizmos.rotationGizmo.attachedMesh.reservedDataStore.lightGizmo;
  217. var obj: any = (lightGizmo && lightGizmo.light) ? lightGizmo.light : manager.gizmos.rotationGizmo.attachedMesh;
  218. if (obj.rotationQuaternion) {
  219. var e = new PropertyChangedEvent();
  220. e.object = obj;
  221. e.property = "rotationQuaternion";
  222. e.value = obj.rotationQuaternion;
  223. this.props.globalState.onPropertyChangedObservable.notifyObservers(e);
  224. } else if (obj.rotation) {
  225. var e = new PropertyChangedEvent();
  226. e.object = obj;
  227. e.property = "rotation";
  228. e.value = obj.rotation;
  229. this.props.globalState.onPropertyChangedObservable.notifyObservers(e);
  230. } else if (obj.direction) {
  231. var e = new PropertyChangedEvent();
  232. e.object = obj;
  233. e.property = "direction";
  234. e.value = obj.direction;
  235. this.props.globalState.onPropertyChangedObservable.notifyObservers(e);
  236. }
  237. }
  238. })
  239. }
  240. break;
  241. case 3:
  242. manager.scaleGizmoEnabled = true;
  243. if (!this._scaleDragEnd) {
  244. // Record movement for generating replay code
  245. this._scaleDragEnd = manager.gizmos.scaleGizmo!.onDragEndObservable.add(() => {
  246. if (manager.gizmos.scaleGizmo && manager.gizmos.scaleGizmo.attachedMesh) {
  247. var lightGizmo: Nullable<LightGizmo> = manager.gizmos.scaleGizmo.attachedMesh.reservedDataStore.lightGizmo;
  248. var obj: any = (lightGizmo && lightGizmo.light) ? lightGizmo.light : manager.gizmos.scaleGizmo.attachedMesh;
  249. if (obj.scaling) {
  250. var e = new PropertyChangedEvent();
  251. e.object = obj;
  252. e.property = "scaling";
  253. e.value = obj.scaling;
  254. this.props.globalState.onPropertyChangedObservable.notifyObservers(e);
  255. }
  256. }
  257. })
  258. }
  259. break;
  260. case 4:
  261. manager.boundingBoxGizmoEnabled = true;
  262. if (manager.gizmos.boundingBoxGizmo) {
  263. manager.gizmos.boundingBoxGizmo.fixedDragMeshScreenSize = true;
  264. }
  265. break;
  266. }
  267. if (this._selectedEntity && this._selectedEntity.getClassName) {
  268. const className = this._selectedEntity.getClassName();
  269. if (className === "TransformNode" || className.indexOf("Mesh") !== -1) {
  270. manager.attachToMesh(this._selectedEntity);
  271. } else if (className.indexOf("Light") !== -1) {
  272. if (!this._selectedEntity.reservedDataStore || !this._selectedEntity.reservedDataStore.lightGizmo) {
  273. this.props.globalState.enableLightGizmo(this._selectedEntity, true);
  274. this.forceUpdate();
  275. }
  276. manager.attachToMesh(this._selectedEntity.reservedDataStore.lightGizmo.attachedMesh);
  277. }
  278. }
  279. }
  280. this.setState({ gizmoMode: mode });
  281. }
  282. render() {
  283. return (
  284. <div className={this.state.isSelected ? "itemContainer selected" : "itemContainer"}>
  285. <div className="sceneNode">
  286. <div className="sceneTitle" onClick={() => this.onSelect()} >
  287. <FontAwesomeIcon icon={faImage} />&nbsp;Scene
  288. </div>
  289. <div className={this.state.gizmoMode === 1 ? "translation selected icon" : "translation icon"} onClick={() => this.setGizmoMode(1)} title="Enable/Disable position mode">
  290. <FontAwesomeIcon icon={faArrowsAlt} />
  291. </div>
  292. <div className={this.state.gizmoMode === 2 ? "rotation selected icon" : "rotation icon"} onClick={() => this.setGizmoMode(2)} title="Enable/Disable rotation mode">
  293. <FontAwesomeIcon icon={faRedoAlt} />
  294. </div>
  295. <div className={this.state.gizmoMode === 3 ? "scaling selected icon" : "scaling icon"} onClick={() => this.setGizmoMode(3)} title="Enable/Disable scaling mode">
  296. <FontAwesomeIcon icon={faCompress} />
  297. </div>
  298. <div className={this.state.gizmoMode === 4 ? "bounding selected icon" : "bounding icon"} onClick={() => this.setGizmoMode(4)} title="Enable/Disable bounding box mode">
  299. <FontAwesomeIcon icon={faVectorSquare} />
  300. </div>
  301. <div className="separator" />
  302. <div className={this.state.isInPickingMode ? "pickingMode selected icon" : "pickingMode icon"} onClick={() => this.onPickingMode()} title="Turn picking mode on/off">
  303. <FontAwesomeIcon icon={faCrosshairs} />
  304. </div>
  305. <div className="refresh icon" onClick={() => this.props.onRefresh()} title="Refresh the explorer">
  306. <FontAwesomeIcon icon={faSyncAlt} />
  307. </div>
  308. {
  309. <ExtensionsComponent target={this.props.scene} extensibilityGroups={this.props.extensibilityGroups} />
  310. }
  311. </div>
  312. </div>
  313. );
  314. }
  315. }