holographicButton.ts 11 KB

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