ConvexObjectBreaker.js 13.6 KB
Newer Older
M
Mugen87 已提交
1
/**
2 3 4 5 6 7 8 9 10 11 12 13
 * @fileoverview This class can be used to subdivide a convex Geometry object into pieces.
 *
 * Usage:
 *
 * Use the function prepareBreakableObject to prepare a Mesh object to be broken.
 *
 * Then, call the various functions to subdivide the object (subdivideByImpact, cutByPlane)
 *
 * Sub-objects that are product of subdivision don't need prepareBreakableObject to be called on them.
 *
 * Requisites for the object:
 *
14
 *  - Mesh object must have a BufferGeometry (not Geometry) and a Material
M
Mugen87 已提交
15
 *
16
 *  - Vertex normals must be planar (not smoothed)
17
 *
18
 *  - The geometry must be convex (this is not checked in the library). You can create convex
M
Mugen87 已提交
19
 *  geometries with THREE.ConvexGeometry. The BoxBufferGeometry, SphereBufferGeometry and other convex primitives
20 21
 *  can also be used.
 *
22
 * Note: This lib adds member variables to object's userData member (see prepareBreakableObject function)
23 24 25 26
 * Use with caution and read the code when using with other libs.
 *
 * @param {double} minSizeForBreak Min size a debris can have to break.
 * @param {double} smallDelta Max distance to consider that a point belongs to a plane.
M
Mugen87 已提交
27
 *
M
Mugen87 已提交
28
*/
29

M
Mugen87 已提交
30
THREE.ConvexObjectBreaker = function ( minSizeForBreak, smallDelta ) {
31 32 33 34 35 36 37

	this.minSizeForBreak = minSizeForBreak || 1.4;
	this.smallDelta = smallDelta || 0.0001;

	this.tempLine1 = new THREE.Line3();
	this.tempPlane1 = new THREE.Plane();
	this.tempPlane2 = new THREE.Plane();
38
	this.tempPlane_Cut = new THREE.Plane();
39 40 41 42 43
	this.tempCM1 = new THREE.Vector3();
	this.tempCM2 = new THREE.Vector3();
	this.tempVector3 = new THREE.Vector3();
	this.tempVector3_2 = new THREE.Vector3();
	this.tempVector3_3 = new THREE.Vector3();
44 45 46 47 48 49 50
	this.tempVector3_P0 = new THREE.Vector3();
	this.tempVector3_P1 = new THREE.Vector3();
	this.tempVector3_P2 = new THREE.Vector3();
	this.tempVector3_N0 = new THREE.Vector3();
	this.tempVector3_N1 = new THREE.Vector3();
	this.tempVector3_AB = new THREE.Vector3();
	this.tempVector3_CB = new THREE.Vector3();
51 52 53 54
	this.tempResultObjects = { object1: null, object2: null };

	this.segments = [];
	var n = 30 * 30;
M
Mugen87 已提交
55
	for ( var i = 0; i < n; i ++ ) this.segments[ i ] = false;
56 57 58 59 60 61 62

};

