/**
 * Break faces with edges longer than maxEdgeLength
 */

THREE.TessellateModifier = function ( maxEdgeLength = 0.1, maxIterations = 6, maxFaces = Infinity ) {

	this.maxEdgeLength = maxEdgeLength;
	this.maxIterations = maxIterations;
	this.maxFaces = maxFaces;

};

// Applies the "modify" pattern
THREE.TessellateModifier.prototype.modify = function ( geometry ) {

	const isBufferGeometry = geometry.isBufferGeometry;

	if ( isBufferGeometry ) {

		geometry = new THREE.Geometry().fromBufferGeometry( geometry );

	} else {

		geometry = geometry.clone();

	}

	geometry.mergeVertices( 6 );

	let finalized = false;
	let iteration = 0;
	const maxEdgeLengthSquared = this.maxEdgeLength * this.maxEdgeLength;

	let edge;

	while ( ! finalized && iteration < this.maxIterations && geometry.faces.length < this.maxFaces ) {

		const faces = [];
		const faceVertexUvs = [];

		finalized = true;
		iteration ++;

		for ( var i = 0, il = geometry.faceVertexUvs.length; i < il; i ++ ) {

			faceVertexUvs[ i ] = [];

		}

		for ( var i = 0, il = geometry.faces.length; i < il; i ++ ) {

			const face = geometry.faces[ i ];

			if ( face instanceof THREE.Face3 ) {

				const a = face.a;
				const b = face.b;
				const c = face.c;

				const va = geometry.vertices[ a ];
				const vb = geometry.vertices[ b ];
				const vc = geometry.vertices[ c ];

				const dab = va.distanceToSquared( vb );
				const dbc = vb.distanceToSquared( vc );
				const dac = va.distanceToSquared( vc );

				const limitReached = ( faces.length + il - i ) >= this.maxFaces;

				if ( ! limitReached && ( dab > maxEdgeLengthSquared || dbc > maxEdgeLengthSquared || dac > maxEdgeLengthSquared ) ) {

					finalized = false;

					const m = geometry.vertices.length;

					const triA = face.clone();
					const triB = face.clone();

					if ( dab >= dbc && dab >= dac ) {

						var vm = va.clone();
						vm.lerp( vb, 0.5 );

						triA.a = a;
						triA.b = m;
						triA.c = c;

						triB.a = m;
						triB.b = b;
						triB.c = c;

						if ( face.vertexNormals.length === 3 ) {

							var vnm = face.vertexNormals[ 0 ].clone();
							vnm.lerp( face.vertexNormals[ 1 ], 0.5 );

							triA.vertexNormals[ 1 ].copy( vnm );
							triB.vertexNormals[ 0 ].copy( vnm );

						}

						if ( face.vertexColors.length === 3 ) {

							var vcm = face.vertexColors[ 0 ].clone();
							vcm.lerp( face.vertexColors[ 1 ], 0.5 );

							triA.vertexColors[ 1 ].copy( vcm );
							triB.vertexColors[ 0 ].copy( vcm );

						}

						edge = 0;

					} else if ( dbc >= dab && dbc >= dac ) {

						var vm = vb.clone();
						vm.lerp( vc, 0.5 );

						triA.a = a;
						triA.b = b;
						triA.c = m;

						triB.a = m;
						triB.b = c;
						triB.c = a;

						if ( face.vertexNormals.length === 3 ) {

							var vnm = face.vertexNormals[ 1 ].clone();
							vnm.lerp( face.vertexNormals[ 2 ], 0.5 );

							triA.vertexNormals[ 2 ].copy( vnm );

							triB.vertexNormals[ 0 ].copy( vnm );
							triB.vertexNormals[ 1 ].copy( face.vertexNormals[ 2 ] );
							triB.vertexNormals[ 2 ].copy( face.vertexNormals[ 0 ] );

						}

						if ( face.vertexColors.length === 3 ) {

							var vcm = face.vertexColors[ 1 ].clone();
							vcm.lerp( face.vertexColors[ 2 ], 0.5 );

							triA.vertexColors[ 2 ].copy( vcm );

							triB.vertexColors[ 0 ].copy( vcm );
							triB.vertexColors[ 1 ].copy( face.vertexColors[ 2 ] );
							triB.vertexColors[ 2 ].copy( face.vertexColors[ 0 ] );

						}

						edge = 1;

					} else {

						var vm = va.clone();
						vm.lerp( vc, 0.5 );

						triA.a = a;
						triA.b = b;
						triA.c = m;

						triB.a = m;
						triB.b = b;
						triB.c = c;

						if ( face.vertexNormals.length === 3 ) {

							var vnm = face.vertexNormals[ 0 ].clone();
							vnm.lerp( face.vertexNormals[ 2 ], 0.5 );

							triA.vertexNormals[ 2 ].copy( vnm );
							triB.vertexNormals[ 0 ].copy( vnm );

						}

						if ( face.vertexColors.length === 3 ) {

							var vcm = face.vertexColors[ 0 ].clone();
							vcm.lerp( face.vertexColors[ 2 ], 0.5 );

							triA.vertexColors[ 2 ].copy( vcm );
							triB.vertexColors[ 0 ].copy( vcm );

						}

						edge = 2;

					}

					faces.push( triA, triB );
					geometry.vertices.push( vm );

					for ( var j = 0, jl = geometry.faceVertexUvs.length; j < jl; j ++ ) {

						if ( geometry.faceVertexUvs[ j ].length ) {

							const uvs = geometry.faceVertexUvs[ j ][ i ];

							const uvA = uvs[ 0 ];
							const uvB = uvs[ 1 ];
							const uvC = uvs[ 2 ];

							// AB

							if ( edge === 0 ) {

								var uvM = uvA.clone();
								uvM.lerp( uvB, 0.5 );

								var uvsTriA = [ uvA.clone(), uvM.clone(), uvC.clone() ];
								var uvsTriB = [ uvM.clone(), uvB.clone(), uvC.clone() ];

								// BC

							} else if ( edge === 1 ) {

								var uvM = uvB.clone();
								uvM.lerp( uvC, 0.5 );

								var uvsTriA = [ uvA.clone(), uvB.clone(), uvM.clone() ];
								var uvsTriB = [ uvM.clone(), uvC.clone(), uvA.clone() ];

								// AC

							} else {

								var uvM = uvA.clone();
								uvM.lerp( uvC, 0.5 );

								var uvsTriA = [ uvA.clone(), uvB.clone(), uvM.clone() ];
								var uvsTriB = [ uvM.clone(), uvB.clone(), uvC.clone() ];

							}

							faceVertexUvs[ j ].push( uvsTriA, uvsTriB );

						}

					}

				} else {

					faces.push( face );

					for ( var j = 0, jl = geometry.faceVertexUvs.length; j < jl; j ++ ) {

						faceVertexUvs[ j ].push( geometry.faceVertexUvs[ j ][ i ] );

					}

				}

			}

		}

		geometry.faces = faces;
		geometry.faceVertexUvs = faceVertexUvs;

	}

	if ( isBufferGeometry ) {

		return new THREE.BufferGeometry().fromGeometry( geometry );

	} else {

		return geometry;

	}

};
