holographicButton.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. import { Button3D } from "./button3D";
  2. import { Nullable } from "babylonjs/types";
  3. import { Observer } from "babylonjs/Misc/observable";
  4. import { Color3, Vector3 } from "babylonjs/Maths/math";
  5. import { StandardMaterial } from "babylonjs/Materials/standardMaterial";
  6. import { TransformNode } from "babylonjs/Meshes/transformNode";
  7. import { Mesh } from "babylonjs/Meshes/mesh";
  8. import { PlaneBuilder } from "babylonjs/Meshes/Builders/planeBuilder";
  9. import { BoxBuilder } from "babylonjs/Meshes/Builders/boxBuilder";
  10. import { FadeInOutBehavior } from "babylonjs/Behaviors/Meshes/fadeInOutBehavior";
  11. import { Scene } from "babylonjs/scene";
  12. import { FluentMaterial } from "../materials/fluentMaterial";
  13. import { StackPanel } from "../../2D/controls/stackPanel";
  14. import { Image } from "../../2D/controls/image";
  15. import { TextBlock } from "../../2D/controls/textBlock";
  16. import { AdvancedDynamicTexture } from "../../2D/advancedDynamicTexture";
  17. import { Control3D } from "./control3D";
  18. /**
  19. * Class used to create a holographic button in 3D
  20. */
  21. export class HolographicButton extends Button3D {
  22. private _backPlate: Mesh;
  23. private _textPlate: Mesh;
  24. private _frontPlate: Mesh;
  25. private _text: string;
  26. private _imageUrl: string;
  27. private _shareMaterials = true;
  28. private _frontMaterial: FluentMaterial;
  29. private _backMaterial: FluentMaterial;
  30. private _plateMaterial: StandardMaterial;
  31. private _pickedPointObserver: Nullable<Observer<Nullable<Vector3>>>;
  32. // Tooltip
  33. private _tooltipFade: Nullable<FadeInOutBehavior>;
  34. private _tooltipTextBlock: Nullable<TextBlock>;
  35. private _tooltipTexture: Nullable<AdvancedDynamicTexture>;
  36. private _tooltipMesh: Nullable<Mesh>;
  37. private _tooltipHoverObserver: Nullable<Observer<Control3D>>;
  38. private _tooltipOutObserver: Nullable<Observer<Control3D>>;
  39. private _disposeTooltip() {
  40. this._tooltipFade = null;
  41. if (this._tooltipTextBlock) {
  42. this._tooltipTextBlock.dispose();
  43. }
  44. if (this._tooltipTexture) {
  45. this._tooltipTexture.dispose();
  46. }
  47. if (this._tooltipMesh) {
  48. this._tooltipMesh.dispose();
  49. }
  50. this.onPointerEnterObservable.remove(this._tooltipHoverObserver);
  51. this.onPointerOutObservable.remove(this._tooltipOutObserver);
  52. }
  53. /**
  54. * Rendering ground id of all the mesh in the button
  55. */
  56. public set renderingGroupId(id: number) {
  57. this._backPlate.renderingGroupId = id;
  58. this._textPlate.renderingGroupId = id;
  59. this._frontPlate.renderingGroupId = id;
  60. if (this._tooltipMesh) {
  61. this._tooltipMesh.renderingGroupId = id;
  62. }
  63. }
  64. public get renderingGroupId(): number {
  65. return this._backPlate.renderingGroupId;
  66. }
  67. /**
  68. * Text to be displayed on the tooltip shown when hovering on the button. When set to null tooltip is disabled. (Default: null)
  69. */
  70. public set tooltipText(text: Nullable<string>) {
  71. if (!text) {
  72. this._disposeTooltip();
  73. return;
  74. }
  75. if (!this._tooltipFade) {
  76. // Create tooltip with mesh and text
  77. this._tooltipMesh = PlaneBuilder.CreatePlane("", { size: 1 }, this._backPlate._scene);
  78. var tooltipBackground = PlaneBuilder.CreatePlane("", { size: 1, sideOrientation: Mesh.DOUBLESIDE }, this._backPlate._scene);
  79. var mat = new StandardMaterial("", this._backPlate._scene);
  80. mat.diffuseColor = Color3.FromHexString("#212121");
  81. tooltipBackground.material = mat;
  82. tooltipBackground.isPickable = false;
  83. this._tooltipMesh.addChild(tooltipBackground);
  84. tooltipBackground.position.z = 0.05;
  85. this._tooltipMesh.scaling.y = 1 / 3;
  86. this._tooltipMesh.position.y = 0.7;
  87. this._tooltipMesh.position.z = -0.15;
  88. this._tooltipMesh.isPickable = false;
  89. this._tooltipMesh.parent = this._backPlate;
  90. // Create text texture for the tooltip
  91. this._tooltipTexture = AdvancedDynamicTexture.CreateForMesh(this._tooltipMesh);
  92. this._tooltipTextBlock = new TextBlock();
  93. this._tooltipTextBlock.scaleY = 3;
  94. this._tooltipTextBlock.color = "white";
  95. this._tooltipTextBlock.fontSize = 130;
  96. this._tooltipTexture.addControl(this._tooltipTextBlock);
  97. // Add hover action to tooltip
  98. this._tooltipFade = new FadeInOutBehavior();
  99. this._tooltipFade.delay = 500;
  100. this._tooltipMesh.addBehavior(this._tooltipFade);
  101. this._tooltipHoverObserver = this.onPointerEnterObservable.add(() => {
  102. if (this._tooltipFade) {
  103. this._tooltipFade.fadeIn(true);
  104. }
  105. });
  106. this._tooltipOutObserver = this.onPointerOutObservable.add(() => {
  107. if (this._tooltipFade) {
  108. this._tooltipFade.fadeIn(false);
  109. }
  110. });
  111. }
  112. if (this._tooltipTextBlock) {
  113. this._tooltipTextBlock.text = text;
  114. }
  115. }
  116. public get tooltipText() {
  117. if (this._tooltipTextBlock) {
  118. return this._tooltipTextBlock.text;
  119. }
  120. return null;
  121. }
  122. /**
  123. * Gets or sets text for the button
  124. */
  125. public get text(): string {
  126. return this._text;
  127. }
  128. public set text(value: string) {
  129. if (this._text === value) {
  130. return;
  131. }
  132. this._text = value;
  133. this._rebuildContent();
  134. }
  135. /**
  136. * Gets or sets the image url for the button
  137. */
  138. public get imageUrl(): string {
  139. return this._imageUrl;
  140. }
  141. public set imageUrl(value: string) {
  142. if (this._imageUrl === value) {
  143. return;
  144. }
  145. this._imageUrl = value;
  146. this._rebuildContent();
  147. }
  148. /**
  149. * Gets the back material used by this button
  150. */
  151. public get backMaterial(): FluentMaterial {
  152. return this._backMaterial;
  153. }
  154. /**
  155. * Gets the front material used by this button
  156. */
  157. public get frontMaterial(): FluentMaterial {
  158. return this._frontMaterial;
  159. }
  160. /**
  161. * Gets the plate material used by this button
  162. */
  163. public get plateMaterial(): StandardMaterial {
  164. return this._plateMaterial;
  165. }
  166. /**
  167. * Gets a boolean indicating if this button shares its material with other HolographicButtons
  168. */
  169. public get shareMaterials(): boolean {
  170. return this._shareMaterials;
  171. }
  172. /**
  173. * Creates a new button
  174. * @param name defines the control name
  175. */
  176. constructor(name?: string, shareMaterials = true) {
  177. super(name);
  178. this._shareMaterials = shareMaterials;
  179. // Default animations
  180. this.pointerEnterAnimation = () => {
  181. if (!this.mesh) {
  182. return;
  183. }
  184. this._frontPlate.setEnabled(true);
  185. };
  186. this.pointerOutAnimation = () => {
  187. if (!this.mesh) {
  188. return;
  189. }
  190. this._frontPlate.setEnabled(false);
  191. };
  192. }
  193. protected _getTypeName(): string {
  194. return "HolographicButton";
  195. }
  196. private _rebuildContent(): void {
  197. this._disposeFacadeTexture();
  198. let panel = new StackPanel();
  199. panel.isVertical = true;
  200. if (this._imageUrl) {
  201. let image = new Image();
  202. image.source = this._imageUrl;
  203. image.paddingTop = "40px";
  204. image.height = "180px";
  205. image.width = "100px";
  206. image.paddingBottom = "40px";
  207. panel.addControl(image);
  208. }
  209. if (this._text) {
  210. let text = new TextBlock();
  211. text.text = this._text;
  212. text.color = "white";
  213. text.height = "30px";
  214. text.fontSize = 24;
  215. panel.addControl(text);
  216. }
  217. if (this._frontPlate) {
  218. this.content = panel;
  219. }
  220. }
  221. // Mesh association
  222. protected _createNode(scene: Scene): TransformNode {
  223. this._backPlate = BoxBuilder.CreateBox(this.name + "BackMesh", {
  224. width: 1.0,
  225. height: 1.0,
  226. depth: 0.08
  227. }, scene);
  228. this._frontPlate = BoxBuilder.CreateBox(this.name + "FrontMesh", {
  229. width: 1.0,
  230. height: 1.0,
  231. depth: 0.08
  232. }, scene);
  233. this._frontPlate.parent = this._backPlate;
  234. this._frontPlate.position.z = -0.08;
  235. this._frontPlate.isPickable = false;
  236. this._frontPlate.setEnabled(false);
  237. this._textPlate = <Mesh>super._createNode(scene);
  238. this._textPlate.parent = this._backPlate;
  239. this._textPlate.position.z = -0.08;
  240. this._textPlate.isPickable = false;
  241. return this._backPlate;
  242. }
  243. protected _applyFacade(facadeTexture: AdvancedDynamicTexture) {
  244. this._plateMaterial.emissiveTexture = facadeTexture;
  245. this._plateMaterial.opacityTexture = facadeTexture;
  246. }
  247. private _createBackMaterial(mesh: Mesh) {
  248. this._backMaterial = new FluentMaterial(this.name + "Back Material", mesh.getScene());
  249. this._backMaterial.renderHoverLight = true;
  250. this._pickedPointObserver = this._host.onPickedPointChangedObservable.add((pickedPoint) => {
  251. if (pickedPoint) {
  252. this._backMaterial.hoverPosition = pickedPoint;
  253. this._backMaterial.hoverColor.a = 1.0;
  254. } else {
  255. this._backMaterial.hoverColor.a = 0;
  256. }
  257. });
  258. }
  259. private _createFrontMaterial(mesh: Mesh) {
  260. this._frontMaterial = new FluentMaterial(this.name + "Front Material", mesh.getScene());
  261. this._frontMaterial.innerGlowColorIntensity = 0; // No inner glow
  262. this._frontMaterial.alpha = 0.5; // Additive
  263. this._frontMaterial.renderBorders = true;
  264. }
  265. private _createPlateMaterial(mesh: Mesh) {
  266. this._plateMaterial = new StandardMaterial(this.name + "Plate Material", mesh.getScene());
  267. this._plateMaterial.specularColor = Color3.Black();
  268. }
  269. protected _affectMaterial(mesh: Mesh) {
  270. // Back
  271. if (this._shareMaterials) {
  272. if (!this._host._sharedMaterials["backFluentMaterial"]) {
  273. this._createBackMaterial(mesh);
  274. this._host._sharedMaterials["backFluentMaterial"] = this._backMaterial;
  275. } else {
  276. this._backMaterial = this._host._sharedMaterials["backFluentMaterial"] as FluentMaterial;
  277. }
  278. // Front
  279. if (!this._host._sharedMaterials["frontFluentMaterial"]) {
  280. this._createFrontMaterial(mesh);
  281. this._host._sharedMaterials["frontFluentMaterial"] = this._frontMaterial;
  282. } else {
  283. this._frontMaterial = this._host._sharedMaterials["frontFluentMaterial"] as FluentMaterial;
  284. }
  285. } else {
  286. this._createBackMaterial(mesh);
  287. this._createFrontMaterial(mesh);
  288. }
  289. this._createPlateMaterial(mesh);
  290. this._backPlate.material = this._backMaterial;
  291. this._frontPlate.material = this._frontMaterial;
  292. this._textPlate.material = this._plateMaterial;
  293. this._rebuildContent();
  294. }
  295. /**
  296. * Releases all associated resources
  297. */
  298. public dispose() {
  299. super.dispose(); // will dispose main mesh ie. back plate
  300. this._disposeTooltip();
  301. if (!this.shareMaterials) {
  302. this._backMaterial.dispose();
  303. this._frontMaterial.dispose();
  304. this._plateMaterial.dispose();
  305. if (this._pickedPointObserver) {
  306. this._host.onPickedPointChangedObservable.remove(this._pickedPointObserver);
  307. this._pickedPointObserver = null;
  308. }
  309. }
  310. }
  311. }