Path.js 11.2 KB
Newer Older
Z
zz85 已提交
1 2
/**
 * @author zz85 / http://www.lab4games.net/zz85/blog
Z
zz85 已提交
3 4
 * Creates free form 2d path using series of points, lines or curves.
 *
Z
zz85 已提交
5 6
 **/

7 8
THREE.Path = function ( points ) {

9
	THREE.CurvePath.call(this);
10 11 12 13 14 15 16

	this.actions = [];

	if ( points ) {

		this.fromPoints( points );

Z
zz85 已提交
17
	}
18

Z
zz85 已提交
19 20
};

21
THREE.Path.prototype = Object.create( THREE.CurvePath.prototype );
22

23 24
THREE.PathActions = {

Z
zz85 已提交
25 26
	MOVE_TO: 'moveTo',
	LINE_TO: 'lineTo',
Z
zz85 已提交
27 28 29
	QUADRATIC_CURVE_TO: 'quadraticCurveTo', // Bezier quadratic curve
	BEZIER_CURVE_TO: 'bezierCurveTo', 		// Bezier cubic curve
	CSPLINE_THRU: 'splineThru',				// Catmull-rom spline
30 31
	ARC: 'arc',								// Circle
	ELLIPSE: 'ellipse'
Z
zz85 已提交
32 33
};

34
// TODO Clean up PATH API
35

36 37
// Create path using straight lines to connect all points
// - vectors: array of Vector2
38

39
THREE.Path.prototype.fromPoints = function ( vectors ) {
40 41 42

	this.moveTo( vectors[ 0 ].x, vectors[ 0 ].y );

Z
zz85 已提交
43
	for ( var v = 1, vlen = vectors.length; v < vlen; v ++ ) {
44 45 46

		this.lineTo( vectors[ v ].x, vectors[ v ].y );

Z
zz85 已提交
47
	};
48

Z
zz85 已提交
49 50
};

Z
zz85 已提交
51 52
// startPath() endPath()?

53
THREE.Path.prototype.moveTo = function ( x, y ) {
54 55 56 57

	var args = Array.prototype.slice.call( arguments );
	this.actions.push( { action: THREE.PathActions.MOVE_TO, args: args } );

Z
zz85 已提交
58 59
};

60
THREE.Path.prototype.lineTo = function ( x, y ) {
61 62

	var args = Array.prototype.slice.call( arguments );
63

Z
curves  
zz85 已提交
64
	var lastargs = this.actions[ this.actions.length - 1 ].args;
65

Z
curves  
zz85 已提交
66 67
	var x0 = lastargs[ lastargs.length - 2 ];
	var y0 = lastargs[ lastargs.length - 1 ];
68

69
	var curve = new THREE.LineCurve( new THREE.Vector2( x0, y0 ), new THREE.Vector2( x, y ) );
Z
zz85 已提交
70
	this.curves.push( curve );
71

72
	this.actions.push( { action: THREE.PathActions.LINE_TO, args: args } );
73

Z
zz85 已提交
74 75
};

76 77 78
THREE.Path.prototype.quadraticCurveTo = function( aCPx, aCPy, aX, aY ) {

	var args = Array.prototype.slice.call( arguments );
79

Z
curves  
zz85 已提交
80
	var lastargs = this.actions[ this.actions.length - 1 ].args;
81

Z
curves  
zz85 已提交
82 83
	var x0 = lastargs[ lastargs.length - 2 ];
	var y0 = lastargs[ lastargs.length - 1 ];
84

85 86 87
	var curve = new THREE.QuadraticBezierCurve( new THREE.Vector2( x0, y0 ),
												new THREE.Vector2( aCPx, aCPy ),
												new THREE.Vector2( aX, aY ) );
Z
zz85 已提交
88
	this.curves.push( curve );
89

90
	this.actions.push( { action: THREE.PathActions.QUADRATIC_CURVE_TO, args: args } );
91

Z
zz85 已提交
92 93
};