THREE.ConvexObjectBreaker.prototype = {

	constructor: THREE.ConvexObjectBreaker,

M
Mugen87 已提交
63
	prepareBreakableObject: function ( object, mass, velocity, angularVelocity, breakable ) {
64

65
		// object is a THREE.Object3d (normally a Mesh), must have a BufferGeometry, and it must be convex.
66 67
		// Its material property is propagated to its children (sub-pieces)
		// mass must be > 0
M
Mugen87 已提交
68

69
		if ( ! object.geometry.isBufferGeometry ) {
M
Mugen87 已提交
70 71 72

			console.error( 'THREE.ConvexObjectBreaker.prepareBreakableObject(): Parameter object must have a BufferGeometry.' );

73
		}
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88

		var userData = object.userData;
		userData.mass = mass;
		userData.velocity = velocity.clone();
		userData.angularVelocity = angularVelocity.clone();
		userData.breakable = breakable;

	},

	/*
	 * @param {int} maxRadialIterations Iterations for radial cuts.
	 * @param {int} maxRandomIterations Max random iterations for not-radial cuts
	 *
	 * Returns the array of pieces
	 */
M
Mugen87 已提交
89
	subdivideByImpact: function ( object, pointOfImpact, normal, maxRadialIterations, maxRandomIterations ) {
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109

		var debris = [];

		var tempPlane1 = this.tempPlane1;
		var tempPlane2 = this.tempPlane2;

		this.tempVector3.addVectors( pointOfImpact, normal );
		tempPlane1.setFromCoplanarPoints( pointOfImpact, object.position, this.tempVector3 );

		var maxTotalIterations = maxRandomIterations + maxRadialIterations;

		var scope = this;

		function subdivideRadial( subObject, startAngle, endAngle, numIterations ) {

			if ( Math.random() < numIterations * 0.05 || numIterations > maxTotalIterations ) {

				debris.push( subObject );

				return;
M
Mugen87 已提交
110

111
			}
M
Mugen87 已提交
112

113 114 115 116 117 118 119
			var angle = Math.PI;

			if ( numIterations === 0 ) {

				tempPlane2.normal.copy( tempPlane1.normal );
				tempPlane2.constant = tempPlane1.constant;

M
Mugen87 已提交
120
			} else {
121 122

				if ( numIterations <= maxRadialIterations ) {
M
Mugen87 已提交
123

124 125 126 127 128 129
					angle = ( endAngle - startAngle ) * ( 0.2 + 0.6 * Math.random() ) + startAngle;

					// Rotate tempPlane2 at impact point around normal axis and the angle
					scope.tempVector3_2.copy( object.position ).sub( pointOfImpact ).applyAxisAngle( normal, angle ).add( pointOfImpact );
					tempPlane2.setFromCoplanarPoints( pointOfImpact, scope.tempVector3, scope.tempVector3_2 );

M
Mugen87 已提交
130
				} else {
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168

					angle = ( ( 0.5 * ( numIterations & 1 ) ) + 0.2 * ( 2 - Math.random() ) ) * Math.PI;

					// Rotate tempPlane2 at object position around normal axis and the angle
					scope.tempVector3_2.copy( pointOfImpact ).sub( subObject.position ).applyAxisAngle( normal, angle ).add( subObject.position );
					scope.tempVector3_3.copy( normal ).add( subObject.position );
					tempPlane2.setFromCoplanarPoints( subObject.position, scope.tempVector3_3, scope.tempVector3_2 );

				}

			}

			// Perform the cut
			scope.cutByPlane( subObject, tempPlane2, scope.tempResultObjects );

			var obj1 = scope.tempResultObjects.object1;
			var obj2 = scope.tempResultObjects.object2;

			if ( obj1 ) {

				subdivideRadial( obj1, startAngle, angle, numIterations + 1 );

			}

			if ( obj2 ) {

				subdivideRadial( obj2, angle, endAngle, numIterations + 1 );

			}

		}

		subdivideRadial( object, 0, 2 * Math.PI, 0 );

		return debris;

	},

M
Mugen87 已提交
169
	cutByPlane: function ( object, plane, output ) {
170 171 172 173 174 175 176

		// Returns breakable objects in output.object1 and output.object2 members, the resulting 2 pieces of the cut.
		// object2 can be null if the plane doesn't cut the object.
		// object1 can be null only in case of internal error
		// Returned value is number of pieces, 0 for error.

		var geometry = object.geometry;
177 178 179 180 181
		var coords = geometry.attributes.position.array;
		var normals = geometry.attributes.normal.array;

		var numPoints = coords.length / 3;
		var numFaces = numPoints / 3;
M
Mugen87 已提交
182

183
		var indices = geometry.getIndex();
M
Mugen87 已提交
184

185
		if ( indices ) {
M
Mugen87 已提交
186

187 188
			indices = indices.array;
			numFaces = indices.length / 3;
M
Mugen87 已提交
189

190
		}
M
Mugen87 已提交
191 192 193

		function getVertexIndex( faceIdx, vert ) {

194
			// vert = 0, 1 or 2.
M
Mugen87 已提交
195

196 197 198
			var idx = faceIdx * 3 + vert;

			return indices ? indices[ idx ] : idx;
M
Mugen87 已提交
199

200
		}
201 202 203 204 205 206 207 208

		var points1 = [];
		var points2 = [];

		var delta = this.smallDelta;

		// Reset segments mark
		var numPointPairs = numPoints * numPoints;
M
Mugen87 已提交
209
		for ( var i = 0; i < numPointPairs; i ++ ) this.segments[ i ] = false;
M
Mugen87 已提交
210

211 212 213 214
		var p0 = this.tempVector3_P0;
		var p1 = this.tempVector3_P1;
		var n0 = this.tempVector3_N0;
		var n1 = this.tempVector3_N1;
215 216

		// Iterate through the faces to mark edges shared by coplanar faces
217
		for ( var i = 0; i < numFaces - 1; i ++ ) {
218

219 220 221
			var a1 = getVertexIndex( i, 0 );
			var b1 = getVertexIndex( i, 1 );
			var c1 = getVertexIndex( i, 2 );
222

223
			// Assuming all 3 vertices have the same normal
M
Mugen87 已提交
224
			n0.set( normals[ a1 ], normals[ a1 ] + 1, normals[ a1 ] + 2 );
225

226
			for ( var j = i + 1; j < numFaces; j ++ ) {
227

228 229 230
				var a2 = getVertexIndex( j, 0 );
				var b2 = getVertexIndex( j, 1 );
				var c2 = getVertexIndex( j, 2 );
M
Mugen87 已提交
231

232
				// Assuming all 3 vertices have the same normal
M
Mugen87 已提交
233
				n1.set( normals[ a2 ], normals[ a2 ] + 1, normals[ a2 ] + 2 );
234

235
				var coplanar = 1 - n0.dot( n1 ) < delta;
236

237
				if ( coplanar ) {
238 239

					if ( a1 === a2 || a1 === b2 || a1 === c2 ) {
M
Mugen87 已提交
240

241
						if ( b1 === a2 || b1 === b2 || b1 === c2 ) {
M
Mugen87 已提交
242

243 244
							this.segments[ a1 * numPoints + b1 ] = true;
							this.segments[ b1 * numPoints + a1 ] = true;
M
Mugen87 已提交
245 246 247

						}	else {

248 249
							this.segments[ c1 * numPoints + a1 ] = true;
							this.segments[ a1 * numPoints + c1 ] = true;
M
Mugen87 已提交
250

251
						}
M
Mugen87 已提交
252 253 254

					}	else if ( b1 === a2 || b1 === b2 || b1 === c2 ) {

255 256
						this.segments[ c1 * numPoints + b1 ] = true;
						this.segments[ b1 * numPoints + c1 ] = true;
M
Mugen87 已提交
257

258 259 260 261 262 263 264 265 266
					}

				}

			}

		}

		// Transform the plane to object local space
267
		var localPlane = this.tempPlane_Cut;
268 269 270 271
		object.updateMatrix();
		THREE.ConvexObjectBreaker.transformPlaneToLocalSpace( plane, object.matrix, localPlane );

		// Iterate through the faces adding points to both pieces
272
		for ( var i = 0; i < numFaces; i ++ ) {
273

274 275 276
			var va = getVertexIndex( i, 0 );
			var vb = getVertexIndex( i, 1 );
			var vc = getVertexIndex( i, 2 );
277

M
Mugen87 已提交
278
			for ( var segment = 0; segment < 3; segment ++ ) {
279

280 281
				var i0 = segment === 0 ? va : ( segment === 1 ? vb : vc );
				var i1 = segment === 0 ? vb : ( segment === 1 ? vc : va );
282 283 284

				var segmentState = this.segments[ i0 * numPoints + i1 ];

M
Mugen87 已提交
285
				if ( segmentState ) continue; // The segment already has been processed in another face
286 287 288 289 290

				// Mark segment as processed (also inverted segment)
				this.segments[ i0 * numPoints + i1 ] = true;
				this.segments[ i1 * numPoints + i0 ] = true;

291
				p0.set( coords[ 3 * i0 ], coords[ 3 * i0 + 1 ], coords[ 3 * i0 + 2 ] );
M
Mugen87 已提交
292
				p1.set( coords[ 3 * i1 ], coords[ 3 * i1 + 1 ], coords[ 3 * i1 + 2 ] );
293

M
Mugen87 已提交
294
				// mark: 1 for negative side, 2 for positive side, 3 for coplanar point
295
				var mark0 = 0;
296

297
				var d = localPlane.distanceToPoint( p0 );
M
Mugen87 已提交
298

299
				if ( d > delta ) {
M
Mugen87 已提交
300

301 302
					mark0 = 2;
					points2.push( p0.clone() );
M
Mugen87 已提交
303

304
				} else if ( d < - delta ) {
M
Mugen87 已提交
305

306 307
					mark0 = 1;
					points1.push( p0.clone() );
M
Mugen87 已提交
308

309
				} else {
M
Mugen87 已提交
310

311 312 313
					mark0 = 3;
					points1.push( p0.clone() );
					points2.push( p0.clone() );
314 315 316

				}

317 318
				// mark: 1 for negative side, 2 for positive side, 3 for coplanar point
				var mark1 = 0;
319

320
				var d = localPlane.distanceToPoint( p1 );
321

322
				if ( d > delta ) {
M
Mugen87 已提交
323

324 325
					mark1 = 2;
					points2.push( p1.clone() );
M
Mugen87 已提交
326

327
				} else if ( d < - delta ) {
M
Mugen87 已提交
328

329 330
					mark1 = 1;
					points1.push( p1.clone() );
M
Mugen87 已提交
331

332
				}	else {
M
Mugen87 已提交
333

334 335 336
					mark1 = 3;
					points1.push( p1.clone() );
					points2.push( p1.clone() );
337 338 339 340 341 342 343 344 345

				}

				if ( ( mark0 === 1 && mark1 === 2 ) || ( mark0 === 2 && mark1 === 1 ) ) {

					// Intersection of segment with the plane

					this.tempLine1.start.copy( p0 );
					this.tempLine1.end.copy( p1 );
M
Mugen87 已提交
346 347 348 349

					var intersection = new THREE.Vector3();
					intersection = localPlane.intersectLine( this.tempLine1, intersection );

350
					if ( intersection === undefined ) {
M
Mugen87 已提交
351

352
						// Shouldn't happen
G
Garrett Johnson 已提交
353
						console.error( 'Internal error: segment does not intersect plane.' );
354 355 356
						output.segmentedObject1 = null;
						output.segmentedObject2 = null;
						return 0;
M
Mugen87 已提交
357

358 359 360
					}

					points1.push( intersection );
361
					points2.push( intersection.clone() );
362 363 364 365 366 367 368 369 370 371 372 373 374 375

				}

			}

		}

		// Calculate debris mass (very fast and imprecise):
		var newMass = object.userData.mass * 0.5;

		// Calculate debris Center of Mass (again fast and imprecise)
		this.tempCM1.set( 0, 0, 0 );
		var radius1 = 0;
		var numPoints1 = points1.length;
M
Mugen87 已提交
376

377
		if ( numPoints1 > 0 ) {
M
Mugen87 已提交
378 379 380

			for ( var i = 0; i < numPoints1; i ++ ) this.tempCM1.add( points1[ i ] );

381
			this.tempCM1.divideScalar( numPoints1 );
M
Mugen87 已提交
382 383
			for ( var i = 0; i < numPoints1; i ++ ) {

384 385 386
				var p = points1[ i ];
				p.sub( this.tempCM1 );
				radius1 = Math.max( radius1, p.x, p.y, p.z );
M
Mugen87 已提交
387

388
			}
M
Mugen87 已提交
389

390
			this.tempCM1.add( object.position );
M
Mugen87 已提交
391

392 393 394 395 396 397
		}

		this.tempCM2.set( 0, 0, 0 );
		var radius2 = 0;
		var numPoints2 = points2.length;
		if ( numPoints2 > 0 ) {
M
Mugen87 已提交
398 399 400

			for ( var i = 0; i < numPoints2; i ++ ) this.tempCM2.add( points2[ i ] );

401
			this.tempCM2.divideScalar( numPoints2 );
M
Mugen87 已提交
402 403
			for ( var i = 0; i < numPoints2; i ++ ) {

404 405 406
				var p = points2[ i ];
				p.sub( this.tempCM2 );
				radius2 = Math.max( radius2, p.x, p.y, p.z );
M
Mugen87 已提交
407

408
			}
M
Mugen87 已提交
409

410
			this.tempCM2.add( object.position );
M
Mugen87 已提交
411

412 413 414 415 416 417 418 419 420
		}

		var object1 = null;
		var object2 = null;

		var numObjects = 0;

		if ( numPoints1 > 4 ) {

M
Mugen87 已提交
421
			object1 = new THREE.Mesh( new THREE.ConvexGeometry( points1 ), object.material );
422 423 424 425 426
			object1.position.copy( this.tempCM1 );
			object1.quaternion.copy( object.quaternion );

			this.prepareBreakableObject( object1, newMass, object.userData.velocity, object.userData.angularVelocity, 2 * radius1 > this.minSizeForBreak );

M
Mugen87 已提交
427
			numObjects ++;
428 429 430 431 432

		}

		if ( numPoints2 > 4 ) {

M
Mugen87 已提交
433
			object2 = new THREE.Mesh( new THREE.ConvexGeometry( points2 ), object.material );
434 435 436 437 438
			object2.position.copy( this.tempCM2 );
			object2.quaternion.copy( object.quaternion );

			this.prepareBreakableObject( object2, newMass, object.userData.velocity, object.userData.angularVelocity, 2 * radius2 > this.minSizeForBreak );

M
Mugen87 已提交
439
			numObjects ++;
440 441 442 443 444 445 446 447 448 449 450 451

		}

		output.object1 = object1;
		output.object2 = object2;

		return numObjects;

	}

};

