@needle-tools/gltf-progressive
Version:
three.js support for loading glTF or GLB files that contain progressive loading data
151 lines (150 loc) • 6.22 kB
JavaScript
import { MeshoptDecoder } from 'three/examples/jsm/libs/meshopt_decoder.module.js';
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
import { KTX2Loader } from 'three/examples/jsm/loaders/KTX2Loader.js';
let DEFAULT_DRACO_DECODER_LOCATION = 'https://www.gstatic.com/draco/versioned/decoders/1.5.7/';
let DEFAULT_KTX2_TRANSCODER_LOCATION = 'https://www.gstatic.com/basis-universal/versioned/2021-04-15-ba1c3e4/';
const defaultDraco = DEFAULT_DRACO_DECODER_LOCATION;
const defaultKTX2 = DEFAULT_KTX2_TRANSCODER_LOCATION;
const _remoteDracoDecoderUrl = new URL(DEFAULT_DRACO_DECODER_LOCATION + "draco_decoder.js");
// if (typeof window !== "undefined") {
// if (!window.navigator.onLine) {
// // check if the default values have been changed by the user.
// // If they didnt change / the default paths are not reachable, fall back to local versions
// if (DEFAULT_DRACO_DECODER_LOCATION === defaultDraco)
// DEFAULT_DRACO_DECODER_LOCATION = "./include/draco/";
// if (DEFAULT_KTX2_TRANSCODER_LOCATION === defaultKTX2)
// DEFAULT_KTX2_TRANSCODER_LOCATION = "./include/ktx2/";
// }
// prepareLoaders();
// }
// Avoid cache busting - if we don't do this chrome will clear the fully cached file (potentially)
_remoteDracoDecoderUrl.searchParams.append("range", "true");
fetch(_remoteDracoDecoderUrl, {
method: "GET",
headers: {
"Range": "bytes=0-1"
}
})
.catch(_ => {
console.debug(`Failed to fetch remote Draco decoder from ${DEFAULT_DRACO_DECODER_LOCATION} (offline: ${(typeof navigator !== "undefined") ? navigator.onLine : "unknown"})`);
// check if the default values have been changed by the user.
// If they didnt change / the default paths are not reachable, fall back to local versions
if (DEFAULT_DRACO_DECODER_LOCATION === defaultDraco) {
setDracoDecoderLocation("./include/draco/");
}
if (DEFAULT_KTX2_TRANSCODER_LOCATION === defaultKTX2) {
setKTX2TranscoderLocation("./include/ktx2/");
}
})
.finally(() => {
prepareLoaders();
});
/**
* Set the location of the Draco decoder. If a draco loader has already been created, it will be updated.
* @default 'https://www.gstatic.com/draco/versioned/decoders/1.5.7/'
*/
export function setDracoDecoderLocation(location) {
DEFAULT_DRACO_DECODER_LOCATION = location;
if (dracoLoader && dracoLoader[$dracoDecoderPath] != DEFAULT_DRACO_DECODER_LOCATION) {
console.debug("Updating Draco decoder path to " + location);
dracoLoader[$dracoDecoderPath] = DEFAULT_DRACO_DECODER_LOCATION;
dracoLoader.setDecoderPath(DEFAULT_DRACO_DECODER_LOCATION);
dracoLoader.preload();
}
else {
console.debug("Setting Draco decoder path to " + location);
}
}
/**
* Set the location of the KTX2 transcoder. If a KTX2 loader has already been created, it will be updated.
* @default 'https://www.gstatic.com/basis-universal/versioned/2021-04-15-ba1c3e4/'
*/
export function setKTX2TranscoderLocation(location) {
DEFAULT_KTX2_TRANSCODER_LOCATION = location;
// if set from <needle-engine>
if (ktx2Loader && ktx2Loader.transcoderPath != DEFAULT_KTX2_TRANSCODER_LOCATION) {
console.debug("Updating KTX2 transcoder path to " + location);
ktx2Loader.setTranscoderPath(DEFAULT_KTX2_TRANSCODER_LOCATION);
ktx2Loader.init();
}
else {
console.debug("Setting KTX2 transcoder path to " + location);
}
}
const $dracoDecoderPath = Symbol("dracoDecoderPath");
let dracoLoader;
let meshoptDecoder;
let ktx2Loader;
/** Used to create and load loaders */
function prepareLoaders() {
if (!dracoLoader) {
dracoLoader = new DRACOLoader();
dracoLoader[$dracoDecoderPath] = DEFAULT_DRACO_DECODER_LOCATION;
dracoLoader.setDecoderPath(DEFAULT_DRACO_DECODER_LOCATION);
dracoLoader.setDecoderConfig({ type: 'js' });
dracoLoader.preload();
}
if (!ktx2Loader) {
ktx2Loader = new KTX2Loader();
ktx2Loader.setTranscoderPath(DEFAULT_KTX2_TRANSCODER_LOCATION);
ktx2Loader.init();
}
if (!meshoptDecoder) {
meshoptDecoder = MeshoptDecoder;
}
}
/**
* Create loaders/decoders for Draco, KTX2 and Meshopt to be used with GLTFLoader.
* @param renderer - Provide a renderer to detect KTX2 support.
* @returns The loaders/decoders.
*/
export function createLoaders(renderer) {
prepareLoaders();
if (renderer) {
ktx2Loader.detectSupport(renderer);
}
else if (renderer !== null)
console.warn("No renderer provided to detect ktx2 support - loading KTX2 textures might fail");
return { dracoLoader, ktx2Loader, meshoptDecoder };
}
export function addDracoAndKTX2Loaders(loader) {
if (!loader.dracoLoader)
loader.setDRACOLoader(dracoLoader);
if (!loader.ktx2Loader)
loader.setKTX2Loader(ktx2Loader);
if (!loader.meshoptDecoder)
loader.setMeshoptDecoder(meshoptDecoder);
}
const gltfLoaderConfigurations = new WeakMap();
export function configureLoader(loader, opts) {
let config = gltfLoaderConfigurations.get(loader);
if (config) {
config = Object.assign(config, opts);
}
else {
config = opts;
}
gltfLoaderConfigurations.set(loader, config);
}
const originalLoadFunction = GLTFLoader.prototype.load;
function onLoad(...args) {
const config = gltfLoaderConfigurations.get(this);
let url_str = args[0];
const url = new URL(url_str, window.location.href);
if (url.hostname.endsWith("needle.tools")) {
const progressive = config?.progressive !== undefined ? config.progressive : true;
const usecase = config?.usecase ? config.usecase : "default";
if (progressive) {
this.requestHeader["Accept"] = `*/*;progressive=allowed;usecase=${usecase}`;
}
else {
this.requestHeader["Accept"] = `*/*;usecase=${usecase}`;
}
url_str = url.toString();
}
args[0] = url_str;
const res = originalLoadFunction?.call(this, ...args);
return res;
}
GLTFLoader.prototype.load = onLoad;