94 95
THREE.Path.prototype.bezierCurveTo = function( aCP1x, aCP1y,
                                               aCP2x, aCP2y,
96
                                               aX, aY ) {
97 98

	var args = Array.prototype.slice.call( arguments );
99

Z
curves  
zz85 已提交
100
	var lastargs = this.actions[ this.actions.length - 1 ].args;
101

Z
curves  
zz85 已提交
102 103 104
	var x0 = lastargs[ lastargs.length - 2 ];
	var y0 = lastargs[ lastargs.length - 1 ];

105 106 107 108
	var curve = new THREE.CubicBezierCurve( new THREE.Vector2( x0, y0 ),
											new THREE.Vector2( aCP1x, aCP1y ),
											new THREE.Vector2( aCP2x, aCP2y ),
											new THREE.Vector2( aX, aY ) );
Z
zz85 已提交
109
	this.curves.push( curve );
110

111
	this.actions.push( { action: THREE.PathActions.BEZIER_CURVE_TO, args: args } );
112

Z
zz85 已提交
113 114
};

Z
zz85 已提交
115
THREE.Path.prototype.splineThru = function( pts /*Array of Vector*/ ) {
116

Z
zz85 已提交
117
	var args = Array.prototype.slice.call( arguments );
Z
curves  
zz85 已提交
118
	var lastargs = this.actions[ this.actions.length - 1 ].args;
119

Z
curves  
zz85 已提交
120 121
	var x0 = lastargs[ lastargs.length - 2 ];
	var y0 = lastargs[ lastargs.length - 1 ];
Z
zz85 已提交
122
//---
123
	var npts = [ new THREE.Vector2( x0, y0 ) ];
124
	Array.prototype.push.apply( npts, pts );
125

Z
zz85 已提交
126 127
	var curve = new THREE.SplineCurve( npts );
	this.curves.push( curve );
128

129
	this.actions.push( { action: THREE.PathActions.CSPLINE_THRU, args: args } );
130

131
};
Z
zz85 已提交
132

Z
zz85 已提交
133
// FUTURE: Change the API or follow canvas API?
134

135
THREE.Path.prototype.ellipse = function ( aX, aY, xRadius, yRadius,
136
									  aStartAngle, aEndAngle, aClockwise ) {
137

138 139 140 141 142 143 144
	var laste = this.actions[ this.actions.length - 1];
	this.absellipse(laste.x + aX, laste.y + aY, xRadius, yRadius,
									aStartAngle, aEndAngle, aClockwise );
 };
 
THREE.Path.prototype.arc = function ( aX, aY, aRadius,
									  aStartAngle, aEndAngle, aClockwise ) {
A
alteredq 已提交
145

146
	var laste = this.actions[ this.actions.length - 1];
147 148 149 150 151 152 153 154 155 156
	this.absarc(laste.x + aX, laste.y + aY, aRadius,
									aStartAngle, aEndAngle, aClockwise );
 };
 


THREE.Path.prototype.absellipse = function ( aX, aY, xRadius, yRadius,
									  aStartAngle, aEndAngle, aClockwise ) {

	var args = Array.prototype.slice.call( arguments );
157

158
	var curve = new THREE.EllipseCurve( aX, aY, xRadius, yRadius,
159 160 161 162 163 164 165 166 167
									aStartAngle, aEndAngle, aClockwise );
	this.curves.push( curve );

	// All of the other actions look to the last two elements in the list to
	// find the ending point, so we need to append them.
	var lastPoint = curve.getPoint(aClockwise ? 1 : 0);
	args.push(lastPoint.x);
	args.push(lastPoint.y);

168
	this.actions.push( { action: THREE.PathActions.ELLIPSE, args: args } );
169 170 171 172 173

 };

THREE.Path.prototype.absarc = function ( aX, aY, aRadius,
									  aStartAngle, aEndAngle, aClockwise ) {
174 175
	this.absellipse(aX, aY, aRadius, aRadius,
		aStartAngle, aEndAngle, aClockwise);
Z
zz85 已提交
176 177
 };

Z
zz85 已提交
178

