Dies ist das Repository meines kleinen Portfolios.
Im Hintergrund läuft eine Planetensimulation, geschrieben in JavaScript und Three.js.
Die zu sehenden Texturen stammen von:
https://www.solarsystemscope.com/textures/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
783 lines
19 KiB
783 lines
19 KiB
import { |
|
CompressedTexture, |
|
FileLoader, |
|
LinearFilter, |
|
LinearMipmapLinearFilter, |
|
Loader, |
|
RGBAFormat, |
|
RGBA_ASTC_4x4_Format, |
|
RGBA_BPTC_Format, |
|
RGBA_ETC2_EAC_Format, |
|
RGBA_PVRTC_4BPPV1_Format, |
|
RGBA_S3TC_DXT5_Format, |
|
RGB_ETC1_Format, |
|
RGB_ETC2_Format, |
|
RGB_PVRTC_4BPPV1_Format, |
|
RGB_S3TC_DXT1_Format, |
|
UnsignedByteType |
|
} from 'three'; |
|
|
|
/** |
|
* Loader for Basis Universal GPU Texture Codec. |
|
* |
|
* Basis Universal is a "supercompressed" GPU texture and texture video |
|
* compression system that outputs a highly compressed intermediate file format |
|
* (.basis) that can be quickly transcoded to a wide variety of GPU texture |
|
* compression formats. |
|
* |
|
* This loader parallelizes the transcoding process across a configurable number |
|
* of web workers, before transferring the transcoded compressed texture back |
|
* to the main thread. |
|
*/ |
|
|
|
const _taskCache = new WeakMap(); |
|
|
|
class BasisTextureLoader extends Loader { |
|
|
|
constructor( manager ) { |
|
|
|
super( manager ); |
|
|
|
this.transcoderPath = ''; |
|
this.transcoderBinary = null; |
|
this.transcoderPending = null; |
|
|
|
this.workerLimit = 4; |
|
this.workerPool = []; |
|
this.workerNextTaskID = 1; |
|
this.workerSourceURL = ''; |
|
this.workerConfig = null; |
|
|
|
} |
|
|
|
setTranscoderPath( path ) { |
|
|
|
this.transcoderPath = path; |
|
|
|
return this; |
|
|
|
} |
|
|
|
setWorkerLimit( workerLimit ) { |
|
|
|
this.workerLimit = workerLimit; |
|
|
|
return this; |
|
|
|
} |
|
|
|
detectSupport( renderer ) { |
|
|
|
this.workerConfig = { |
|
astcSupported: renderer.extensions.has( 'WEBGL_compressed_texture_astc' ), |
|
etc1Supported: renderer.extensions.has( 'WEBGL_compressed_texture_etc1' ), |
|
etc2Supported: renderer.extensions.has( 'WEBGL_compressed_texture_etc' ), |
|
dxtSupported: renderer.extensions.has( 'WEBGL_compressed_texture_s3tc' ), |
|
bptcSupported: renderer.extensions.has( 'EXT_texture_compression_bptc' ), |
|
pvrtcSupported: renderer.extensions.has( 'WEBGL_compressed_texture_pvrtc' ) |
|
|| renderer.extensions.has( 'WEBKIT_WEBGL_compressed_texture_pvrtc' ) |
|
}; |
|
|
|
return this; |
|
|
|
} |
|
|
|
load( url, onLoad, onProgress, onError ) { |
|
|
|
const loader = new FileLoader( this.manager ); |
|
|
|
loader.setResponseType( 'arraybuffer' ); |
|
loader.setWithCredentials( this.withCredentials ); |
|
|
|
const texture = new CompressedTexture(); |
|
|
|
loader.load( url, ( buffer ) => { |
|
|
|
// Check for an existing task using this buffer. A transferred buffer cannot be transferred |
|
// again from this thread. |
|
if ( _taskCache.has( buffer ) ) { |
|
|
|
const cachedTask = _taskCache.get( buffer ); |
|
|
|
return cachedTask.promise.then( onLoad ).catch( onError ); |
|
|
|
} |
|
|
|
this._createTexture( [ buffer ] ) |
|
.then( function ( _texture ) { |
|
|
|
texture.copy( _texture ); |
|
texture.needsUpdate = true; |
|
|
|
if ( onLoad ) onLoad( texture ); |
|
|
|
} ) |
|
.catch( onError ); |
|
|
|
}, onProgress, onError ); |
|
|
|
return texture; |
|
|
|
} |
|
|
|
/** Low-level transcoding API, exposed for use by KTX2Loader. */ |
|
parseInternalAsync( options ) { |
|
|
|
const { levels } = options; |
|
|
|
const buffers = new Set(); |
|
|
|
for ( let i = 0; i < levels.length; i ++ ) { |
|
|
|
buffers.add( levels[ i ].data.buffer ); |
|
|
|
} |
|
|
|
return this._createTexture( Array.from( buffers ), { ...options, lowLevel: true } ); |
|
|
|
} |
|
|
|
/** |
|
* @param {ArrayBuffer[]} buffers |
|
* @param {object?} config |
|
* @return {Promise<CompressedTexture>} |
|
*/ |
|
_createTexture( buffers, config = {} ) { |
|
|
|
let worker; |
|
let taskID; |
|
|
|
const taskConfig = config; |
|
let taskCost = 0; |
|
|
|
for ( let i = 0; i < buffers.length; i ++ ) { |
|
|
|
taskCost += buffers[ i ].byteLength; |
|
|
|
} |
|
|
|
const texturePending = this._allocateWorker( taskCost ) |
|
.then( ( _worker ) => { |
|
|
|
worker = _worker; |
|
taskID = this.workerNextTaskID ++; |
|
|
|
return new Promise( ( resolve, reject ) => { |
|
|
|
worker._callbacks[ taskID ] = { resolve, reject }; |
|
|
|
worker.postMessage( { type: 'transcode', id: taskID, buffers: buffers, taskConfig: taskConfig }, buffers ); |
|
|
|
} ); |
|
|
|
} ) |
|
.then( ( message ) => { |
|
|
|
const { mipmaps, width, height, format } = message; |
|
|
|
const texture = new CompressedTexture( mipmaps, width, height, format, UnsignedByteType ); |
|
texture.minFilter = mipmaps.length === 1 ? LinearFilter : LinearMipmapLinearFilter; |
|
texture.magFilter = LinearFilter; |
|
texture.generateMipmaps = false; |
|
texture.needsUpdate = true; |
|
|
|
return texture; |
|
|
|
} ); |
|
|
|
// Note: replaced '.finally()' with '.catch().then()' block - iOS 11 support (#19416) |
|
texturePending |
|
.catch( () => true ) |
|
.then( () => { |
|
|
|
if ( worker && taskID ) { |
|
|
|
worker._taskLoad -= taskCost; |
|
delete worker._callbacks[ taskID ]; |
|
|
|
} |
|
|
|
} ); |
|
|
|
// Cache the task result. |
|
_taskCache.set( buffers[ 0 ], { promise: texturePending } ); |
|
|
|
return texturePending; |
|
|
|
} |
|
|
|
_initTranscoder() { |
|
|
|
if ( ! this.transcoderPending ) { |
|
|
|
// Load transcoder wrapper. |
|
const jsLoader = new FileLoader( this.manager ); |
|
jsLoader.setPath( this.transcoderPath ); |
|
jsLoader.setWithCredentials( this.withCredentials ); |
|
const jsContent = new Promise( ( resolve, reject ) => { |
|
|
|
jsLoader.load( 'basis_transcoder.js', resolve, undefined, reject ); |
|
|
|
} ); |
|
|
|
// Load transcoder WASM binary. |
|
const binaryLoader = new FileLoader( this.manager ); |
|
binaryLoader.setPath( this.transcoderPath ); |
|
binaryLoader.setResponseType( 'arraybuffer' ); |
|
binaryLoader.setWithCredentials( this.withCredentials ); |
|
const binaryContent = new Promise( ( resolve, reject ) => { |
|
|
|
binaryLoader.load( 'basis_transcoder.wasm', resolve, undefined, reject ); |
|
|
|
} ); |
|
|
|
this.transcoderPending = Promise.all( [ jsContent, binaryContent ] ) |
|
.then( ( [ jsContent, binaryContent ] ) => { |
|
|
|
const fn = BasisTextureLoader.BasisWorker.toString(); |
|
|
|
const body = [ |
|
'/* constants */', |
|
'let _EngineFormat = ' + JSON.stringify( BasisTextureLoader.EngineFormat ), |
|
'let _TranscoderFormat = ' + JSON.stringify( BasisTextureLoader.TranscoderFormat ), |
|
'let _BasisFormat = ' + JSON.stringify( BasisTextureLoader.BasisFormat ), |
|
'/* basis_transcoder.js */', |
|
jsContent, |
|
'/* worker */', |
|
fn.substring( fn.indexOf( '{' ) + 1, fn.lastIndexOf( '}' ) ) |
|
].join( '\n' ); |
|
|
|
this.workerSourceURL = URL.createObjectURL( new Blob( [ body ] ) ); |
|
this.transcoderBinary = binaryContent; |
|
|
|
} ); |
|
|
|
} |
|
|
|
return this.transcoderPending; |
|
|
|
} |
|
|
|
_allocateWorker( taskCost ) { |
|
|
|
return this._initTranscoder().then( () => { |
|
|
|
if ( this.workerPool.length < this.workerLimit ) { |
|
|
|
const worker = new Worker( this.workerSourceURL ); |
|
|
|
worker._callbacks = {}; |
|
worker._taskLoad = 0; |
|
|
|
worker.postMessage( { |
|
type: 'init', |
|
config: this.workerConfig, |
|
transcoderBinary: this.transcoderBinary, |
|
} ); |
|
|
|
worker.onmessage = function ( e ) { |
|
|
|
const message = e.data; |
|
|
|
switch ( message.type ) { |
|
|
|
case 'transcode': |
|
worker._callbacks[ message.id ].resolve( message ); |
|
break; |
|
|
|
case 'error': |
|
worker._callbacks[ message.id ].reject( message ); |
|
break; |
|
|
|
default: |
|
console.error( 'THREE.BasisTextureLoader: Unexpected message, "' + message.type + '"' ); |
|
|
|
} |
|
|
|
}; |
|
|
|
this.workerPool.push( worker ); |
|
|
|
} else { |
|
|
|
this.workerPool.sort( function ( a, b ) { |
|
|
|
return a._taskLoad > b._taskLoad ? - 1 : 1; |
|
|
|
} ); |
|
|
|
} |
|
|
|
const worker = this.workerPool[ this.workerPool.length - 1 ]; |
|
|
|
worker._taskLoad += taskCost; |
|
|
|
return worker; |
|
|
|
} ); |
|
|
|
} |
|
|
|
dispose() { |
|
|
|
for ( let i = 0; i < this.workerPool.length; i ++ ) { |
|
|
|
this.workerPool[ i ].terminate(); |
|
|
|
} |
|
|
|
this.workerPool.length = 0; |
|
|
|
return this; |
|
|
|
} |
|
|
|
} |
|
|
|
/* CONSTANTS */ |
|
|
|
BasisTextureLoader.BasisFormat = { |
|
ETC1S: 0, |
|
UASTC_4x4: 1, |
|
}; |
|
|
|
BasisTextureLoader.TranscoderFormat = { |
|
ETC1: 0, |
|
ETC2: 1, |
|
BC1: 2, |
|
BC3: 3, |
|
BC4: 4, |
|
BC5: 5, |
|
BC7_M6_OPAQUE_ONLY: 6, |
|
BC7_M5: 7, |
|
PVRTC1_4_RGB: 8, |
|
PVRTC1_4_RGBA: 9, |
|
ASTC_4x4: 10, |
|
ATC_RGB: 11, |
|
ATC_RGBA_INTERPOLATED_ALPHA: 12, |
|
RGBA32: 13, |
|
RGB565: 14, |
|
BGR565: 15, |
|
RGBA4444: 16, |
|
}; |
|
|
|
BasisTextureLoader.EngineFormat = { |
|
RGBAFormat: RGBAFormat, |
|
RGBA_ASTC_4x4_Format: RGBA_ASTC_4x4_Format, |
|
RGBA_BPTC_Format: RGBA_BPTC_Format, |
|
RGBA_ETC2_EAC_Format: RGBA_ETC2_EAC_Format, |
|
RGBA_PVRTC_4BPPV1_Format: RGBA_PVRTC_4BPPV1_Format, |
|
RGBA_S3TC_DXT5_Format: RGBA_S3TC_DXT5_Format, |
|
RGB_ETC1_Format: RGB_ETC1_Format, |
|
RGB_ETC2_Format: RGB_ETC2_Format, |
|
RGB_PVRTC_4BPPV1_Format: RGB_PVRTC_4BPPV1_Format, |
|
RGB_S3TC_DXT1_Format: RGB_S3TC_DXT1_Format, |
|
}; |
|
|
|
|
|
/* WEB WORKER */ |
|
|
|
BasisTextureLoader.BasisWorker = function () { |
|
|
|
let config; |
|
let transcoderPending; |
|
let BasisModule; |
|
|
|
const EngineFormat = _EngineFormat; // eslint-disable-line no-undef |
|
const TranscoderFormat = _TranscoderFormat; // eslint-disable-line no-undef |
|
const BasisFormat = _BasisFormat; // eslint-disable-line no-undef |
|
|
|
onmessage = function ( e ) { |
|
|
|
const message = e.data; |
|
|
|
switch ( message.type ) { |
|
|
|
case 'init': |
|
config = message.config; |
|
init( message.transcoderBinary ); |
|
break; |
|
|
|
case 'transcode': |
|
transcoderPending.then( () => { |
|
|
|
try { |
|
|
|
const { width, height, hasAlpha, mipmaps, format } = message.taskConfig.lowLevel |
|
? transcodeLowLevel( message.taskConfig ) |
|
: transcode( message.buffers[ 0 ] ); |
|
|
|
const buffers = []; |
|
|
|
for ( let i = 0; i < mipmaps.length; ++ i ) { |
|
|
|
buffers.push( mipmaps[ i ].data.buffer ); |
|
|
|
} |
|
|
|
self.postMessage( { type: 'transcode', id: message.id, width, height, hasAlpha, mipmaps, format }, buffers ); |
|
|
|
} catch ( error ) { |
|
|
|
console.error( error ); |
|
|
|
self.postMessage( { type: 'error', id: message.id, error: error.message } ); |
|
|
|
} |
|
|
|
} ); |
|
break; |
|
|
|
} |
|
|
|
}; |
|
|
|
function init( wasmBinary ) { |
|
|
|
transcoderPending = new Promise( ( resolve ) => { |
|
|
|
BasisModule = { wasmBinary, onRuntimeInitialized: resolve }; |
|
BASIS( BasisModule ); // eslint-disable-line no-undef |
|
|
|
} ).then( () => { |
|
|
|
BasisModule.initializeBasis(); |
|
|
|
} ); |
|
|
|
} |
|
|
|
function transcodeLowLevel( taskConfig ) { |
|
|
|
const { basisFormat, width, height, hasAlpha } = taskConfig; |
|
|
|
const { transcoderFormat, engineFormat } = getTranscoderFormat( basisFormat, width, height, hasAlpha ); |
|
|
|
const blockByteLength = BasisModule.getBytesPerBlockOrPixel( transcoderFormat ); |
|
|
|
assert( BasisModule.isFormatSupported( transcoderFormat ), 'THREE.BasisTextureLoader: Unsupported format.' ); |
|
|
|
const mipmaps = []; |
|
|
|
if ( basisFormat === BasisFormat.ETC1S ) { |
|
|
|
const transcoder = new BasisModule.LowLevelETC1SImageTranscoder(); |
|
|
|
const { endpointCount, endpointsData, selectorCount, selectorsData, tablesData } = taskConfig.globalData; |
|
|
|
try { |
|
|
|
let ok; |
|
|
|
ok = transcoder.decodePalettes( endpointCount, endpointsData, selectorCount, selectorsData ); |
|
|
|
assert( ok, 'THREE.BasisTextureLoader: decodePalettes() failed.' ); |
|
|
|
ok = transcoder.decodeTables( tablesData ); |
|
|
|
assert( ok, 'THREE.BasisTextureLoader: decodeTables() failed.' ); |
|
|
|
for ( let i = 0; i < taskConfig.levels.length; i ++ ) { |
|
|
|
const level = taskConfig.levels[ i ]; |
|
const imageDesc = taskConfig.globalData.imageDescs[ i ]; |
|
|
|
const dstByteLength = getTranscodedImageByteLength( transcoderFormat, level.width, level.height ); |
|
const dst = new Uint8Array( dstByteLength ); |
|
|
|
ok = transcoder.transcodeImage( |
|
transcoderFormat, |
|
dst, dstByteLength / blockByteLength, |
|
level.data, |
|
getWidthInBlocks( transcoderFormat, level.width ), |
|
getHeightInBlocks( transcoderFormat, level.height ), |
|
level.width, level.height, level.index, |
|
imageDesc.rgbSliceByteOffset, imageDesc.rgbSliceByteLength, |
|
imageDesc.alphaSliceByteOffset, imageDesc.alphaSliceByteLength, |
|
imageDesc.imageFlags, |
|
hasAlpha, |
|
false, |
|
0, 0 |
|
); |
|
|
|
assert( ok, 'THREE.BasisTextureLoader: transcodeImage() failed for level ' + level.index + '.' ); |
|
|
|
mipmaps.push( { data: dst, width: level.width, height: level.height } ); |
|
|
|
} |
|
|
|
} finally { |
|
|
|
transcoder.delete(); |
|
|
|
} |
|
|
|
} else { |
|
|
|
for ( let i = 0; i < taskConfig.levels.length; i ++ ) { |
|
|
|
const level = taskConfig.levels[ i ]; |
|
|
|
const dstByteLength = getTranscodedImageByteLength( transcoderFormat, level.width, level.height ); |
|
const dst = new Uint8Array( dstByteLength ); |
|
|
|
const ok = BasisModule.transcodeUASTCImage( |
|
transcoderFormat, |
|
dst, dstByteLength / blockByteLength, |
|
level.data, |
|
getWidthInBlocks( transcoderFormat, level.width ), |
|
getHeightInBlocks( transcoderFormat, level.height ), |
|
level.width, level.height, level.index, |
|
0, |
|
level.data.byteLength, |
|
0, |
|
hasAlpha, |
|
false, |
|
0, 0, |
|
- 1, - 1 |
|
); |
|
|
|
assert( ok, 'THREE.BasisTextureLoader: transcodeUASTCImage() failed for level ' + level.index + '.' ); |
|
|
|
mipmaps.push( { data: dst, width: level.width, height: level.height } ); |
|
|
|
} |
|
|
|
} |
|
|
|
return { width, height, hasAlpha, mipmaps, format: engineFormat }; |
|
|
|
} |
|
|
|
function transcode( buffer ) { |
|
|
|
const basisFile = new BasisModule.BasisFile( new Uint8Array( buffer ) ); |
|
|
|
const basisFormat = basisFile.isUASTC() ? BasisFormat.UASTC_4x4 : BasisFormat.ETC1S; |
|
const width = basisFile.getImageWidth( 0, 0 ); |
|
const height = basisFile.getImageHeight( 0, 0 ); |
|
const levels = basisFile.getNumLevels( 0 ); |
|
const hasAlpha = basisFile.getHasAlpha(); |
|
|
|
function cleanup() { |
|
|
|
basisFile.close(); |
|
basisFile.delete(); |
|
|
|
} |
|
|
|
const { transcoderFormat, engineFormat } = getTranscoderFormat( basisFormat, width, height, hasAlpha ); |
|
|
|
if ( ! width || ! height || ! levels ) { |
|
|
|
cleanup(); |
|
throw new Error( 'THREE.BasisTextureLoader: Invalid texture' ); |
|
|
|
} |
|
|
|
if ( ! basisFile.startTranscoding() ) { |
|
|
|
cleanup(); |
|
throw new Error( 'THREE.BasisTextureLoader: .startTranscoding failed' ); |
|
|
|
} |
|
|
|
const mipmaps = []; |
|
|
|
for ( let mip = 0; mip < levels; mip ++ ) { |
|
|
|
const mipWidth = basisFile.getImageWidth( 0, mip ); |
|
const mipHeight = basisFile.getImageHeight( 0, mip ); |
|
const dst = new Uint8Array( basisFile.getImageTranscodedSizeInBytes( 0, mip, transcoderFormat ) ); |
|
|
|
const status = basisFile.transcodeImage( |
|
dst, |
|
0, |
|
mip, |
|
transcoderFormat, |
|
0, |
|
hasAlpha |
|
); |
|
|
|
if ( ! status ) { |
|
|
|
cleanup(); |
|
throw new Error( 'THREE.BasisTextureLoader: .transcodeImage failed.' ); |
|
|
|
} |
|
|
|
mipmaps.push( { data: dst, width: mipWidth, height: mipHeight } ); |
|
|
|
} |
|
|
|
cleanup(); |
|
|
|
return { width, height, hasAlpha, mipmaps, format: engineFormat }; |
|
|
|
} |
|
|
|
// |
|
|
|
// Optimal choice of a transcoder target format depends on the Basis format (ETC1S or UASTC), |
|
// device capabilities, and texture dimensions. The list below ranks the formats separately |
|
// for ETC1S and UASTC. |
|
// |
|
// In some cases, transcoding UASTC to RGBA32 might be preferred for higher quality (at |
|
// significant memory cost) compared to ETC1/2, BC1/3, and PVRTC. The transcoder currently |
|
// chooses RGBA32 only as a last resort and does not expose that option to the caller. |
|
const FORMAT_OPTIONS = [ |
|
{ |
|
if: 'astcSupported', |
|
basisFormat: [ BasisFormat.UASTC_4x4 ], |
|
transcoderFormat: [ TranscoderFormat.ASTC_4x4, TranscoderFormat.ASTC_4x4 ], |
|
engineFormat: [ EngineFormat.RGBA_ASTC_4x4_Format, EngineFormat.RGBA_ASTC_4x4_Format ], |
|
priorityETC1S: Infinity, |
|
priorityUASTC: 1, |
|
needsPowerOfTwo: false, |
|
}, |
|
{ |
|
if: 'bptcSupported', |
|
basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], |
|
transcoderFormat: [ TranscoderFormat.BC7_M5, TranscoderFormat.BC7_M5 ], |
|
engineFormat: [ EngineFormat.RGBA_BPTC_Format, EngineFormat.RGBA_BPTC_Format ], |
|
priorityETC1S: 3, |
|
priorityUASTC: 2, |
|
needsPowerOfTwo: false, |
|
}, |
|
{ |
|
if: 'dxtSupported', |
|
basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], |
|
transcoderFormat: [ TranscoderFormat.BC1, TranscoderFormat.BC3 ], |
|
engineFormat: [ EngineFormat.RGB_S3TC_DXT1_Format, EngineFormat.RGBA_S3TC_DXT5_Format ], |
|
priorityETC1S: 4, |
|
priorityUASTC: 5, |
|
needsPowerOfTwo: false, |
|
}, |
|
{ |
|
if: 'etc2Supported', |
|
basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], |
|
transcoderFormat: [ TranscoderFormat.ETC1, TranscoderFormat.ETC2 ], |
|
engineFormat: [ EngineFormat.RGB_ETC2_Format, EngineFormat.RGBA_ETC2_EAC_Format ], |
|
priorityETC1S: 1, |
|
priorityUASTC: 3, |
|
needsPowerOfTwo: false, |
|
}, |
|
{ |
|
if: 'etc1Supported', |
|
basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], |
|
transcoderFormat: [ TranscoderFormat.ETC1, TranscoderFormat.ETC1 ], |
|
engineFormat: [ EngineFormat.RGB_ETC1_Format, EngineFormat.RGB_ETC1_Format ], |
|
priorityETC1S: 2, |
|
priorityUASTC: 4, |
|
needsPowerOfTwo: false, |
|
}, |
|
{ |
|
if: 'pvrtcSupported', |
|
basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], |
|
transcoderFormat: [ TranscoderFormat.PVRTC1_4_RGB, TranscoderFormat.PVRTC1_4_RGBA ], |
|
engineFormat: [ EngineFormat.RGB_PVRTC_4BPPV1_Format, EngineFormat.RGBA_PVRTC_4BPPV1_Format ], |
|
priorityETC1S: 5, |
|
priorityUASTC: 6, |
|
needsPowerOfTwo: true, |
|
}, |
|
]; |
|
|
|
const ETC1S_OPTIONS = FORMAT_OPTIONS.sort( function ( a, b ) { |
|
|
|
return a.priorityETC1S - b.priorityETC1S; |
|
|
|
} ); |
|
const UASTC_OPTIONS = FORMAT_OPTIONS.sort( function ( a, b ) { |
|
|
|
return a.priorityUASTC - b.priorityUASTC; |
|
|
|
} ); |
|
|
|
function getTranscoderFormat( basisFormat, width, height, hasAlpha ) { |
|
|
|
let transcoderFormat; |
|
let engineFormat; |
|
|
|
const options = basisFormat === BasisFormat.ETC1S ? ETC1S_OPTIONS : UASTC_OPTIONS; |
|
|
|
for ( let i = 0; i < options.length; i ++ ) { |
|
|
|
const opt = options[ i ]; |
|
|
|
if ( ! config[ opt.if ] ) continue; |
|
if ( ! opt.basisFormat.includes( basisFormat ) ) continue; |
|
if ( opt.needsPowerOfTwo && ! ( isPowerOfTwo( width ) && isPowerOfTwo( height ) ) ) continue; |
|
|
|
transcoderFormat = opt.transcoderFormat[ hasAlpha ? 1 : 0 ]; |
|
engineFormat = opt.engineFormat[ hasAlpha ? 1 : 0 ]; |
|
|
|
return { transcoderFormat, engineFormat }; |
|
|
|
} |
|
|
|
console.warn( 'THREE.BasisTextureLoader: No suitable compressed texture format found. Decoding to RGBA32.' ); |
|
|
|
transcoderFormat = TranscoderFormat.RGBA32; |
|
engineFormat = EngineFormat.RGBAFormat; |
|
|
|
return { transcoderFormat, engineFormat }; |
|
|
|
} |
|
|
|
function assert( ok, message ) { |
|
|
|
if ( ! ok ) throw new Error( message ); |
|
|
|
} |
|
|
|
function getWidthInBlocks( transcoderFormat, width ) { |
|
|
|
return Math.ceil( width / BasisModule.getFormatBlockWidth( transcoderFormat ) ); |
|
|
|
} |
|
|
|
function getHeightInBlocks( transcoderFormat, height ) { |
|
|
|
return Math.ceil( height / BasisModule.getFormatBlockHeight( transcoderFormat ) ); |
|
|
|
} |
|
|
|
function getTranscodedImageByteLength( transcoderFormat, width, height ) { |
|
|
|
const blockByteLength = BasisModule.getBytesPerBlockOrPixel( transcoderFormat ); |
|
|
|
if ( BasisModule.formatIsUncompressed( transcoderFormat ) ) { |
|
|
|
return width * height * blockByteLength; |
|
|
|
} |
|
|
|
if ( transcoderFormat === TranscoderFormat.PVRTC1_4_RGB |
|
|| transcoderFormat === TranscoderFormat.PVRTC1_4_RGBA ) { |
|
|
|
// GL requires extra padding for very small textures: |
|
// https://www.khronos.org/registry/OpenGL/extensions/IMG/IMG_texture_compression_pvrtc.txt |
|
const paddedWidth = ( width + 3 ) & ~ 3; |
|
const paddedHeight = ( height + 3 ) & ~ 3; |
|
|
|
return ( Math.max( 8, paddedWidth ) * Math.max( 8, paddedHeight ) * 4 + 7 ) / 8; |
|
|
|
} |
|
|
|
return ( getWidthInBlocks( transcoderFormat, width ) |
|
* getHeightInBlocks( transcoderFormat, height ) |
|
* blockByteLength ); |
|
|
|
} |
|
|
|
function isPowerOfTwo( value ) { |
|
|
|
if ( value <= 2 ) return true; |
|
|
|
return ( value & ( value - 1 ) ) === 0 && value !== 0; |
|
|
|
} |
|
|
|
}; |
|
|
|
export { BasisTextureLoader };
|
|
|