videoDome.ts 11 KB

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