179
THREE.Path.prototype.getSpacedPoints = function ( divisions, closedPath ) {
180

Z
zz85 已提交
181
	if ( ! divisions ) divisions = 40;
182

183 184
	var points = [];

185
	for ( var i = 0; i < divisions; i ++ ) {
186

187
		points.push( this.getPoint( i / divisions ) );
188

189
		//if( !this.getPoint( i / divisions ) ) throw "DIE";
190

Z
zz85 已提交
191
	}
Z
zz85 已提交
192

193
	// if ( closedPath ) {
194
	//
195
	// 	points.push( points[ 0 ] );
196
	//
197
	// }
198 199

	return points;
200 201

};
Z
zz85 已提交
202

Z
zz85 已提交
203
/* Return an array of vectors based on contour of the path */
204

A
alteredq 已提交
205
THREE.Path.prototype.getPoints = function( divisions, closedPath ) {
206

207 208 209 210 211
	if (this.useSpacedPoints) {
		console.log('tata');
		return this.getSpacedPoints( divisions, closedPath );
	}

Z
zz85 已提交
212 213
	divisions = divisions || 12;

214 215 216 217 218 219 220
	var points = [];

	var i, il, item, action, args;
	var cpx, cpy, cpx2, cpy2, cpx1, cpy1, cpx0, cpy0,
		laste, j,
		t, tx, ty;

221
	for ( i = 0, il = this.actions.length; i < il; i ++ ) {
222 223 224 225 226 227 228 229 230 231

		item = this.actions[ i ];

		action = item.action;
		args = item.args;

		switch( action ) {

		case THREE.PathActions.MOVE_TO:

232
			points.push( new THREE.Vector2( args[ 0 ], args[ 1 ] ) );
Z
zz85 已提交
233 234 235

			break;

236 237 238 239
		case THREE.PathActions.LINE_TO:

			points.push( new THREE.Vector2( args[ 0 ], args[ 1 ] ) );

Z
zz85 已提交
240 241
			break;

242 243 244 245 246 247 248 249 250 251 252
		case THREE.PathActions.QUADRATIC_CURVE_TO:

			cpx  = args[ 2 ];
			cpy  = args[ 3 ];

			cpx1 = args[ 0 ];
			cpy1 = args[ 1 ];

			if ( points.length > 0 ) {

				laste = points[ points.length - 1 ];
Z
zz85 已提交
253 254 255

				cpx0 = laste.x;
				cpy0 = laste.y;
256

Z
zz85 已提交
257 258
			} else {

259 260 261 262
				laste = this.actions[ i - 1 ].args;

				cpx0 = laste[ laste.length - 2 ];
				cpy0 = laste[ laste.length - 1 ];
Z
zz85 已提交
263 264 265

			}

266 267 268 269
			for ( j = 1; j <= divisions; j ++ ) {

				t = j / divisions;

270 271
				tx = THREE.Shape.Utils.b2( t, cpx0, cpx1, cpx );
				ty = THREE.Shape.Utils.b2( t, cpy0, cpy1, cpy );
272 273 274 275 276

				points.push( new THREE.Vector2( tx, ty ) );

		  	}

Z
zz85 已提交
277 278
			break;

279
		case THREE.PathActions.BEZIER_CURVE_TO:
Z
zz85 已提交
280

281 282
			cpx  = args[ 4 ];
			cpy  = args[ 5 ];
Z
zz85 已提交
283

284 285
			cpx1 = args[ 0 ];
			cpy1 = args[ 1 ];
Z
zz85 已提交
286

287 288
			cpx2 = args[ 2 ];
			cpy2 = args[ 3 ];
Z
zz85 已提交
289

290
			if ( points.length > 0 ) {
Z
zz85 已提交
291

292
				laste = points[ points.length - 1 ];
Z
zz85 已提交
293

294 295
				cpx0 = laste.x;
				cpy0 = laste.y;
Z
zz85 已提交
296

297
			} else {
Z
zz85 已提交
298

299
				laste = this.actions[ i - 1 ].args;
Z
zz85 已提交
300

301 302
				cpx0 = laste[ laste.length - 2 ];
				cpy0 = laste[ laste.length - 1 ];
Z
zz85 已提交
303

304
			}
Z
zz85 已提交
305 306


307
			for ( j = 1; j <= divisions; j ++ ) {
Z
zz85 已提交
308

309
				t = j / divisions;
Z
zz85 已提交
310

311 312
				tx = THREE.Shape.Utils.b3( t, cpx0, cpx1, cpx2, cpx );
				ty = THREE.Shape.Utils.b3( t, cpy0, cpy1, cpy2, cpy );
Z
zz85 已提交
313

314
				points.push( new THREE.Vector2( tx, ty ) );
Z
zz85 已提交
315

316
			}
Z
zz85 已提交
317

318
			break;
Z
zz85 已提交
319

Z
zz85 已提交
320
		case THREE.PathActions.CSPLINE_THRU:
321

322
			laste = this.actions[ i - 1 ].args;
323

324
			var last = new THREE.Vector2( laste[ laste.length - 2 ], laste[ laste.length - 1 ] );
325
			var spts = [ last ];
326

Z
zz85 已提交
327
			var n = divisions * args[ 0 ].length;
328

329
			spts = spts.concat( args[ 0 ] );
330

331
			var spline = new THREE.SplineCurve( spts );
332

Z
zz85 已提交
333
			for ( j = 1; j <= n; j ++ ) {
334

Z
zz85 已提交
335
				points.push( spline.getPointAt( j / n ) ) ;
336

337
			}
338

Z
zz85 已提交
339
			break;
Z
zz85 已提交
340

Z
zz85 已提交
341
		case THREE.PathActions.ARC:
Z
zz85 已提交
342

343 344 345 346
			var aX = args[ 0 ], aY = args[ 1 ],
				aRadius = args[ 2 ],
				aStartAngle = args[ 3 ], aEndAngle = args[ 4 ],
				aClockwise = !!args[ 5 ];
Z
zz85 已提交
347

348

Z
zz85 已提交
349 350
			var deltaAngle = aEndAngle - aStartAngle;
			var angle;
Z
zz85 已提交
351
			var tdivisions = divisions * 2;
352

Z
zz85 已提交
353
			for ( j = 1; j <= tdivisions; j ++ ) {
354

355
				t = j / tdivisions;
356

Z
zz85 已提交
357
				if ( ! aClockwise ) {
358

Z
zz85 已提交
359
					t = 1 - t;
360

Z
zz85 已提交
361
				}
362

Z
zz85 已提交
363
				angle = aStartAngle + t * deltaAngle;
364

365 366
				tx = aX + aRadius * Math.cos( angle );
				ty = aY + aRadius * Math.sin( angle );
367

Z
zz85 已提交
368
				//console.log('t', t, 'angle', angle, 'tx', tx, 'ty', ty);
369

Z
zz85 已提交
370
				points.push( new THREE.Vector2( tx, ty ) );
371

Z
zz85 已提交
372
			}
373

Z
zz85 已提交
374
			//console.log(points);
Z
zz85 已提交
375 376

		  break;
377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414
		  
		case THREE.PathActions.ELLIPSE:

			var aX = args[ 0 ], aY = args[ 1 ],
				xRadius = args[ 2 ],
				yRadius = args[3]
				aStartAngle = args[ 4 ], aEndAngle = args[ 5 ],
				aClockwise = !!args[ 6 ];


			var deltaAngle = aEndAngle - aStartAngle;
			var angle;
			var tdivisions = divisions * 2;

			for ( j = 1; j <= tdivisions; j ++ ) {

				t = j / tdivisions;

				if ( ! aClockwise ) {

					t = 1 - t;

				}

				angle = aStartAngle + t * deltaAngle;

				tx = aX + xRadius * Math.cos( angle );
				ty = aY + yRadius * Math.sin( angle );

				//console.log('t', t, 'angle', angle, 'tx', tx, 'ty', ty);

				points.push( new THREE.Vector2( tx, ty ) );

			}

			//console.log(points);

		  break;
Z
zz85 已提交
415 416

		} // end switch
Z
zz85 已提交
417

418 419
	}

420 421 422 423 424 425 426 427


	// Normalize to remove the closing point by default.
	var lastPoint = points[ points.length - 1];
	var EPSILON = 0.0000000001;
	if ( Math.abs(lastPoint.x - points[ 0 ].x) < EPSILON &&
             Math.abs(lastPoint.y - points[ 0 ].y) < EPSILON)
		points.splice( points.length - 1, 1);
A
alteredq 已提交
428 429 430 431 432 433
	if ( closedPath ) {

		points.push( points[ 0 ] );

	}

434
	return points;
Z
zz85 已提交
435

436
};
Z
zz85 已提交
437

