<!-- NEW3 — TEST 3 · THE RIVER'S JOURNEY · part 301 -->
number at this entry's interior point.
		//
		// A subpath only contributes to the fill boundary when crossing it
		// actually flips the "insideness" per the fill rule; otherwise it's a
		// redundant overlap and gets excluded to avoid double-counting.
		for ( let i = 0; i < entries.length; i ++ ) {

			const entry = entries[ i ];
			let containerWinding = 0;

			for ( let j = i - 1; j >= 0; j -- ) {

				const candidate = entries[ j ];

				if ( ! candidate.boundingBox.containsBox( entry.boundingBox ) ) continue;
				if ( ! pointInPolygon( entry.interiorPoint, candidate.points ) ) continue;

				entry.container = candidate.exclude ? candidate.container : candidate;
				containerWinding = candidate.winding;
				entry.winding += containerWinding;
				break;

			}

			if ( isInside( entry.winding ) === isInside( containerWinding ) ) {

				entry.exclude = true;

			}

		}

		// Classify retained entries. An entry is an outer shape if it has no
		// container or if its container is itself a hole (a solid nested inside
		// a hole becomes a new top-level shape); otherwise it's a hole in its
		// container. Entries were already sorted outermost-first, so each
		// container's role is known by the time we look at it.
		for ( const entry of entries ) {

			if ( entry.exclude ) continue;
			entry.role = ( entry.container === null || entry.container.role === 'hole' ) ? 'outer' : 'hole';

		}

		// Build Shapes for outers first, then attach holes to their container's
		// Shape.
		const shapes = [];
		const shapeByEntry = new Map();

		for ( const entry of entries ) {

			if ( entry.exclude || entry.role !== 'outer' ) continue;

			const shape = new Shape();
			shape.curves = entry.subPath.curves;
			shapes.push( shape );
			shapeByEntry.set( entry, shape );

		}

		for ( const entry of entries ) {

			if ( entry.exclude || entry.role !== 'hole' ) continue;

			const shape = shapeByEntry.get( entry.container );
			if ( ! shape ) continue;

			const hole = new Path();
			hole.curves = entry.subPath.curves;
			shape.holes.push( hole );

		}

		return shapes;


	}

}

/**
 * Abstract base class for controls.
 *
 * @abstract
 * @augments EventDispatcher
 */
class Controls extends EventDispatcher {

	/**
	 * Constructs a new controls instance.
	 *
	 * @param {Object3D} object - The object that is managed by the controls.
	 * @param {?HTMLElement} domElement - The HTML element used for event listeners.
	 */
	constructor( object, domElement = null ) {

		super();

		/**
		 * The object that is managed by the controls.
		 *
		 * @type {Object3D}
		 */
		this.object = object;

		/**
		 * The HTML element used for event listeners.
		 *
		 * @type {?HTMLElement}
		 * @default null
		 */
		this.domElement = domElement;

		/**
		 * Whether the controls responds to user input or not.
		 *
		 * @type {boolean}
		 * @default true
		 */
		this.enabled = true;

		/**
		 * The internal state of the controls.
		 *
		 * @type {number}
		 * @default -1
		 */
		this.state = -1;

		/**
		 * This object defines the keyboard input of the controls.
		 *
		 * @type {Object}
		 */
		this.keys = {};

		/**
		 * This object defines what type of actions are assigned to the available mouse buttons.
		 * It depends on the control implementation what kind of mouse buttons and actions are supported.
		 *
		 * @type {{LEFT: ?number, MIDDLE: ?number, RIGHT: ?number}}
		 */
		this.mouseButtons = { LEFT: null, MIDDLE: null, RIGHT: null };

		/**
		 * This object defines what type of actions are assigned to what kind of touch interaction.
		 * It depends on the control implementation what kind of touch interaction and actions are supported.
		 *
		 * @type {{ONE: ?number, TWO: ?number}}
		 */
		this.touches = { ONE: null, TWO: null };

	}

	/**
	 * Connects the controls to the DOM. This method has so called "side effects" since
	 * it adds the module's event listeners to the DOM.
	 *
	 * @param {HTMLElement} element - The DOM element to connect to.
	 */
	connect( element ) {

		if ( this.domElement !== null ) this.disconnect();

		this.domElement = element;

	}

	/**
	 * Disconnects the controls from the DOM.
	 */
	disconnect() {}

	/**
	 * Call this method if you no longer want use to the controls. It frees all internal
	 * resources and removes all event listeners.
	 */
	dispose() {}

	/**
	 * Controls should implement this method if they have to update their internal state
	 * per simulation step.
	 *
	 * @param {number} [delta] - The time delta in seconds.
	 */
	update( /* delta */ ) {}

}

/**
 * Scales the texture as large as possible within its surface without cropping
 * or stretching the texture. The method preserves the original aspect ratio of
 * the texture. Akin to CSS `object-fit: contain`
 *
 * @param {Texture} texture - The texture.
 * @param {number} aspect - The texture's aspect ratio.
 * @return {Texture} The updated texture.
 */
function contain( texture, aspect ) {

	const image