Explorar o código

EXRLoader: Add HTJ2K compression support.

Mr.doob hai 3 meses
pai
achega
828a6ef1cf

BIN=BIN
examples/jsm/libs/openjph/libopenjph.wasm


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 8 - 0
examples/jsm/libs/openjph/openjph.module.js


+ 180 - 8
examples/jsm/loaders/EXRLoader.js

@@ -10,6 +10,9 @@ import {
 	RGBAFormat
 } from 'three';
 import * as fflate from '../libs/fflate.module.js';
+import OpenJPHModule from '../libs/openjph/openjph.module.js';
+
+let _openjph;
 
 // Referred to the original Industrial Light & Magic OpenEXR implementation and the TinyEXR / Syoyo Fujita
 // implementation, so I have preserved their copyright notices.
@@ -84,11 +87,15 @@ import * as fflate from '../libs/fflate.module.js';
  * A loader for the OpenEXR texture format.
  *
  * `EXRLoader` currently supports uncompressed, ZIP(S), RLE, PIZ and DWA/B compression.
+ * HTJ2K (High Throughput JPEG 2000) compression is supported when the OpenJPH WASM module is loaded.
  * Supports reading as UnsignedByte, HalfFloat and Float type data texture.
  *
  * ```js
  * const loader = new EXRLoader();
  * const texture = await loader.loadAsync( 'textures/memorial.exr' );
+ * 
+ * // For HTJ2K support:
+ * EXRLoader.setOpenJPH( openjphModule );
  * ```
  *
  * @augments DataTextureLoader
