<!-- NEW7 — TEST 7 · THE WOVEN CITY · part 227 -->
he JSON object.
	 */
	toJSON() {

		return this.constructor.toJSON( this );

	}

}

function getTrackTypeForValueTypeName( typeName ) {

	switch ( typeName.toLowerCase() ) {

		case 'scalar':
		case 'double':
		case 'float':
		case 'number':
		case 'integer':

			return NumberKeyframeTrack;

		case 'vector':
		case 'vector2':
		case 'vector3':
		case 'vector4':

			return VectorKeyframeTrack;

		case 'color':

			return ColorKeyframeTrack;

		case 'quaternion':

			return QuaternionKeyframeTrack;

		case 'bool':
		case 'boolean':

			return BooleanKeyframeTrack;

		case 'string':

			return StringKeyframeTrack;

	}

	throw new Error( 'THREE.KeyframeTrack: Unsupported typeName: ' + typeName );

}

function parseKeyframeTrack( json ) {

	if ( json.type === undefined ) {

		throw new Error( 'THREE.KeyframeTrack: track type undefined, can not parse' );

	}

	const trackType = getTrackTypeForValueTypeName( json.type );

	if ( json.times === undefined ) {

		const times = [], values = [];

		flattenJSON( json.keys, times, values, 'value' );

		json.times = times;
		json.values = values;

	}

	let track;

	// derived classes can define a static parse method
	if ( trackType.parse !== undefined ) {

		track = trackType.parse( json );

	} else {

		// by default, we assume a constructor compatible with the base
		track = new trackType( json.name, json.times, json.values, json.interpolation );

	}

	if ( hasTangents( json.settings ) ) {

		track.settings = {
			inTangents: convertArray( json.settings.inTangents, Float32Array ),
			outTangents: convertArray( json.settings.outTangents, Float32Array )
		};

	}

	return track;

}

/**
 * @class
 * @classdesc A simple caching system, used internally by {@link FileLoader}.
 * To enable caching across all loaders that use {@link FileLoader}, add `THREE.Cache.enabled = true.` once in your app.
 * @hideconstructor
 */
const Cache = {

	/**
	 * Whether caching is enabled or not.
	 *
	 * @static
	 * @type {boolean}
	 * @default false
	 */
	enabled: false,

	/**
	 * A dictionary that holds cached files.
	 *
	 * @static
	 * @type {Object<string,Object>}
	 */
	files: {},

	/**
	 * Adds a cache entry with a key to reference the file. If this key already
	 * holds a file, it is overwritten.
	 *
	 * @static
	 * @param {string} key - The key to reference the cached file.
	 * @param {Object} file -  The file to be cached.
	 */
	add: function ( key, file ) {

		if ( this.enabled === false ) return;

		if ( isBlobURL( key ) ) return;

		// log( 'Cache', 'Adding key:', key );

		this.files[ key ] = file;

	},

	/**
	 * Gets the cached value for the given key.
	 *
	 * @static
	 * @param {string} key - The key to reference the cached file.
	 * @return {Object|undefined} The cached file. If the key does not exist `undefined` is returned.
	 */
	get: function ( key ) {

		if ( this.enabled === false ) return;

		if ( isBlobURL( key ) ) return;

		// log( 'Cache', 'Checking key:', key );

		return this.files[ key ];

	},

	/**
	 * Removes the cached file associated with the given key.
	 *
	 * @static
	 * @param {string} key - The key to reference the cached file.
	 */
	remove: function ( key ) {

		delete this.files[ key ];

	},

	/**
	 * Remove all values from the cache.
	 *
	 * @static
	 */
	clear: function () {

		this.files = {};

	}

};

/**
 * Returns true if the given cache key contains the blob: scheme.
 *
 * @private
 * @param {string} key - The cache key.
 * @return {boolean} Whether the given cache key contains the blob: scheme or not.
 */
function isBlobURL( key ) {

	try {

		const urlString = key.slice( key.indexOf( ':' ) + 1 ); // remove type identifier

		const url = new URL( urlString );
		return url.protocol === 'blob:';

	} catch ( e ) {

		// If the string is not a valid URL, it throws an error
		return false;

	}

}

/**
 * Handles and keeps track of loaded and pending data. A default global
 * instance of this class is created and used by loaders if not supplied
 * manually.
 *
 * In general that should be sufficient, however there are times when it can
 * be useful to have separate loaders - for example if you want to show
 * separate loading bars for objects and textures.
 *
 * ```js
 * const manager = new THREE.LoadingManager();
 * manager.onLoad = () => console.log( 'Loading complete!' );
 *
 * const loader1 = new OBJLoader( manager );
 * const loader2 = new ColladaLoader( manager );
 * ```
 */
class LoadingManager {

	/**
	 * Constructs a new loading manager.
	 *
	 * @param {Function} [onLoad] - Executes when all items have been loaded.
	 * @param {Function} [onProgress] - Executes when single items have been loaded.
	 * @param {Function} [onError] - Executes when an error occurs.
	 */
	constructor( onLoad, onProgress, onError ) {

		const scope = this;

		let isLoading = false;
		let itemsLoaded = 0;
		let itemsTotal = 0;
		let urlModifier = undefined;
		const handlers = [];

		// Refer to #5689 for the reason why w