438
// Breaks path into shapes
439

440
THREE.Path.prototype.toShapes = function() {
441

442 443 444 445 446 447 448 449 450 451
	var i, il, item, action, args;

	var subPaths = [], lastPath = new THREE.Path();

	for ( i = 0, il = this.actions.length; i < il; i ++ ) {

		item = this.actions[ i ];

		args = item.args;
		action = item.action;
452 453 454 455 456 457

		if ( action == THREE.PathActions.MOVE_TO ) {

			if ( lastPath.actions.length != 0 ) {

				subPaths.push( lastPath );
458
				lastPath = new THREE.Path();
459

460
			}
461

462
		}
463 464 465

		lastPath[ action ].apply( lastPath, args );

466
	}
467 468 469 470 471

	if ( lastPath.actions.length != 0 ) {

		subPaths.push( lastPath );

472
	}
473

Z
zz85 已提交
474
	// console.log(subPaths);
475

476
	if ( subPaths.length == 0 ) return [];
477

Z
zz85 已提交
478 479
	var tmpPath, tmpShape, shapes = [];

480
	var holesFirst = !THREE.Shape.Utils.isClockWise( subPaths[ 0 ].getPoints() );
Z
zz85 已提交
481 482
	// console.log("Holes first", holesFirst);

Z
zz85 已提交
483 484 485 486 487
	if ( subPaths.length == 1) {
		tmpPath = subPaths[0];
		tmpShape = new THREE.Shape();
		tmpShape.actions = tmpPath.actions;
		tmpShape.curves = tmpPath.curves;
Z
zz85 已提交
488 489
		shapes.push( tmpShape );
		return shapes;
Z
zz85 已提交
490
	};
491 492 493

	if ( holesFirst ) {

494
		tmpShape = new THREE.Shape();
495 496 497 498 499 500 501

		for ( i = 0, il = subPaths.length; i < il; i ++ ) {

			tmpPath = subPaths[ i ];

			if ( THREE.Shape.Utils.isClockWise( tmpPath.getPoints() ) ) {

502 503
				tmpShape.actions = tmpPath.actions;
				tmpShape.curves = tmpPath.curves;
504 505

				shapes.push( tmpShape );
506
				tmpShape = new THREE.Shape();
507

508
				//console.log('cw', i);
509

510
			} else {
511 512 513

				tmpShape.holes.push( tmpPath );

514
				//console.log('ccw', i);
515

516
			}
517

518
		}
519

520
	} else {
521

522
		// Shapes first
523 524 525 526 527 528 529 530 531 532

		for ( i = 0, il = subPaths.length; i < il; i ++ ) {

			tmpPath = subPaths[ i ];

			if ( THREE.Shape.Utils.isClockWise( tmpPath.getPoints() ) ) {


				if ( tmpShape ) shapes.push( tmpShape );

533 534 535
				tmpShape = new THREE.Shape();
				tmpShape.actions = tmpPath.actions;
				tmpShape.curves = tmpPath.curves;
536

537
			} else {
538 539 540

				tmpShape.holes.push( tmpPath );

541
			}
542

543
		}
544 545 546

		shapes.push( tmpShape );

547
	}
548

549
	//console.log("shape", shapes);
550

551
	return shapes;
552

Z
zz85 已提交
553
};