M
Mugen87 已提交
452
THREE.ConvexObjectBreaker.transformFreeVector = function ( v, m ) {
453 454 455 456 457 458 459 460

	// input:
	// vector interpreted as a free vector
	// THREE.Matrix4 orthogonal matrix (matrix without scale)

	var x = v.x, y = v.y, z = v.z;
	var e = m.elements;

M
Mugen87 已提交
461 462
	v.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z;
	v.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z;
463 464 465 466 467 468
	v.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z;

	return v;

};

M
Mugen87 已提交
469
THREE.ConvexObjectBreaker.transformFreeVectorInverse = function ( v, m ) {
470 471 472 473 474 475 476 477

	// input:
	// vector interpreted as a free vector
	// THREE.Matrix4 orthogonal matrix (matrix without scale)

	var x = v.x, y = v.y, z = v.z;
	var e = m.elements;

M
Mugen87 已提交
478 479
	v.x = e[ 0 ] * x + e[ 1 ] * y + e[ 2 ] * z;
	v.y = e[ 4 ] * x + e[ 5 ] * y + e[ 6 ] * z;
480 481 482 483 484 485
	v.z = e[ 8 ] * x + e[ 9 ] * y + e[ 10 ] * z;

	return v;

};

M
Mugen87 已提交
486
THREE.ConvexObjectBreaker.transformTiedVectorInverse = function ( v, m ) {
487 488 489 490 491 492 493 494

	// input:
	// vector interpreted as a tied (ordinary) vector
	// THREE.Matrix4 orthogonal matrix (matrix without scale)

	var x = v.x, y = v.y, z = v.z;
	var e = m.elements;

M
Mugen87 已提交
495 496
	v.x = e[ 0 ] * x + e[ 1 ] * y + e[ 2 ] * z - e[ 12 ];
	v.y = e[ 4 ] * x + e[ 5 ] * y + e[ 6 ] * z - e[ 13 ];
497 498 499 500 501 502
	v.z = e[ 8 ] * x + e[ 9 ] * y + e[ 10 ] * z - e[ 14 ];

	return v;

};

M
Mugen87 已提交
503
THREE.ConvexObjectBreaker.transformPlaneToLocalSpace = function () {
504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522

	var v1 = new THREE.Vector3();

	return function transformPlaneToLocalSpace( plane, m, resultPlane ) {

		resultPlane.normal.copy( plane.normal );
		resultPlane.constant = plane.constant;

		var referencePoint = THREE.ConvexObjectBreaker.transformTiedVectorInverse( plane.coplanarPoint( v1 ), m );

		THREE.ConvexObjectBreaker.transformFreeVectorInverse( resultPlane.normal, m );

		// recalculate constant (like in setFromNormalAndCoplanarPoint)
		resultPlane.constant = - referencePoint.dot( resultPlane.normal );


	};

}();