textureDome.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. import { Scene } from "../scene";
  2. import { TransformNode } from "../Meshes/transformNode";
  3. import { Mesh } from "../Meshes/mesh";
  4. import { Texture } from "../Materials/Textures/texture";
  5. import { BackgroundMaterial } from "../Materials/Background/backgroundMaterial";
  6. import "../Meshes/Builders/sphereBuilder";
  7. import { Nullable } from "../types";
  8. import { Observer, Observable } from "../Misc/observable";
  9. import { Vector3 } from "../Maths/math.vector";
  10. import { Axis } from "../Maths/math";
  11. import { SphereBuilder } from "../Meshes/Builders/sphereBuilder";
  12. declare type Camera = import("../Cameras/camera").Camera;
  13. /**
  14. * Display a 360/180 degree texture on an approximately spherical surface, useful for VR applications or skyboxes.
  15. * As a subclass of TransformNode, this allow parenting to the camera or multiple textures with different locations in the scene.
  16. * This class achieves its effect with a Texture and a correctly configured BackgroundMaterial on an inverted sphere.
  17. * Potential additions to this helper include zoom and and non-infinite distance rendering effects.
  18. */
  19. export abstract class TextureDome<T extends Texture> extends TransformNode {
  20. /**
  21. * Define the source as a Monoscopic panoramic 360/180.
  22. */
  23. public static readonly MODE_MONOSCOPIC = 0;
  24. /**
  25. * Define the source as a Stereoscopic TopBottom/OverUnder panoramic 360/180.
  26. */
  27. public static readonly MODE_TOPBOTTOM = 1;
  28. /**
  29. * Define the source as a Stereoscopic Side by Side panoramic 360/180.
  30. */
  31. public static readonly MODE_SIDEBYSIDE = 2;
  32. private _halfDome: boolean = false;
  33. private _crossEye: boolean = false;
  34. protected _useDirectMapping = false;
  35. /**
  36. * The texture being displayed on the sphere
  37. */
  38. protected _texture: T;
  39. /**
  40. * Gets the texture being displayed on the sphere
  41. */
  42. public get texture(): T {
  43. return this._texture;
  44. }
  45. /**
  46. * Sets the texture being displayed on the sphere
  47. */
  48. public set texture(newTexture: T) {
  49. if (this._texture === newTexture) {
  50. return;
  51. }
  52. this._texture = newTexture;
  53. if (this._useDirectMapping) {
  54. this._texture.wrapU = Texture.CLAMP_ADDRESSMODE;
  55. this._texture.wrapV = Texture.CLAMP_ADDRESSMODE;
  56. this._material.diffuseTexture = this._texture;
  57. } else {
  58. this._texture.coordinatesMode = Texture.FIXED_EQUIRECTANGULAR_MIRRORED_MODE; // matches orientation
  59. this._texture.wrapV = Texture.CLAMP_ADDRESSMODE;
  60. this._material.reflectionTexture = this._texture;
  61. }
  62. }
  63. /**
  64. * The skybox material
  65. */
  66. protected _material: BackgroundMaterial;
  67. /**
  68. * The surface used for the dome
  69. */
  70. protected _mesh: Mesh;
  71. /**
  72. * Gets the mesh used for the dome.
  73. */
  74. public get mesh(): Mesh {
  75. return this._mesh;
  76. }
  77. /**
  78. * A mesh that will be used to mask the back of the dome in case it is a 180 degree movie.
  79. */
  80. private _halfDomeMask: Mesh;
  81. /**
  82. * The current fov(field of view) multiplier, 0.0 - 2.0. Defaults to 1.0. Lower values "zoom in" and higher values "zoom out".
  83. * Also see the options.resolution property.
  84. */
  85. public get fovMultiplier(): number {
  86. return this._material.fovMultiplier;
  87. }
  88. public set fovMultiplier(value: number) {
  89. this._material.fovMultiplier = value;
  90. }
  91. protected _textureMode = TextureDome.MODE_MONOSCOPIC;
  92. /**
  93. * Gets or set the current texture mode for the texture. It can be:
  94. * * TextureDome.MODE_MONOSCOPIC : Define the texture source as a Monoscopic panoramic 360.
  95. * * TextureDome.MODE_TOPBOTTOM : Define the texture source as a Stereoscopic TopBottom/OverUnder panoramic 360.
  96. * * TextureDome.MODE_SIDEBYSIDE : Define the texture source as a Stereoscopic Side by Side panoramic 360.
  97. */
  98. public get textureMode(): number {
  99. return this._textureMode;
  100. }
  101. /**
  102. * Sets the current texture mode for the texture. It can be:
  103. * * TextureDome.MODE_MONOSCOPIC : Define the texture source as a Monoscopic panoramic 360.
  104. * * TextureDome.MODE_TOPBOTTOM : Define the texture source as a Stereoscopic TopBottom/OverUnder panoramic 360.
  105. * * TextureDome.MODE_SIDEBYSIDE : Define the texture source as a Stereoscopic Side by Side panoramic 360.
  106. */
  107. public set textureMode(value: number) {
  108. if (this._textureMode === value) {
  109. return;
  110. }
  111. this._changeTextureMode(value);
  112. }
  113. /**
  114. * Is it a 180 degrees dome (half dome) or 360 texture (full dome)
  115. */
  116. public get halfDome(): boolean {
  117. return this._halfDome;
  118. }
  119. /**
  120. * Set the halfDome mode. If set, only the front (180 degrees) will be displayed and the back will be blacked out.
  121. */
  122. public set halfDome(enabled: boolean) {
  123. this._halfDome = enabled;
  124. this._halfDomeMask.setEnabled(enabled);
  125. }
  126. /**
  127. * Set the cross-eye mode. If set, images that can be seen when crossing eyes will render correctly
  128. */
  129. public set crossEye(enabled: boolean) {
  130. this._crossEye = enabled;
  131. }
  132. /**
  133. * Is it a cross-eye texture?
  134. */
  135. public get crossEye(): boolean {
  136. return this._crossEye;
  137. }
  138. /**
  139. * The background material of this dome.
  140. */
  141. public get material(): BackgroundMaterial {
  142. return this._material;
  143. }
  144. /**
  145. * Oberserver used in Stereoscopic VR Mode.
  146. */
  147. private _onBeforeCameraRenderObserver: Nullable<Observer<Camera>> = null;
  148. /**
  149. * Observable raised when an error occured while loading the 360 image
  150. */
  151. public onLoadErrorObservable = new Observable<string>();
  152. /**
  153. * Create an instance of this class and pass through the parameters to the relevant classes- Texture, StandardMaterial, and Mesh.
  154. * @param name Element's name, child elements will append suffixes for their own names.
  155. * @param textureUrlOrElement defines the url(s) or the (video) HTML element to use
  156. * @param options An object containing optional or exposed sub element properties
  157. */
  158. constructor(
  159. name: string,
  160. textureUrlOrElement: string | string[] | HTMLVideoElement,
  161. options: {
  162. resolution?: number;
  163. clickToPlay?: boolean;
  164. autoPlay?: boolean;
  165. loop?: boolean;
  166. size?: number;
  167. poster?: string;
  168. faceForward?: boolean;
  169. useDirectMapping?: boolean;
  170. halfDomeMode?: boolean;
  171. crossEyeMode?: boolean;
  172. generateMipMaps?: boolean;
  173. },
  174. scene: Scene,
  175. protected onError: Nullable<(message?: string, exception?: any) => void> = null
  176. ) {
  177. super(name, scene);
  178. scene = this.getScene();
  179. // set defaults and manage values
  180. name = name || "textureDome";
  181. options.resolution = Math.abs(options.resolution as any) | 0 || 32;
  182. options.clickToPlay = Boolean(options.clickToPlay);
  183. options.autoPlay = options.autoPlay === undefined ? true : Boolean(options.autoPlay);
  184. options.loop = options.loop === undefined ? true : Boolean(options.loop);
  185. options.size = Math.abs(options.size as any) || (scene.activeCamera ? scene.activeCamera.maxZ * 0.48 : 1000);
  186. if (options.useDirectMapping === undefined) {
  187. this._useDirectMapping = true;
  188. } else {
  189. this._useDirectMapping = options.useDirectMapping;
  190. }
  191. if (options.faceForward === undefined) {
  192. options.faceForward = true;
  193. }
  194. this._setReady(false);
  195. this._mesh = Mesh.CreateSphere(name + "_mesh", options.resolution, options.size, scene, false, Mesh.BACKSIDE);
  196. // configure material
  197. let material = (this._material = new BackgroundMaterial(name + "_material", scene));
  198. material.useEquirectangularFOV = true;
  199. material.fovMultiplier = 1.0;
  200. material.opacityFresnel = false;
  201. const texture = this._initTexture(textureUrlOrElement, scene, options);
  202. this.texture = texture;
  203. // configure mesh
  204. this._mesh.material = material;
  205. this._mesh.parent = this;
  206. // create a (disabled until needed) mask to cover unneeded segments of 180 texture.
  207. this._halfDomeMask = SphereBuilder.CreateSphere("", { slice: 0.5, diameter: options.size * 0.98, segments: options.resolution * 2, sideOrientation: Mesh.BACKSIDE }, scene);
  208. this._halfDomeMask.rotate(Axis.X, -Math.PI / 2);
  209. // set the parent, so it will always be positioned correctly AND will be disposed when the main sphere is disposed
  210. this._halfDomeMask.parent = this._mesh;
  211. this._halfDome = !!options.halfDomeMode;
  212. // enable or disable according to the settings
  213. this._halfDomeMask.setEnabled(this._halfDome);
  214. this._crossEye = !!options.crossEyeMode;
  215. // create
  216. this._texture.anisotropicFilteringLevel = 1;
  217. this._texture.onLoadObservable.addOnce(() => {
  218. this._setReady(true);
  219. });
  220. // Initial rotation
  221. if (options.faceForward && scene.activeCamera) {
  222. let camera = scene.activeCamera;
  223. let forward = Vector3.Forward();
  224. var direction = Vector3.TransformNormal(forward, camera.getViewMatrix());
  225. direction.normalize();
  226. this.rotation.y = Math.acos(Vector3.Dot(forward, direction));
  227. }
  228. this._changeTextureMode(this._textureMode);
  229. }
  230. protected abstract _initTexture(urlsOrElement: string | string[] | HTMLElement, scene: Scene, options: any): T;
  231. protected _changeTextureMode(value: number): void {
  232. this._scene.onBeforeCameraRenderObservable.remove(this._onBeforeCameraRenderObserver);
  233. this._textureMode = value;
  234. // Default Setup and Reset.
  235. this._texture.uScale = 1;
  236. this._texture.vScale = 1;
  237. this._texture.uOffset = 0;
  238. this._texture.vOffset = 0;
  239. this._texture.vAng = 0;
  240. switch (value) {
  241. case TextureDome.MODE_MONOSCOPIC:
  242. if (this._halfDome) {
  243. this._texture.uScale = 2;
  244. this._texture.uOffset = -1;
  245. }
  246. break;
  247. case TextureDome.MODE_SIDEBYSIDE:
  248. // in half-dome mode the uScale should be double of 360 texture
  249. // Use 0.99999 to boost perf by not switching program
  250. this._texture.uScale = this._halfDome ? 0.99999 : 0.5;
  251. const rightOffset = this._halfDome ? 0.0 : 0.5;
  252. const leftOffset = this._halfDome ? -0.5 : 0.0;
  253. this._onBeforeCameraRenderObserver = this._scene.onBeforeCameraRenderObservable.add((camera) => {
  254. let isRightCamera = camera.isRightCamera;
  255. if (this._crossEye) {
  256. isRightCamera = !isRightCamera;
  257. }
  258. if (isRightCamera) {
  259. this._texture.uOffset = rightOffset;
  260. } else {
  261. this._texture.uOffset = leftOffset;
  262. }
  263. });
  264. break;
  265. case TextureDome.MODE_TOPBOTTOM:
  266. // in half-dome mode the vScale should be double of 360 texture
  267. // Use 0.99999 to boost perf by not switching program
  268. this._texture.vScale = this._halfDome ? 0.99999 : 0.5;
  269. this._onBeforeCameraRenderObserver = this._scene.onBeforeCameraRenderObservable.add((camera) => {
  270. let isRightCamera = camera.isRightCamera;
  271. // allow "cross-eye" if left and right were switched in this mode
  272. if (this._crossEye) {
  273. isRightCamera = !isRightCamera;
  274. }
  275. this._texture.vOffset = isRightCamera ? 0.5 : 0.0;
  276. });
  277. break;
  278. }
  279. }
  280. /**
  281. * Releases resources associated with this node.
  282. * @param doNotRecurse Set to true to not recurse into each children (recurse into each children by default)
  283. * @param disposeMaterialAndTextures Set to true to also dispose referenced materials and textures (false by default)
  284. */
  285. public dispose(doNotRecurse?: boolean, disposeMaterialAndTextures = false): void {
  286. this._texture.dispose();
  287. this._mesh.dispose();
  288. this._material.dispose();
  289. this._scene.onBeforeCameraRenderObservable.remove(this._onBeforeCameraRenderObserver);
  290. this.onLoadErrorObservable.clear();
  291. super.dispose(doNotRecurse, disposeMaterialAndTextures);
  292. }
  293. }