Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import {
- BufferGeometry,
- Float32BufferAttribute,
- LinearFilter,
- Mesh,
- MeshBasicMaterial,
- MeshStandardMaterial,
- ShaderMaterial,
- PlaneGeometry,
- // sRGBEncoding,
- SRGBColorSpace,
- Texture,
- Uint16BufferAttribute,
- CompressedArrayTexture,
- WebGLRenderer,
- Vector2,
- GLSL3
- } from 'three'
- import { DRACOLoader } from '../lib/DRACOLoader';
- import { KTX2Loader } from '../lib/KTX2Loader';
- export enum PlayMode {
- single = 'single',
- random = 'random',
- loop = 'loop',
- singleloop = 'singleloop'
- }
- type onMeshBufferingCallback = (progress: number) => void
- type onFrameShowCallback = (frame: number) => void
- export type PlayerConstructorArgs = {
- renderer: WebGLRenderer
- playMode?: PlayMode
- paths: Array<string>
- onMeshBuffering?: onMeshBufferingCallback
- onFrameShow?: onFrameShowCallback
- material?: MeshBasicMaterial | MeshBasicMaterial
- }
- export default class Player {
- static defaultWorkerURL = new URL('./worker.build.js', import.meta.url).href
- // Public Fields
- public renderer: WebGLRenderer
- public playMode: PlayMode
- // Three objects
- public mesh: Mesh
- public paths: Array<string>
- public material: MeshBasicMaterial
- public bufferGeometry: BufferGeometry
- public failMaterial?: MeshBasicMaterial
- // Private Fields
- private currentFrame: number = 0
- private currentSegment: number = 0
- private currentTrack: number = 0
- private meshMap: Map<number, BufferGeometry> = new Map()
- private textureMap: Map<number, CompressedArrayTexture> = new Map()
- private onMeshBuffering: onMeshBufferingCallback | null = null
- private onFrameShow: onFrameShowCallback | null = null
- private nextSegmentToRequest: number = 0;
- private manifestFilePath: string
- private meshFilePath: string
- private manifestData: any;
- private ktx2Loader: KTX2Loader
- private dracoLoader: DRACOLoader
- constructor({
- renderer,
- playMode,
- paths,
- onMeshBuffering,
- onFrameShow,
- material
- }: PlayerConstructorArgs) {
- this.renderer = renderer
- this.onMeshBuffering = onMeshBuffering
- this.onFrameShow = onFrameShow
- this.paths = paths
- // backwards-compat
- if (typeof playMode === 'number') {
- switch (playMode) {
- case 1:
- playMode = PlayMode.single
- break
- case 2:
- playMode = PlayMode.random
- break
- case 3:
- playMode = PlayMode.loop
- break
- case 4:
- playMode = PlayMode.singleloop
- break
- }
- }
- this.playMode = playMode || PlayMode.loop
- this.material = material
- this.mesh = new Mesh(new PlaneGeometry(0.00001, 0.00001), new MeshStandardMaterial({ color: 0xffffff }))
- this.ktx2Loader = new KTX2Loader();
- this.ktx2Loader.setTranscoderPath("/");
- this.ktx2Loader.detectSupport(this.renderer);
- this.dracoLoader = new DRACOLoader();
- this.dracoLoader.setDecoderPath("/");
- this.dracoLoader.preload();
- this.fetchManifest();
- this.fetchBuffers();
- }
- prepareNextLoop = () => {
- let nextTrack = -1;
- if (this.playMode == PlayMode.random) {
- nextTrack = Math.floor(Math.random() * this.paths.length)
- } else if (this.playMode == PlayMode.single) {
- nextTrack = (this.currentTrack + 1) % this.paths.length
- } else if (this.playMode == PlayMode.singleloop) {
- nextTrack = this.currentTrack
- } else {
- nextTrack = (this.currentTrack + 1) % this.paths.length
- }
- this.currentTrack = nextTrack
- this.nextSegmentToRequest = 0
- this.fetchManifest();
- }
- fetchManifest = () => {
- const manifestFilePath = this.paths[this.currentTrack].replace('uvol', 'manifest');
- const xhr = new XMLHttpRequest()
- xhr.onreadystatechange = () => {
- if (xhr.readyState !== 4) return
- this.manifestData = JSON.parse(xhr.responseText)
- }
- xhr.open('GET', manifestFilePath, false) // true for asynchronous
- xhr.send()
- }
- fetchBuffers = async () => {
- while (true) {
- if (this.nextSegmentToRequest == this.manifestData.sequences.length) {
- this.prepareNextLoop()
- }
- const segmentData = this.manifestData.sequences[this.nextSegmentToRequest];
- const currentFrameCount = segmentData.length - 2;
- const segmentStart = segmentData[0]; // also works as segmentOffset
- const segmentEnd = segmentData[currentFrameCount + 1];
- const batchSize = this.manifestData.batchSize;
- const meshFilePath = this.paths[this.currentTrack];
- const response = await fetch(meshFilePath, {
- headers: {
- range: `bytes=${segmentStart}-${segmentEnd}`
- }
- })
- const buffer = await (response as Response).arrayBuffer()
- const decodedTexture = await this.ktx2Loader._createTexture(buffer.slice(segmentData[0] - segmentData[0], segmentData[1] - segmentData[0]));
- const decodedMaterial = new ShaderMaterial({
- uniforms: {
- diffuse: {
- value: decodedTexture,
- },
- depth: {
- value: 0,
- },
- size: { value: new Vector2(4096, 4096) },
- },
- vertexShader: `uniform vec2 size;
- out vec2 vUv;
- void main() {
- gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
- vUv = uv;
- }`,
- fragmentShader: `precision highp float;
- precision highp int;
- precision highp sampler2DArray;
- uniform sampler2DArray diffuse;
- in vec2 vUv;
- uniform int depth;
- out vec4 outColor;
- void main() {
- vec4 color = texture( diffuse, vec3( vUv, depth ) );
- outColor = vec4(color.rgb, 1.0 );
- }`,
- glslVersion: GLSL3,
- });
- // @ts-ignore
- this.textureMap.set(this.nextSegmentToRequest, decodedMaterial);
- // console.log(`Set the material to segment ${this.nextSegmentToRequest}: ${decodedMaterial}`)
- for (let i = 1; i <= currentFrameCount; i++) {
- const dracoBegin = segmentData[i] - segmentStart;
- let dracoEnd = undefined;
- if (i != currentFrameCount) {
- dracoEnd = segmentData[i + 1] - segmentStart;
- }
- const dracoTaskConfig = {
- attributeIDs: {
- position: 'POSITION',
- normal: 'NORMAL',
- color: 'COLOR',
- uv: 'TEX_COORD',
- },
- attributeTypes: {
- position: 'Float32Array',
- normal: 'Float32Array',
- color: 'Float32Array',
- uv: 'Float32Array',
- },
- useUniqueIDs: false,
- vertexColorSpace: SRGBColorSpace,
- };
- try {
- const decodedDraco = await this.dracoLoader.decodeGeometry(
- buffer.slice(dracoBegin, dracoEnd),
- dracoTaskConfig
- );
- this.meshMap.set(this.nextSegmentToRequest * batchSize + i - 1, decodedDraco);
- } catch (e) {
- }
- }
- this.nextSegmentToRequest++;
- }
- }
- processFrame = () => {
- if (!this.meshMap.has(this.currentFrame) || (!this.mesh.material && !this.textureMap.get(this.currentSegment))) {
- this.onMeshBuffering?.(0)
- return;
- }
- this.mesh.geometry = this.meshMap.get(this.currentFrame)
- this.mesh.geometry.attributes.position.needsUpdate = true;
- const currentBatchSize = this.manifestData.sequences[this.currentSegment].length - 2;
- const offSet = this.currentFrame % currentBatchSize;
- // and ${this.currentSegment}:${offSet} texture frame
- console.log(`currently playing ${this.currentFrame} geometry frame. meshMap.size = ${this.meshMap.size}`);
- // console.log(`currentSegment = ${this.currentSegment}, material = ${this.textureMap.get(this.currentSegment)}`)
- if (offSet == 0 || !this.mesh.material) {
- // this video texture is a new segment
- // @ts-ignore
- this.mesh.material = this.textureMap.get(this.currentSegment);
- // @ts-ignore
- this.mesh.material.needsUpdate = true;
- }
- (this.mesh.material as ShaderMaterial).uniforms['depth'].value = offSet;
- if ((this.currentFrame + 1) == currentBatchSize) {
- this.currentSegment++;
- this.currentFrame++;
- } else {
- this.currentFrame++;
- }
- }
- removePlayedBuffer() {
- //remove played buffer
- // console.log(`before deleting frames (before ${this.currentFrame}), meshMap.size = ${this.meshMap.size}`)
- for (const [key, buffer] of this.meshMap.entries()) {
- if (key < this.currentFrame) {
- buffer.dispose()
- this.meshMap.delete(key)
- }
- }
- // console.log(`after deleting frames (before ${this.currentFrame}), meshMap.size = ${this.meshMap.size}`)
- // console.log(`before deleting frames (before ${this.currentSegment}), textureMap.size = ${this.textureMap.size}`)
- for (const [key, buffer] of this.textureMap.entries()) {
- if (key < this.currentSegment) {
- buffer.dispose()
- this.textureMap.delete(key)
- }
- }
- // console.log(`after deleting frames (before ${this.currentSegment}), textureMap.size = ${this.textureMap.size}`)
- }
- update = () => {
- this.processFrame()
- this.removePlayedBuffer()
- }
- dispose(): void {
- if (this.meshMap) {
- for (let i = 0; i < this.meshMap.size; i++) {
- const buffer = this.meshMap.get(i)
- if (buffer && buffer instanceof BufferGeometry) {
- buffer.dispose()
- }
- }
- this.meshMap.clear()
- }
- if (this.textureMap) {
- for (let i = 0; i < this.textureMap.size; i++) {
- const buffer = this.textureMap.get(i)
- if (buffer && buffer instanceof CompressedArrayTexture) {
- buffer.dispose()
- }
- }
- this.textureMap.clear()
- }
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement