<!-- NEW6 — TEST 6 · THE FIVE SHORES · part 268 -->
d internally.
 */
class PropertyBinding {

	/**
	 * Constructs a new property binding.
	 *
	 * @param {Object} rootNode - The root node.
	 * @param {string} path - The path.
	 * @param {?Object} [parsedPath] - The parsed path.
	 */
	constructor( rootNode, path, parsedPath ) {

		/**
		 * The object path to the animated property.
		 *
		 * @type {string}
		 */
		this.path = path;

		/**
		 * An object holding information about the path.
		 *
		 * @type {Object}
		 */
		this.parsedPath = parsedPath || PropertyBinding.parseTrackName( path );

		/**
		 * The object owns the animated property.
		 *
		 * @type {?Object}
		 */
		this.node = PropertyBinding.findNode( rootNode, this.parsedPath.nodeName );

		/**
		 * The root node.
		 *
		 * @type {Object3D|Skeleton}
		 */
		this.rootNode = rootNode;

		// initial state of these methods that calls 'bind'
		this.getValue = this._getValue_unbound;
		this.setValue = this._setValue_unbound;

	}


	/**
	 * Factory method for creating a property binding from the given parameters.
	 *
	 * @static
	 * @param {Object} root - The root node.
	 * @param {string} path - The path.
	 * @param {?Object} [parsedPath] - The parsed path.
	 * @return {PropertyBinding|Composite} The created property binding or composite.
	 */
	static create( root, path, parsedPath ) {

		if ( ! ( root && root.isAnimationObjectGroup ) ) {

			return new PropertyBinding( root, path, parsedPath );

		} else {

			return new PropertyBinding.Composite( root, path, parsedPath );

		}

	}

	/**
	 * Replaces spaces with underscores and removes unsupported characters from
	 * node names, to ensure compatibility with parseTrackName().
	 *
	 * @param {string} name - Node name to be sanitized.
	 * @return {string} The sanitized node name.
	 */
	static sanitizeNodeName( name ) {

		return name.replace( /\s/g, '_' ).replace( _reservedRe, '' );

	}

	/**
	 * Parses the given track name (an object path to an animated property) and
	 * returns an object with information about the path. Matches strings in the following forms:
	 *
	 * - nodeName.property
	 * - nodeName.property[accessor]
	 * - nodeName.material.property[accessor]
	 * - uuid.property[accessor]
	 * - uuid.objectName[objectIndex].propertyName[propertyIndex]
	 * - parentName/nodeName.property
	 * - parentName/parentName/nodeName.property[index]
	 * - .bone[Armature.DEF_cog].position
	 * - scene:helium_balloon_model:helium_balloon_model.position
	 *
	 * @static
	 * @param {string} trackName - The track name to parse.
	 * @return {Object} The parsed track name as an object.
	 */
	static parseTrackName( trackName ) {

		const matches = _trackRe.exec( trackName );

		if ( matches === null ) {

			throw new Error( 'THREE.PropertyBinding: Cannot parse trackName: ' + trackName );

		}

		const results = {
			// directoryName: matches[ 1 ], // (tschw) currently unused
			nodeName: matches[ 2 ],
			objectName: matches[ 3 ],
			objectIndex: matches[ 4 ],
			propertyName: matches[ 5 ], // required
			propertyIndex: matches[ 6 ]
		};

		const lastDot = results.nodeName && results.nodeName.lastIndexOf( '.' );

		if ( lastDot !== undefined && lastDot !== -1 ) {

			const objectName = results.nodeName.substring( lastDot + 1 );

			// Object names must be checked against an allowlist. Otherwise, there
			// is no way to parse 'foo.bar.baz': 'baz' must be a property, but
			// 'bar' could be the objectName, or part of a nodeName (which can
			// include '.' characters).
			if ( _supportedObjectNames.indexOf( objectName ) !== -1 ) {

				results.nodeName = results.nodeName.substring( 0, lastDot );
				results.objectName = objectName;

			}

		}

		if ( results.propertyName === null || results.propertyName.length === 0 ) {

			throw new Error( 'THREE.PropertyBinding: can not parse propertyName from trackName: ' + trackName );

		}

		return results;

	}

	/**
	 * Searches for a node in the hierarchy of the given root object by the given
	 * node name.
	 *
	 * @static
	 * @param {Object} root - The root object.
	 * @param {string|number} nodeName - The name of the node.
	 * @return {?Object} The found node. Returns `null` if no object was found.
	 */
	static findNode( root, nodeName ) {

		if ( nodeName === undefined || nodeName === '' || nodeName === '.' || nodeName === -1 || nodeName === root.name || nodeName === root.uuid ) {

			return root;

		}

		// search into skeleton bones.
		if ( root.skeleton ) {

			const bone = root.skeleton.getBoneByName( nodeName );

			if ( bone !== undefined ) {

				return bone;

			}

		}

		// search into node subtree.
		if ( root.children ) {

			const searchNodeSubtree = function ( children ) {

				for ( let i = 0; i < children.length; i ++ ) {

					const childNode = children[ i ];

					if ( childNode.name === nodeName || childNode.uuid === nodeName ) {

						return childNode;

					}

					const result = searchNodeSubtree( childNode.children );

					if ( result ) return result;

	