Advertisement
AppajiC

V2Player

May 27th, 2023
908
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. import {
  2.     BufferGeometry,
  3.     Float32BufferAttribute,
  4.     LinearFilter,
  5.     Mesh,
  6.     MeshBasicMaterial,
  7.     MeshStandardMaterial,
  8.     ShaderMaterial,
  9.     PlaneGeometry,
  10.     // sRGBEncoding,
  11.     SRGBColorSpace,
  12.     Texture,
  13.     Uint16BufferAttribute,
  14.     CompressedArrayTexture,
  15.     WebGLRenderer,
  16.     Vector2,
  17.     GLSL3
  18. } from 'three'
  19.  
  20. import { DRACOLoader } from '../lib/DRACOLoader';
  21. import { KTX2Loader } from '../lib/KTX2Loader';
  22.  
  23.  
  24. export enum PlayMode {
  25.     single = 'single',
  26.     random = 'random',
  27.     loop = 'loop',
  28.     singleloop = 'singleloop'
  29. }
  30.  
  31. type onMeshBufferingCallback = (progress: number) => void
  32. type onFrameShowCallback = (frame: number) => void
  33.  
  34. export type PlayerConstructorArgs = {
  35.     renderer: WebGLRenderer
  36.     playMode?: PlayMode
  37.     paths: Array<string>
  38.     onMeshBuffering?: onMeshBufferingCallback
  39.     onFrameShow?: onFrameShowCallback
  40.     material?: MeshBasicMaterial | MeshBasicMaterial
  41. }
  42.  
  43. export default class Player {
  44.     static defaultWorkerURL = new URL('./worker.build.js', import.meta.url).href
  45.  
  46.     // Public Fields
  47.     public renderer: WebGLRenderer
  48.     public playMode: PlayMode
  49.  
  50.     // Three objects
  51.     public mesh: Mesh
  52.     public paths: Array<string>
  53.     public material: MeshBasicMaterial
  54.     public bufferGeometry: BufferGeometry
  55.     public failMaterial?: MeshBasicMaterial
  56.  
  57.     // Private Fields
  58.     private currentFrame: number = 0
  59.     private currentSegment: number = 0
  60.     private currentTrack: number = 0
  61.     private meshMap: Map<number, BufferGeometry> = new Map()
  62.     private textureMap: Map<number, CompressedArrayTexture> = new Map()
  63.     private onMeshBuffering: onMeshBufferingCallback | null = null
  64.     private onFrameShow: onFrameShowCallback | null = null
  65.     private nextSegmentToRequest: number = 0;
  66.  
  67.     private manifestFilePath: string
  68.     private meshFilePath: string
  69.     private manifestData: any;
  70.     private ktx2Loader: KTX2Loader
  71.     private dracoLoader: DRACOLoader
  72.  
  73.  
  74.  
  75.     constructor({
  76.         renderer,
  77.         playMode,
  78.         paths,
  79.         onMeshBuffering,
  80.         onFrameShow,
  81.         material
  82.     }: PlayerConstructorArgs) {
  83.         this.renderer = renderer
  84.  
  85.         this.onMeshBuffering = onMeshBuffering
  86.         this.onFrameShow = onFrameShow
  87.  
  88.         this.paths = paths
  89.  
  90.         // backwards-compat
  91.         if (typeof playMode === 'number') {
  92.             switch (playMode) {
  93.                 case 1:
  94.                     playMode = PlayMode.single
  95.                     break
  96.                 case 2:
  97.                     playMode = PlayMode.random
  98.                     break
  99.                 case 3:
  100.                     playMode = PlayMode.loop
  101.                     break
  102.                 case 4:
  103.                     playMode = PlayMode.singleloop
  104.                     break
  105.             }
  106.         }
  107.  
  108.         this.playMode = playMode || PlayMode.loop
  109.         this.material = material
  110.         this.mesh = new Mesh(new PlaneGeometry(0.00001, 0.00001), new MeshStandardMaterial({ color: 0xffffff }))
  111.         this.ktx2Loader = new KTX2Loader();
  112.         this.ktx2Loader.setTranscoderPath("/");
  113.         this.ktx2Loader.detectSupport(this.renderer);
  114.  
  115.         this.dracoLoader = new DRACOLoader();
  116.         this.dracoLoader.setDecoderPath("/");
  117.         this.dracoLoader.preload();
  118.         this.fetchManifest();
  119.         this.fetchBuffers();
  120.     }
  121.  
  122.     prepareNextLoop = () => {
  123.         let nextTrack = -1;
  124.         if (this.playMode == PlayMode.random) {
  125.             nextTrack = Math.floor(Math.random() * this.paths.length)
  126.         } else if (this.playMode == PlayMode.single) {
  127.             nextTrack = (this.currentTrack + 1) % this.paths.length
  128.         } else if (this.playMode == PlayMode.singleloop) {
  129.             nextTrack = this.currentTrack
  130.         } else {
  131.             nextTrack = (this.currentTrack + 1) % this.paths.length
  132.         }
  133.         this.currentTrack = nextTrack
  134.         this.nextSegmentToRequest = 0
  135.         this.fetchManifest();
  136.     }
  137.  
  138.     fetchManifest = () => {
  139.         const manifestFilePath = this.paths[this.currentTrack].replace('uvol', 'manifest');
  140.         const xhr = new XMLHttpRequest()
  141.         xhr.onreadystatechange = () => {
  142.             if (xhr.readyState !== 4) return
  143.             this.manifestData = JSON.parse(xhr.responseText)
  144.         }
  145.  
  146.         xhr.open('GET', manifestFilePath, false) // true for asynchronous
  147.         xhr.send()
  148.     }
  149.  
  150.     fetchBuffers = async () => {
  151.         while (true) {
  152.             if (this.nextSegmentToRequest == this.manifestData.sequences.length) {
  153.                 this.prepareNextLoop()
  154.             }
  155.             const segmentData = this.manifestData.sequences[this.nextSegmentToRequest];
  156.             const currentFrameCount = segmentData.length - 2;
  157.             const segmentStart = segmentData[0]; // also works as segmentOffset
  158.             const segmentEnd = segmentData[currentFrameCount + 1];
  159.             const batchSize = this.manifestData.batchSize;
  160.  
  161.             const meshFilePath = this.paths[this.currentTrack];
  162.             const response = await fetch(meshFilePath, {
  163.                 headers: {
  164.                     range: `bytes=${segmentStart}-${segmentEnd}`
  165.                 }
  166.             })
  167.             const buffer = await (response as Response).arrayBuffer()
  168.             const decodedTexture = await this.ktx2Loader._createTexture(buffer.slice(segmentData[0] - segmentData[0], segmentData[1] - segmentData[0]));
  169.             const decodedMaterial = new ShaderMaterial({
  170.                 uniforms: {
  171.                     diffuse: {
  172.                         value: decodedTexture,
  173.                     },
  174.                     depth: {
  175.                         value: 0,
  176.                     },
  177.                     size: { value: new Vector2(4096, 4096) },
  178.                 },
  179.                 vertexShader: `uniform vec2 size;
  180.                 out vec2 vUv;
  181.                
  182.                 void main() {
  183.                     gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
  184.                     vUv = uv;
  185.                 }`,
  186.                 fragmentShader: `precision highp float;
  187.                 precision highp int;
  188.                 precision highp sampler2DArray;
  189.                
  190.                 uniform sampler2DArray diffuse;
  191.                 in vec2 vUv;
  192.                 uniform int depth;
  193.                 out vec4 outColor;
  194.                
  195.                 void main() {
  196.                     vec4 color = texture( diffuse, vec3( vUv, depth ) );
  197.                     outColor = vec4(color.rgb, 1.0 );
  198.                 }`,
  199.                 glslVersion: GLSL3,
  200.             });
  201.             // @ts-ignore
  202.             this.textureMap.set(this.nextSegmentToRequest, decodedMaterial);
  203.             // console.log(`Set the material to segment ${this.nextSegmentToRequest}: ${decodedMaterial}`)
  204.  
  205.             for (let i = 1; i <= currentFrameCount; i++) {
  206.                 const dracoBegin = segmentData[i] - segmentStart;
  207.                 let dracoEnd = undefined;
  208.                 if (i != currentFrameCount) {
  209.                     dracoEnd = segmentData[i + 1] - segmentStart;
  210.                 }
  211.                 const dracoTaskConfig = {
  212.                     attributeIDs: {
  213.                         position: 'POSITION',
  214.                         normal: 'NORMAL',
  215.                         color: 'COLOR',
  216.                         uv: 'TEX_COORD',
  217.                     },
  218.                     attributeTypes: {
  219.                         position: 'Float32Array',
  220.                         normal: 'Float32Array',
  221.                         color: 'Float32Array',
  222.                         uv: 'Float32Array',
  223.                     },
  224.                     useUniqueIDs: false,
  225.                     vertexColorSpace: SRGBColorSpace,
  226.                 };
  227.                 try {
  228.                     const decodedDraco = await this.dracoLoader.decodeGeometry(
  229.                         buffer.slice(dracoBegin, dracoEnd),
  230.                         dracoTaskConfig
  231.                     );
  232.                     this.meshMap.set(this.nextSegmentToRequest * batchSize + i - 1, decodedDraco);
  233.                 } catch (e) {
  234.                 }
  235.  
  236.             }
  237.             this.nextSegmentToRequest++;
  238.         }
  239.     }
  240.  
  241.     processFrame = () => {
  242.         if (!this.meshMap.has(this.currentFrame) || (!this.mesh.material && !this.textureMap.get(this.currentSegment))) {
  243.             this.onMeshBuffering?.(0)
  244.             return;
  245.         }
  246.         this.mesh.geometry = this.meshMap.get(this.currentFrame)
  247.         this.mesh.geometry.attributes.position.needsUpdate = true;
  248.         const currentBatchSize = this.manifestData.sequences[this.currentSegment].length - 2;
  249.         const offSet = this.currentFrame % currentBatchSize;
  250.         // and ${this.currentSegment}:${offSet} texture frame
  251.         console.log(`currently playing ${this.currentFrame} geometry frame. meshMap.size = ${this.meshMap.size}`);
  252.         // console.log(`currentSegment = ${this.currentSegment}, material = ${this.textureMap.get(this.currentSegment)}`)
  253.         if (offSet == 0 || !this.mesh.material) {
  254.             // this video texture is a new segment
  255.             // @ts-ignore
  256.             this.mesh.material = this.textureMap.get(this.currentSegment);
  257.             // @ts-ignore
  258.             this.mesh.material.needsUpdate = true;
  259.         }
  260.         (this.mesh.material as ShaderMaterial).uniforms['depth'].value = offSet;
  261.         if ((this.currentFrame + 1) == currentBatchSize) {
  262.             this.currentSegment++;
  263.             this.currentFrame++;
  264.         } else {
  265.             this.currentFrame++;
  266.         }
  267.     }
  268.  
  269.     removePlayedBuffer() {
  270.         //remove played buffer
  271.         // console.log(`before deleting frames (before ${this.currentFrame}), meshMap.size = ${this.meshMap.size}`)
  272.         for (const [key, buffer] of this.meshMap.entries()) {
  273.             if (key < this.currentFrame) {
  274.                 buffer.dispose()
  275.                 this.meshMap.delete(key)
  276.             }
  277.         }
  278.         // console.log(`after deleting frames (before ${this.currentFrame}), meshMap.size = ${this.meshMap.size}`)
  279.  
  280.         // console.log(`before deleting frames (before ${this.currentSegment}), textureMap.size = ${this.textureMap.size}`)
  281.         for (const [key, buffer] of this.textureMap.entries()) {
  282.             if (key < this.currentSegment) {
  283.                 buffer.dispose()
  284.                 this.textureMap.delete(key)
  285.             }
  286.         }
  287.         // console.log(`after deleting frames (before ${this.currentSegment}), textureMap.size = ${this.textureMap.size}`)
  288.     }
  289.  
  290.     update = () => {
  291.         this.processFrame()
  292.         this.removePlayedBuffer()
  293.     }
  294.  
  295.     dispose(): void {
  296.         if (this.meshMap) {
  297.             for (let i = 0; i < this.meshMap.size; i++) {
  298.                 const buffer = this.meshMap.get(i)
  299.                 if (buffer && buffer instanceof BufferGeometry) {
  300.                     buffer.dispose()
  301.                 }
  302.             }
  303.             this.meshMap.clear()
  304.         }
  305.  
  306.         if (this.textureMap) {
  307.             for (let i = 0; i < this.textureMap.size; i++) {
  308.                 const buffer = this.textureMap.get(i)
  309.                 if (buffer && buffer instanceof CompressedArrayTexture) {
  310.                     buffer.dispose()
  311.                 }
  312.             }
  313.             this.textureMap.clear()
  314.         }
  315.     }
  316. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement