lightGizmo.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. import { Vector3, Quaternion, TmpVectors } from "../Maths/math.vector.js";
  2. import { Color3 } from "../Maths/math.color.js";
  3. import { AbstractMesh } from "../Meshes/abstractMesh.js";
  4. import { Mesh } from "../Meshes/mesh.js";
  5. import { Gizmo } from "./gizmo.js";
  6. import { UtilityLayerRenderer } from "../Rendering/utilityLayerRenderer.js";
  7. import { StandardMaterial } from "../Materials/standardMaterial.js";
  8. import { HemisphericLight } from "../Lights/hemisphericLight.js";
  9. import { DirectionalLight } from "../Lights/directionalLight.js";
  10. import { CreateSphere } from "../Meshes/Builders/sphereBuilder.js";
  11. import { CreateHemisphere } from "../Meshes/Builders/hemisphereBuilder.js";
  12. import { SpotLight } from "../Lights/spotLight.js";
  13. import { TransformNode } from "../Meshes/transformNode.js";
  14. import { PointerEventTypes } from "../Events/pointerEvents.js";
  15. import { Observable } from "../Misc/observable.js";
  16. import { CreateCylinder } from "../Meshes/Builders/cylinderBuilder.js";
  17. import { Logger } from "../Misc/logger.js";
  18. /**
  19. * Gizmo that enables viewing a light
  20. */
  21. export class LightGizmo extends Gizmo {
  22. /**
  23. * Creates a LightGizmo
  24. * @param gizmoLayer The utility layer the gizmo will be added to
  25. */
  26. constructor(gizmoLayer = UtilityLayerRenderer.DefaultUtilityLayer) {
  27. super(gizmoLayer);
  28. this._cachedPosition = new Vector3();
  29. this._cachedForward = new Vector3(0, 0, 1);
  30. this._pointerObserver = null;
  31. /**
  32. * Event that fires each time the gizmo is clicked
  33. */
  34. this.onClickedObservable = new Observable();
  35. this._light = null;
  36. this.attachedMesh = new AbstractMesh("", this.gizmoLayer.utilityLayerScene);
  37. this._attachedMeshParent = new TransformNode("parent", this.gizmoLayer.utilityLayerScene);
  38. this.attachedMesh.parent = this._attachedMeshParent;
  39. this._material = new StandardMaterial("light", this.gizmoLayer.utilityLayerScene);
  40. this._material.diffuseColor = new Color3(0.5, 0.5, 0.5);
  41. this._material.specularColor = new Color3(0.1, 0.1, 0.1);
  42. this._pointerObserver = gizmoLayer.utilityLayerScene.onPointerObservable.add((pointerInfo) => {
  43. if (!this._light) {
  44. return;
  45. }
  46. this._isHovered = !!(pointerInfo.pickInfo && this._rootMesh.getChildMeshes().indexOf(pointerInfo.pickInfo.pickedMesh) != -1);
  47. if (this._isHovered && pointerInfo.event.button === 0) {
  48. this.onClickedObservable.notifyObservers(this._light);
  49. }
  50. }, PointerEventTypes.POINTERDOWN);
  51. }
  52. /**
  53. * Override attachedNode because lightgizmo only support attached mesh
  54. * It will return the attached mesh (if any) and setting an attached node will log
  55. * a warning
  56. */
  57. get attachedNode() {
  58. return this.attachedMesh;
  59. }
  60. set attachedNode(value) {
  61. Logger.Warn("Nodes cannot be attached to LightGizmo. Attach to a mesh instead.");
  62. }
  63. /**
  64. * The light that the gizmo is attached to
  65. */
  66. set light(light) {
  67. this._light = light;
  68. if (light) {
  69. // Create the mesh for the given light type
  70. if (this._lightMesh) {
  71. this._lightMesh.dispose();
  72. }
  73. if (light instanceof HemisphericLight) {
  74. this._lightMesh = LightGizmo._CreateHemisphericLightMesh(this.gizmoLayer.utilityLayerScene);
  75. }
  76. else if (light instanceof DirectionalLight) {
  77. this._lightMesh = LightGizmo._CreateDirectionalLightMesh(this.gizmoLayer.utilityLayerScene);
  78. }
  79. else if (light instanceof SpotLight) {
  80. this._lightMesh = LightGizmo._CreateSpotLightMesh(this.gizmoLayer.utilityLayerScene);
  81. }
  82. else {
  83. this._lightMesh = LightGizmo._CreatePointLightMesh(this.gizmoLayer.utilityLayerScene);
  84. }
  85. this._lightMesh.getChildMeshes(false).forEach((m) => {
  86. m.material = this._material;
  87. });
  88. this._lightMesh.parent = this._rootMesh;
  89. // Add lighting to the light gizmo
  90. const gizmoLight = this.gizmoLayer._getSharedGizmoLight();
  91. gizmoLight.includedOnlyMeshes = gizmoLight.includedOnlyMeshes.concat(this._lightMesh.getChildMeshes(false));
  92. this._lightMesh.rotationQuaternion = new Quaternion();
  93. if (!this.attachedMesh.reservedDataStore) {
  94. this.attachedMesh.reservedDataStore = {};
  95. }
  96. this.attachedMesh.reservedDataStore.lightGizmo = this;
  97. if (light.parent) {
  98. this._attachedMeshParent.freezeWorldMatrix(light.parent.getWorldMatrix());
  99. }
  100. // Get update position and direction if the light has it
  101. if (light.position) {
  102. this.attachedMesh.position.copyFrom(light.position);
  103. this.attachedMesh.computeWorldMatrix(true);
  104. this._cachedPosition.copyFrom(this.attachedMesh.position);
  105. }
  106. if (light.direction) {
  107. this.attachedMesh.setDirection(light.direction);
  108. this.attachedMesh.computeWorldMatrix(true);
  109. const forward = this._getMeshForward();
  110. this._cachedForward.copyFrom(forward);
  111. }
  112. this._update();
  113. }
  114. }
  115. get light() {
  116. return this._light;
  117. }
  118. /**
  119. * Gets the material used to render the light gizmo
  120. */
  121. get material() {
  122. return this._material;
  123. }
  124. /**
  125. * @internal
  126. * returns mesh forward
  127. */
  128. _getMeshForward() {
  129. let forward = this.attachedMesh.forward;
  130. if (this.attachedMesh.getScene().useRightHandedSystem) {
  131. forward.negateToRef(TmpVectors.Vector3[0]);
  132. forward = TmpVectors.Vector3[0];
  133. }
  134. return forward;
  135. }
  136. /**
  137. * @internal
  138. * Updates the gizmo to match the attached mesh's position/rotation
  139. */
  140. _update() {
  141. super._update();
  142. if (!this._light) {
  143. return;
  144. }
  145. if (this._light.parent) {
  146. this._attachedMeshParent.freezeWorldMatrix(this._light.parent.getWorldMatrix());
  147. }
  148. // For light position and direction, a dirty flag is set to true in the setter
  149. // It means setting values individually or copying values will not call setter and
  150. // dirty flag will not be set to true. Hence creating a new Vector3.
  151. if (this._light.position) {
  152. // If the gizmo is moved update the light otherwise update the gizmo to match the light
  153. if (!this.attachedMesh.position.equals(this._cachedPosition)) {
  154. // update light to match gizmo
  155. const position = this.attachedMesh.position;
  156. this._light.position = new Vector3(position.x, position.y, position.z);
  157. this._cachedPosition.copyFrom(this.attachedMesh.position);
  158. }
  159. else {
  160. // update gizmo to match light
  161. this.attachedMesh.position.copyFrom(this._light.position);
  162. this.attachedMesh.computeWorldMatrix(true);
  163. this._cachedPosition.copyFrom(this.attachedMesh.position);
  164. }
  165. }
  166. if (this._light.direction) {
  167. // If the gizmo is moved update the light otherwise update the gizmo to match the light
  168. const forward = this._getMeshForward();
  169. if (Vector3.DistanceSquared(forward, this._cachedForward) > 0.0001) {
  170. // update light to match gizmo
  171. const direction = forward;
  172. this._light.direction = new Vector3(direction.x, direction.y, direction.z);
  173. this._cachedForward.copyFrom(forward);
  174. }
  175. else if (Vector3.DistanceSquared(forward, this._light.direction) > 0.0001) {
  176. // update gizmo to match light
  177. this.attachedMesh.setDirection(this._light.direction);
  178. this.attachedMesh.computeWorldMatrix(true);
  179. this._cachedForward.copyFrom(forward);
  180. }
  181. }
  182. }
  183. /**
  184. * Disposes of the light gizmo
  185. */
  186. dispose() {
  187. this.onClickedObservable.clear();
  188. this.gizmoLayer.utilityLayerScene.onPointerObservable.remove(this._pointerObserver);
  189. this._material.dispose();
  190. super.dispose();
  191. this._attachedMeshParent.dispose();
  192. }
  193. static _CreateHemisphericLightMesh(scene) {
  194. const root = new Mesh("hemisphereLight", scene);
  195. const hemisphere = CreateHemisphere(root.name, { segments: 10, diameter: 1 }, scene);
  196. hemisphere.position.z = -0.15;
  197. hemisphere.rotation.x = Math.PI / 2;
  198. hemisphere.parent = root;
  199. const lines = this._CreateLightLines(3, scene);
  200. lines.parent = root;
  201. root.scaling.scaleInPlace(LightGizmo._Scale);
  202. root.rotation.x = Math.PI / 2;
  203. return root;
  204. }
  205. static _CreatePointLightMesh(scene) {
  206. const root = new Mesh("pointLight", scene);
  207. const sphere = CreateSphere(root.name, { segments: 10, diameter: 1 }, scene);
  208. sphere.rotation.x = Math.PI / 2;
  209. sphere.parent = root;
  210. const lines = this._CreateLightLines(5, scene);
  211. lines.parent = root;
  212. root.scaling.scaleInPlace(LightGizmo._Scale);
  213. root.rotation.x = Math.PI / 2;
  214. return root;
  215. }
  216. static _CreateSpotLightMesh(scene) {
  217. const root = new Mesh("spotLight", scene);
  218. const sphere = CreateSphere(root.name, { segments: 10, diameter: 1 }, scene);
  219. sphere.parent = root;
  220. const hemisphere = CreateHemisphere(root.name, { segments: 10, diameter: 2 }, scene);
  221. hemisphere.parent = root;
  222. hemisphere.rotation.x = -Math.PI / 2;
  223. const lines = this._CreateLightLines(2, scene);
  224. lines.parent = root;
  225. root.scaling.scaleInPlace(LightGizmo._Scale);
  226. root.rotation.x = Math.PI / 2;
  227. return root;
  228. }
  229. static _CreateDirectionalLightMesh(scene) {
  230. const root = new Mesh("directionalLight", scene);
  231. const mesh = new Mesh(root.name, scene);
  232. mesh.parent = root;
  233. const sphere = CreateSphere(root.name, { diameter: 1.2, segments: 10 }, scene);
  234. sphere.parent = mesh;
  235. const line = CreateCylinder(root.name, {
  236. updatable: false,
  237. height: 6,
  238. diameterTop: 0.3,
  239. diameterBottom: 0.3,
  240. tessellation: 6,
  241. subdivisions: 1,
  242. }, scene);
  243. line.parent = mesh;
  244. let left = line.clone(root.name);
  245. left.scaling.y = 0.5;
  246. left.position.x += 1.25;
  247. let right = line.clone(root.name);
  248. right.scaling.y = 0.5;
  249. right.position.x += -1.25;
  250. const arrowHead = CreateCylinder(root.name, {
  251. updatable: false,
  252. height: 1,
  253. diameterTop: 0,
  254. diameterBottom: 0.6,
  255. tessellation: 6,
  256. subdivisions: 1,
  257. }, scene);
  258. arrowHead.position.y += 3;
  259. arrowHead.parent = mesh;
  260. left = arrowHead.clone(root.name);
  261. left.position.y = 1.5;
  262. left.position.x += 1.25;
  263. right = arrowHead.clone(root.name);
  264. right.position.y = 1.5;
  265. right.position.x += -1.25;
  266. mesh.scaling.scaleInPlace(LightGizmo._Scale);
  267. mesh.rotation.z = Math.PI / 2;
  268. mesh.rotation.y = Math.PI / 2;
  269. return root;
  270. }
  271. }
  272. // Static helper methods
  273. LightGizmo._Scale = 0.007;
  274. /**
  275. * Creates the lines for a light mesh
  276. * @param levels
  277. * @param scene
  278. * @returns the light lines mesh
  279. */
  280. LightGizmo._CreateLightLines = (levels, scene) => {
  281. const distFromSphere = 1.2;
  282. const root = new Mesh("root", scene);
  283. root.rotation.x = Math.PI / 2;
  284. // Create the top line, this will be cloned for all other lines
  285. const linePivot = new Mesh("linePivot", scene);
  286. linePivot.parent = root;
  287. const line = CreateCylinder("line", {
  288. updatable: false,
  289. height: 2,
  290. diameterTop: 0.2,
  291. diameterBottom: 0.3,
  292. tessellation: 6,
  293. subdivisions: 1,
  294. }, scene);
  295. line.position.y = line.scaling.y / 2 + distFromSphere;
  296. line.parent = linePivot;
  297. if (levels < 2) {
  298. return linePivot;
  299. }
  300. for (let i = 0; i < 4; i++) {
  301. const l = linePivot.clone("lineParentClone");
  302. l.rotation.z = Math.PI / 4;
  303. l.rotation.y = Math.PI / 2 + (Math.PI / 2) * i;
  304. l.getChildMeshes()[0].scaling.y = 0.5;
  305. l.getChildMeshes()[0].scaling.x = l.getChildMeshes()[0].scaling.z = 0.8;
  306. l.getChildMeshes()[0].position.y = l.getChildMeshes()[0].scaling.y / 2 + distFromSphere;
  307. }
  308. if (levels < 3) {
  309. return root;
  310. }
  311. for (let i = 0; i < 4; i++) {
  312. const l = linePivot.clone("linePivotClone");
  313. l.rotation.z = Math.PI / 2;
  314. l.rotation.y = (Math.PI / 2) * i;
  315. }
  316. if (levels < 4) {
  317. return root;
  318. }
  319. for (let i = 0; i < 4; i++) {
  320. const l = linePivot.clone("linePivotClone");
  321. l.rotation.z = Math.PI + Math.PI / 4;
  322. l.rotation.y = Math.PI / 2 + (Math.PI / 2) * i;
  323. l.getChildMeshes()[0].scaling.y = 0.5;
  324. l.getChildMeshes()[0].scaling.x = l.getChildMeshes()[0].scaling.z = 0.8;
  325. l.getChildMeshes()[0].position.y = l.getChildMeshes()[0].scaling.y / 2 + distFromSphere;
  326. }
  327. if (levels < 5) {
  328. return root;
  329. }
  330. const l = linePivot.clone("linePivotClone");
  331. l.rotation.z = Math.PI;
  332. return root;
  333. };
  334. //# sourceMappingURL=lightGizmo.js.map