@@ -129,7 +136,7 @@ class EXRLoader extends DataTextureLoader {
 	 * @param {ArrayBuffer} buffer - The raw texture data.
 	 * @return {DataTextureLoader~TexData} An object representing the parsed texture data.
 	 */
-	parse( buffer ) {
+	async parse( buffer ) {
 
 		const USHORT_RANGE = ( 1 << 16 );
 		const BITMAP_SIZE = ( USHORT_RANGE >> 3 );
@@ -1777,7 +1784,165 @@ class EXRLoader extends DataTextureLoader {
 
 		}
 
-		function parseNullTerminatedString( buffer, offset ) {
+	async function uncompressHTJ2K( info ) {
+
+		// HTJ2K (High Throughput JPEG 2000) decompression
+		// Reference: ISO/IEC 15444-15 / ITU-T T.814
+
+		// Lazy initialization of OpenJPH WASM module
+		let openjph;
+
+		if ( ! _openjph ) {
+
+			_openjph = new Promise( async ( resolve ) => {
+
+				const openjph = await OpenJPHModule();
+				resolve( openjph );
+
+			} );
+
+		}
+
+		openjph = await _openjph;
+
+		const compressedData = info.array;
+		const wasm = openjph;
+		const dv = new DataView( compressedData.buffer, compressedData.byteOffset, compressedData.byteLength );
+
+		// Parse HTJ2K header
+		let offset = 0;
+
+		// Read magic number (0x4854 = 'HT')
+		const magic = dv.getUint16( offset, false ); // big-endian
+		offset += 2;
+
+		if ( magic !== 0x4854 ) {
+
+			throw new Error(
+				`THREE.EXRLoader: Invalid HTJ2K magic number: 0x${magic.toString( 16 )}. Expected 0x4854.`
+			);
+
+		}
+
+		// Read payload length
+		const payloadLength = dv.getUint32( offset, false ); // big-endian
+		offset += 4;
+
+		// Read number of channels
+		const numChannels = dv.getUint16( offset, false ); // big-endian
+		offset += 2;
+
+		// Read channel map
+		const channelMap = new Uint16Array( numChannels );
+		for ( let i = 0; i < numChannels; i ++ ) {
+
+			channelMap[ i ] = dv.getUint16( offset, false ); // big-endian
+			offset += 2;
+
+		}
+
+		const headerSize = offset;
+
+		// Extract JPEG 2000 codestream (data after header)
+		const codestreamSize = compressedData.byteLength - headerSize;
+		const codestreamData = new Uint8Array( compressedData.buffer, compressedData.byteOffset + headerSize, codestreamSize );
+
+		// Create J2K data structure
+		const j2kData = wasm._create_j2c_data();
+
+		if ( ! j2kData ) {
+
+			throw new Error( 'THREE.EXRLoader: Failed to create J2K data structure.' );
+
+		}
+
+		try {
+
+			// Allocate memory for codestream
+			const dataPtr = wasm._malloc( codestreamSize );
+			wasm.HEAPU8.set( codestreamData, dataPtr );
+
+			// Initialize codestream
+			wasm._init_j2c_data( j2kData, dataPtr, codestreamSize );
+
+			// Free input data
+			wasm._free( dataPtr );
+
+			// Parse codestream
+			wasm._parse_j2c_data( j2kData );
+
+			// Get dimensions from codestream
+			const width = wasm._get_width( j2kData );
+			const height = wasm._get_height( j2kData );
+			const numComponents = wasm._get_num_components( j2kData );
+
+			// Determine bytes per element from EXR info
+			const bytesPerElement = info.type === 1 ? 2 : 4; // HALF=1, FLOAT=2
+			const totalBytes = width * height * numChannels * bytesPerElement;
+
+			// Allocate output buffer
+			const outputBuffer = new ArrayBuffer( totalBytes );
+			const outputView = new DataView( outputBuffer );
+
+			// Decode scanlines
+			let outputOffset = 0;
+
+			for ( let y = 0; y < height; y ++ ) {
+
+				for ( let c = 0; c < numComponents; c ++ ) {
+
+					const linePtr = wasm._pull_j2c_line( j2kData );
+
+					if ( ! linePtr ) {
+
+						throw new Error( `THREE.EXRLoader: Failed to decode HTJ2K line ${y}, component ${c}.` );
+
+					}
+
+					// Copy and convert line data
+					for ( let x = 0; x < width; x ++ ) {
+
+						const value = wasm.HEAP32[ ( linePtr >> 2 ) + x ];
+
+						if ( bytesPerElement === 2 ) {
+
+							// HALF float - store as uint16
+							outputView.setUint16( outputOffset, value, true );
+							outputOffset += 2;
+
+						} else {
+
+							// FLOAT - store as int32 (will be reinterpreted as float)
+							outputView.setInt32( outputOffset, value, true );
+							outputOffset += 4;
+
+						}
+
+					}
+
+				}
+
+			}
+
+			// Release J2K data
+			wasm._release_j2c_data( j2kData );
+
+			return outputView;
+
+		} catch ( error ) {
+
+			// Clean up on error
+			if ( j2kData ) {
+
+				wasm._release_j2c_data( j2kData );
+
+			}
+
+			throw error;
+
+		}
+
+	}		function parseNullTerminatedString( buffer, offset ) {
 
 			const uintBuffer = new Uint8Array( buffer );
 			let endOffset = 0;
@@ -1995,7 +2160,9 @@ class EXRLoader extends DataTextureLoader {
 				'B44_COMPRESSION',
 				'B44A_COMPRESSION',
 				'DWAA_COMPRESSION',
-				'DWAB_COMPRESSION'
+				'DWAB_COMPRESSION',
+				'UNKNOWN_COMPRESSION', // 10
+				'HTJ2K_COMPRESSION'     // 11 - High Throughput JPEG 2000 (ISO/IEC 15444-15)
 			];
 
 			const compression = parseUint8( dataView, offset );
@@ -2210,7 +2377,7 @@ class EXRLoader extends DataTextureLoader {
 
 		}
 
-		function parseTiles() {
+		async function parseTiles() {
 
 			const EXRDecoder = this;
 			const offset = EXRDecoder.offset;
@@ -2230,7 +2397,7 @@ class EXRLoader extends DataTextureLoader {
 
 				const bytesBlockLine = EXRDecoder.columns * EXRDecoder.totalBytes;
 				const isCompressed = EXRDecoder.size < EXRDecoder.lines * bytesBlockLine;
-				const viewer = isCompressed ? EXRDecoder.uncompress( EXRDecoder ) : uncompressRAW( EXRDecoder );
+				const viewer = isCompressed ? await EXRDecoder.uncompress( EXRDecoder ) : uncompressRAW( EXRDecoder );
 
 				offset.value += EXRDecoder.size;
 
@@ -2264,7 +2431,7 @@ class EXRLoader extends DataTextureLoader {
 
 		}
 
-		function parseScanline() {
+		async function parseScanline() {
 
 			const EXRDecoder = this;
 			const offset = EXRDecoder.offset;
@@ -2278,7 +2445,7 @@ class EXRLoader extends DataTextureLoader {
 
 				const bytesPerLine = EXRDecoder.columns * EXRDecoder.totalBytes;
 				const isCompressed = EXRDecoder.size < EXRDecoder.lines * bytesPerLine;
-				const viewer = isCompressed ? EXRDecoder.uncompress( EXRDecoder ) : uncompressRAW( EXRDecoder );
+				const viewer = isCompressed ? await EXRDecoder.uncompress( EXRDecoder ) : uncompressRAW( EXRDecoder );
 
 				offset.value += EXRDecoder.size;
 
@@ -2447,6 +2614,11 @@ class EXRLoader extends DataTextureLoader {
 					EXRDecoder.uncompress = uncompressDWA;
 					break;
 
+				case 'HTJ2K_COMPRESSION':
+					EXRDecoder.blockHeight = 32; // Typical block height for HTJ2K, may vary
+					EXRDecoder.uncompress = uncompressHTJ2K;
+					break;
+
 				default:
 					throw new Error( 'EXRLoader.parse: ' + EXRHeader.compression + ' is unsupported' );
 
@@ -2702,7 +2874,7 @@ class EXRLoader extends DataTextureLoader {
 		const EXRDecoder = setupDecoder( EXRHeader, bufferDataView, uInt8Array, offset, this.type, this.outputFormat );
 
 		// parse input data
-		EXRDecoder.decode();
+		await EXRDecoder.decode();
 
 		// output texture post-processing
 		if ( EXRDecoder.shouldExpand ) {

+ 284 - 0
examples/jsm/loaders/JPHLoader.js

@@ -0,0 +1,284 @@
+import {
+	DataTexture,
+	FileLoader,
+	FloatType,
+	HalfFloatType,
+	LinearFilter,
+	LinearSRGBColorSpace,
+	Loader
+} from 'three';
+import OpenJPHModule from '../libs/openjph/openjph.module.js';
+
+let _openjph;
+
+/**
+ * Loader for JPEG 2000 codestreams using OpenJPH (High-Throughput JPEG 2000).
+ * 
+ * This loader wraps the OpenJPH WebAssembly module to decode HTJ2K (High Throughput JPEG 2000)
+ * compressed image data. HTJ2K is defined in ISO/IEC 15444-15 / ITU-T T.814.
+ * 
+ * Features:
+ * - Supports lossless and lossy JPEG 2000 compression
+ * - Handles 8-bit, 16-bit, and 32-bit data
+ * - Multiple component/channel support
+ * - Optional resolution reduction for faster decoding
+ * 
+ * Usage:
+ * ```javascript
+ * import { JPHLoader } from 'three/examples/jsm/loaders/JPHLoader.js';
+ * 
+ * // Initialize with OpenJPH WASM module
+ * const loader = new JPHLoader();
+ * 
+ * // Load WASM module first (required)
+ * await loader.setWASMModule(openjphModule);
+ * 
+ * // Load a J2K/JP2 file
+ * loader.load('texture.j2k', (texture) => {
+ *   // Use texture
+ * });
+ * 
+ * // Or decode from buffer directly
+ * const texture = loader.parse(arrayBuffer);
+ * ```
+ * 
+ * OpenJPH WASM Module:
+ * The OpenJPH library must be compiled to WebAssembly using Emscripten.
+ * See: https://github.com/aous72/OpenJPH/tree/main/subprojects/js
+ * 
+ * Build instructions:
+ * ```bash
+ * git clone https://github.com/aous72/OpenJPH.git
+ * cd OpenJPH
+ * mkdir build && cd build
+ * emcmake cmake -DCMAKE_BUILD_TYPE=Release ..
+ * emmake make
+ * ```
+ * 
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+class JPHLoader extends Loader {
+
+	constructor( manager ) {
+
+		super( manager );
+
+		this.type = FloatType;
+
+	}
+
+	load( url, onLoad, onProgress, onError ) {
+
+		const scope = this;
+
+		const loader = new FileLoader( this.manager );
+		loader.setPath( this.path );
+		loader.setResponseType( 'arraybuffer' );
+		loader.setRequestHeader( this.requestHeader );
+		loader.setWithCredentials( this.withCredentials );
+
+		loader.load( url, function ( buffer ) {
+
+			try {
+
+				onLoad( scope.parse( buffer ) );
+
+			} catch ( e ) {
+
+				if ( onError ) {
+
+					onError( e );
+
+				} else {
+
+					console.error( e );
+
+				}
+
+				scope.manager.itemError( url );
+
+			}
+
+		}, onProgress, onError );
+
+	}
+
+	/**
+	 * Parses JPEG 2000 codestream data and returns a DataTexture.
+	 * 
+	 * @param {ArrayBuffer} buffer - The J2K/JP2 file data
+	 * @param {Object} options - Decoding options
+	 * @param {number} options.skipResolutionsRead - Number of resolutions to skip during read (default: 0)
+	 * @param {number} options.skipResolutionsRecon - Number of resolutions to skip during reconstruction (default: 0)
+	 * @returns {DataTexture} The decoded texture
+	 */
+	async parse( buffer, options = {} ) {
+
+		// Lazy initialization of OpenJPH WASM module
+		let openjph;
+
+		if ( ! _openjph ) {
+
+			_openjph = new Promise( async ( resolve ) => {
+
+				const openjph = await OpenJPHModule();
+				resolve( openjph );
+
+			} );
+
+		}
+
+		openjph = await _openjph;
+
+		const {
+			skipResolutionsRead = 0,
+			skipResolutionsRecon = 0
+		} = options;
+
+		const wasm = openjph;
+
+		// Create J2K data structure
+		const j2kData = wasm._create_j2c_data();
+
+		if ( ! j2kData ) {
+
+			throw new Error( 'THREE.JPHLoader: Failed to create J2K data structure.' );
+
+		}
+
+		try {
+
+			// Allocate memory for input data
+			const dataPtr = wasm._malloc( buffer.byteLength );
+			wasm.HEAPU8.set( new Uint8Array( buffer ), dataPtr );
+
+			// Initialize codestream
+			wasm._init_j2c_data( j2kData, dataPtr, buffer.byteLength );
+
+			// Free input data
+			wasm._free( dataPtr );
+
+			// Get codestream info
+			const width = wasm._get_width( j2kData );
+			const height = wasm._get_height( j2kData );
+			const numComponents = wasm._get_num_components( j2kData );
+			const bitDepth = wasm._get_bit_depth( j2kData, 0 );
+			const isSigned = wasm._is_signed( j2kData, 0 );
+
+			// Apply resolution reduction if requested
+			if ( skipResolutionsRead > 0 || skipResolutionsRecon > 0 ) {
+
+				wasm._restrict_input_resolution( j2kData, skipResolutionsRead, skipResolutionsRecon );
+
+			}
+
+			// Parse and prepare for decoding
+			wasm._parse_j2c_data( j2kData );
+
+			// Determine output data type
+			let outputData;
+			let textureType;
+
+			if ( bitDepth <= 8 ) {
+
+				// 8-bit output
+				outputData = new Uint8Array( width * height * numComponents );
+				textureType = this.type; // Use default type
+
+			} else if ( bitDepth <= 16 ) {
+
+				// 16-bit output - use Half or Float
+				outputData = new Uint16Array( width * height * numComponents );
+				textureType = HalfFloatType;
+
+			} else {
+
+				// 32-bit float output
+				outputData = new Float32Array( width * height * numComponents );
+				textureType = FloatType;
+
+			}
+
+			// Decode scanlines
+			let offset = 0;
+
+			for ( let y = 0; y < height; y ++ ) {
+
+				for ( let c = 0; c < numComponents; c ++ ) {
+
+					const linePtr = wasm._pull_j2c_line( j2kData );
+
+					if ( ! linePtr ) {
+
+						throw new Error( `THREE.JPHLoader: Failed to decode line ${y}, component ${c}.` );
+
+					}
+
+					// Copy line data
+					for ( let x = 0; x < width; x ++ ) {
+
+						const value = wasm.HEAP32[ ( linePtr >> 2 ) + x ];
+
+						// Convert signed to unsigned if needed
+						if ( isSigned ) {
+
+							outputData[ offset ++ ] = value + ( 1 << ( bitDepth - 1 ) );
+
+						} else {
+
+							outputData[ offset ++ ] = value;
+
+						}
+
+					}
+
+				}
+
+			}
+
+			// Release J2K data
+			wasm._release_j2c_data( j2kData );
+
+			// Create and configure texture
+			const texture = new DataTexture( outputData, width, height );
+			texture.type = textureType;
+			texture.minFilter = LinearFilter;
+			texture.magFilter = LinearFilter;
+			texture.generateMipmaps = false;
+			texture.needsUpdate = true;
+
+			// Set color space based on number of components
+			if ( numComponents >= 3 ) {
+
+				texture.colorSpace = LinearSRGBColorSpace;
+
+			}
+
+			return texture;
+
+		} catch ( error ) {
+
+			// Clean up on error
+			if ( j2kData ) {
+
+				wasm._release_j2c_data( j2kData );
+
+			}
+
+			throw error;
+
+		}
+
+	}
+
+	setDataType( value ) {
+
+		this.type = value;
+		return this;
+
+	}
+
+}
+
+export { JPHLoader };

+ 180 - 0
examples/webgl_loader_exr_htj2k.html

@@ -0,0 +1,180 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - loaders - EXR with HTJ2K compression</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<link type="text/css" rel="stylesheet" href="main.css">
+	</head>
+
+	<body>
+		<div id="info">
+			<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> -
+			EXR loader with HTJ2K compression support
+		</div>
+
+		<script type="importmap">
+			{
+				"imports": {
+					"three": "../build/three.module.js",
+					"three/addons/": "./jsm/"
+				}
+			}
+		</script>
+
+		<script type="module">
+
+			import * as THREE from 'three';
+			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+			import { EXRLoader } from 'three/addons/loaders/EXRLoader.js';
+			import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
+
+			let camera, scene, renderer;
+
+			init();
+
+			async function init() {
+
+				const container = document.createElement( 'div' );
+				document.body.appendChild( container );
+
+				camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.25, 20 );
+				camera.position.set( - 1.8, 0.6, 2.7 );
+
+				scene = new THREE.Scene();
+
+				// Add loading information
+				const loadingDiv = document.createElement( 'div' );
+				loadingDiv.style.position = 'absolute';
+				loadingDiv.style.top = '50%';
+				loadingDiv.style.left = '50%';
+				loadingDiv.style.transform = 'translate(-50%, -50%)';
+				loadingDiv.style.color = 'white';
+				loadingDiv.style.fontSize = '20px';
+				loadingDiv.innerHTML = 'Loading...';
+				document.body.appendChild( loadingDiv );
+
+				renderer = new THREE.WebGLRenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.setAnimationLoop( animate );
+				renderer.toneMapping = THREE.ACESFilmicToneMapping;
+				renderer.toneMappingExposure = 1;
+				container.appendChild( renderer.domElement );
+
+				const pmremGenerator = new THREE.PMREMGenerator( renderer );
+				scene.environment = pmremGenerator.fromScene( new RoomEnvironment( renderer ), 0.04 ).texture;
+
+				const controls = new OrbitControls( camera, renderer.domElement );
+				controls.addEventListener( 'change', render );
+				controls.minDistance = 2;
+				controls.maxDistance = 10;
+				controls.target.set( 0, 0, - 0.2 );
+				controls.update();
+
+				try {
+
+					// Load HTJ2K-compressed EXR file
+					// HTJ2K support is automatically initialized when needed
+					loadingDiv.innerHTML = 'Loading HTJ2K-compressed EXR...';
+					const loader = new EXRLoader();
+
+					loader.load( 'textures/memorial_htj2k.exr', function ( texture ) {
+
+						loadingDiv.remove();
+
+						texture.mapping = THREE.EquirectangularReflectionMapping;
+
+						const geometry = new THREE.SphereGeometry( 0.8, 64, 32 );
+
+						const material = new THREE.MeshStandardMaterial( {
+							metalness: 0,
+							roughness: 0,
+							envMap: texture,
+							envMapIntensity: 1
+						} );
+
+						const mesh = new THREE.Mesh( geometry, material );
+						scene.add( mesh );
+
+						render();
+
+					}, undefined, function ( error ) {
+
+						loadingDiv.innerHTML = 'Error loading EXR: ' + error.message;
+						console.error( error );
+
+					} );
+
+				} catch ( error ) {
+
+					loadingDiv.innerHTML = 'Error: ' + error.message;
+					console.error( error );
+
+					// Fall back to loading a regular EXR without HTJ2K
+					loadingDiv.innerHTML += '<br>Loading standard EXR instead...';
+
+					const loader = new EXRLoader();
+					loader.load( 'textures/memorial.exr', function ( texture ) {
+
+						const infoDiv = document.createElement( 'div' );
+						infoDiv.style.position = 'absolute';
+						infoDiv.style.bottom = '20px';
+						infoDiv.style.left = '20px';
+						infoDiv.style.color = 'yellow';
+						infoDiv.innerHTML = 'Using fallback: Standard PIZ-compressed EXR';
+						document.body.appendChild( infoDiv );
+
+						loadingDiv.remove();
+
+						texture.mapping = THREE.EquirectangularReflectionMapping;
+
+						const geometry = new THREE.SphereGeometry( 0.8, 64, 32 );
+
+						const material = new THREE.MeshStandardMaterial( {
+							metalness: 0,
+							roughness: 0,
+							envMap: texture,
+							envMapIntensity: 1
+						} );
+
+						const mesh = new THREE.Mesh( geometry, material );
+						scene.add( mesh );
+
+						render();
+
+					} );
+
+				}
+
+				window.addEventListener( 'resize', onWindowResize );
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+				render();
+
+			}
+
+			function render() {
+
+				renderer.render( scene, camera );
+
+			}
+
+			function animate() {
+
+				render();
+
+			}
+
+		</script>
+
+	</body>
+</html>

+ 109 - 0
examples/webgl_loader_jph.html

@@ -0,0 +1,109 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - loaders - JPEG 2000 (HTJ2K) loader</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<link type="text/css" rel="stylesheet" href="main.css">
+	</head>
+
+	<body>
+		<div id="info">
+			<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> -
+			<a href="https://github.com/aous72/OpenJPH" target="_blank" rel="noopener">OpenJPH</a> JPEG 2000 (HTJ2K) loader
+		</div>
+
+		<script type="importmap">
+			{
+				"imports": {
+					"three": "../build/three.module.js",
+					"three/addons/": "./jsm/"
+				}
+			}
+		</script>
+
+		<script type="module">
+
+			import * as THREE from 'three';
+			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+			import { JPHLoader } from 'three/addons/loaders/JPHLoader.js';
+
+			let camera, scene, renderer;
+			let mesh;
+
+			init();
+
+			function init() {
+
+				const container = document.createElement( 'div' );
+				document.body.appendChild( container );
+
+				camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.25, 20 );
+				camera.position.set( 0, 0, 2.5 );
+
+				scene = new THREE.Scene();
+
+				// Add loading information
+				const loadingDiv = document.createElement( 'div' );
+				loadingDiv.style.position = 'absolute';
+				loadingDiv.style.top = '50%';
+				loadingDiv.style.left = '50%';
+				loadingDiv.style.transform = 'translate(-50%, -50%)';
+				loadingDiv.style.color = 'white';
+				loadingDiv.style.fontSize = '20px';
+				loadingDiv.innerHTML = 'Loading J2K texture...';
+				document.body.appendChild( loadingDiv );
+
+				// OpenJPH WASM is automatically loaded when needed
+				const loader = new JPHLoader();
+
+				// Load a JPEG 2000 file
+				loader.load( 'textures/example.j2k', function ( texture ) {
+
+					loadingDiv.remove();
+
+					const material = new THREE.MeshBasicMaterial( { map: texture } );
+					const geometry = new THREE.PlaneGeometry( 2, 2 );
+					mesh = new THREE.Mesh( geometry, material );
+
+					scene.add( mesh );
+
+				}, undefined, function ( error ) {
+
+					loadingDiv.innerHTML = 'Error loading texture: ' + error.message;
+					console.error( error );
+
+				} );
+
+				renderer = new THREE.WebGLRenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.setAnimationLoop( animate );
+				container.appendChild( renderer.domElement );
+
+				const controls = new OrbitControls( camera, renderer.domElement );
+				controls.enableZoom = false;
+
+				window.addEventListener( 'resize', onWindowResize );
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function animate() {
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+
+	</body>
+</html>

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio

粤ICP备19079148号