Path.js 14.4 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 ) {

G
gero3 已提交
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.assign( Object.create( THREE.CurvePath.prototype ), {
22

23
	constructor: THREE.Path,
24

25
	// TODO Clean up PATH API
26

27 28
	// Create path using straight lines to connect all points
	// - vectors: array of Vector2
29

30
	fromPoints: function ( vectors ) {
31

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

34
		for ( var i = 1, l = vectors.length; i < l; i ++ ) {
35

36
			this.lineTo( vectors[ i ].x, vectors[ i ].y );
37

38
		}
Z
zz85 已提交
39

40
	},
Z
zz85 已提交
41

42
	moveTo: function ( x, y ) {
43

44
		this.actions.push( { action: 'moveTo', args: [ x, y ] } );
45

46
	},
Z
zz85 已提交
47

48
	lineTo: function ( x, y ) {
49

50
		var lastargs = this.actions[ this.actions.length - 1 ].args;
51

52 53
		var x0 = lastargs[ lastargs.length - 2 ];
		var y0 = lastargs[ lastargs.length - 1 ];
54

55 56
		var curve = new THREE.LineCurve( new THREE.Vector2( x0, y0 ), new THREE.Vector2( x, y ) );
		this.curves.push( curve );
57

58
		this.actions.push( { action: 'lineTo', args: [ x, y ] } );
59

60
	},
Z
zz85 已提交
61

62
	quadraticCurveTo: function ( aCPx, aCPy, aX, aY ) {
63

64
		var lastargs = this.actions[ this.actions.length - 1 ].args;
65

66 67
		var x0 = lastargs[ lastargs.length - 2 ];
		var y0 = lastargs[ lastargs.length - 1 ];
68

69 70 71 72 73
		var curve = new THREE.QuadraticBezierCurve(
			new THREE.Vector2( x0, y0 ),
			new THREE.Vector2( aCPx, aCPy ),
			new THREE.Vector2( aX, aY )
		);
M
Mr.doob 已提交
74

75
		this.curves.push( curve );
76

77
		this.actions.push( { action: 'quadraticCurveTo', args: [ aCPx, aCPy, aX, aY ] } );
78

79
	},
Z
zz85 已提交
80

81
	bezierCurveTo: function ( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ) {
82

83
		var lastargs = this.actions[ this.actions.length - 1 ].args;
84

85 86
		var x0 = lastargs[ lastargs.length - 2 ];
		var y0 = lastargs[ lastargs.length - 1 ];
Z
curves  
zz85 已提交
87

88 89 90 91 92 93
		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 )
		);
M
Mr.doob 已提交
94

95
		this.curves.push( curve );
96

97
		this.actions.push( { action: 'bezierCurveTo', args: [ aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ] } );
98

99
	},
Z
zz85 已提交
100

101
	splineThru: function ( pts /*Array of Vector*/ ) {
102

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

105
		var lastargs = this.actions[ this.actions.length - 1 ].args;
106

107 108
		var x0 = lastargs[ lastargs.length - 2 ];
		var y0 = lastargs[ lastargs.length - 1 ];
M
Mr.doob 已提交
109

110 111
		var npts = [ new THREE.Vector2( x0, y0 ) ];
		Array.prototype.push.apply( npts, pts );
112

113 114
		var curve = new THREE.SplineCurve( npts );
		this.curves.push( curve );
115

116
		this.actions.push( { action: 'splineThru', args: args } );
117

118
	},
Z
zz85 已提交
119

120
	arc: function ( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) {
121

122 123 124
		var lastargs = this.actions[ this.actions.length - 1 ].args;
		var x0 = lastargs[ lastargs.length - 2 ];
		var y0 = lastargs[ lastargs.length - 1 ];
125

126 127
		this.absarc( aX + x0, aY + y0, aRadius,
			aStartAngle, aEndAngle, aClockwise );
128

129
	},
Z
zz85 已提交
130

131
	absarc: function ( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) {
132

133
		this.absellipse( aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise );
G
gero3 已提交
134

135
	},
G
gero3 已提交
136

137
	ellipse: function ( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) {
Z
zz85 已提交
138

139 140 141
		var lastargs = this.actions[ this.actions.length - 1 ].args;
		var x0 = lastargs[ lastargs.length - 2 ];
		var y0 = lastargs[ lastargs.length - 1 ];
A
alteredq 已提交
142

143
		this.absellipse( aX + x0, aY + y0, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation );
144

145
	},
146

147
	absellipse: function ( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) {
Z
zz85 已提交
148

149 150 151 152 153 154 155
		var args = [
			aX, aY,
			xRadius, yRadius,
			aStartAngle, aEndAngle,
			aClockwise,
			aRotation || 0 // aRotation is optional.
		];
156

157 158
		var curve = new THREE.EllipseCurve( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation );
		this.curves.push( curve );
159

160 161 162
		var lastPoint = curve.getPoint( 1 );
		args.push( lastPoint.x );
		args.push( lastPoint.y );
M
Mr.doob 已提交
163

164
		this.actions.push( { action: 'ellipse', args: args } );
165

166
	},
167

168
	getSpacedPoints: function ( divisions ) {
169

170
		if ( ! divisions ) divisions = 40;
171

172
		var points = [];
173

174
		for ( var i = 0; i < divisions; i ++ ) {
175

176
			points.push( this.getPoint( i / divisions ) );
177

178
			//if ( !this.getPoint( i / divisions ) ) throw "DIE";
179

180
		}
181

182
		if ( this.autoClose ) {
183

184
			points.push( points[ 0 ] );
Z
zz85 已提交
185

186
		}
W
WestLangley 已提交
187

188
		return points;
W
WestLangley 已提交
189

190
	},
191

192
	getPoints: function ( divisions ) {
193

194
		divisions = divisions || 12;
Z
zz85 已提交
195

196 197
		var b2 = THREE.ShapeUtils.b2;
		var b3 = THREE.ShapeUtils.b3;
198

199
		var points = [];
200

201 202
		var cpx, cpy, cpx2, cpy2, cpx1, cpy1, cpx0, cpy0,
			laste, tx, ty;
Z
zz85 已提交
203

204
		for ( var i = 0, l = this.actions.length; i < l; i ++ ) {
M
Mr.doob 已提交
205

206
			var item = this.actions[ i ];
207

208 209
			var action = item.action;
			var args = item.args;
210

211
			switch ( action ) {
212

213
			case 'moveTo':
214

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

217
				break;
218

219
			case 'lineTo':
220

221
				points.push( new THREE.Vector2( args[ 0 ], args[ 1 ] ) );
Z
zz85 已提交
222

223
				break;
Z
zz85 已提交
224

225
			case 'quadraticCurveTo':
226

227 228
				cpx  = args[ 2 ];
				cpy  = args[ 3 ];
229

230 231
				cpx1 = args[ 0 ];
				cpy1 = args[ 1 ];
Z
zz85 已提交
232

233
				if ( points.length > 0 ) {
234

235
					laste = points[ points.length - 1 ];
236

237 238
					cpx0 = laste.x;
					cpy0 = laste.y;
239

240
				} else {
241

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

244 245
					cpx0 = laste[ laste.length - 2 ];
					cpy0 = laste[ laste.length - 1 ];
246

247
				}
Z
zz85 已提交
248

249
				for ( var j = 1; j <= divisions; j ++ ) {
250

251
					var t = j / divisions;
Z
zz85 已提交
252

253 254
					tx = b2( t, cpx0, cpx1, cpx );
					ty = b2( t, cpy0, cpy1, cpy );
Z
zz85 已提交
255

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

258
				}
259

260
				break;
261

262
			case 'bezierCurveTo':
263

264 265
				cpx  = args[ 4 ];
				cpy  = args[ 5 ];
266

267 268
				cpx1 = args[ 0 ];
				cpy1 = args[ 1 ];
Z
zz85 已提交
269

270 271
				cpx2 = args[ 2 ];
				cpy2 = args[ 3 ];
Z
zz85 已提交
272

273
				if ( points.length > 0 ) {
Z
zz85 已提交
274

275
					laste = points[ points.length - 1 ];
Z
zz85 已提交
276

277 278
					cpx0 = laste.x;
					cpy0 = laste.y;
Z
zz85 已提交
279

280
				} else {
Z
zz85 已提交
281

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

284 285
					cpx0 = laste[ laste.length - 2 ];
					cpy0 = laste[ laste.length - 1 ];
Z
zz85 已提交
286

287
				}
Z
zz85 已提交
288 289


290
				for ( var j = 1; j <= divisions; j ++ ) {
Z
zz85 已提交
291

292
					var t = j / divisions;
Z
zz85 已提交
293

294 295
					tx = b3( t, cpx0, cpx1, cpx2, cpx );
					ty = b3( t, cpy0, cpy1, cpy2, cpy );
Z
zz85 已提交
296

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

299
				}
Z
zz85 已提交
300

301
				break;
Z
zz85 已提交
302

303
			case 'splineThru':
Z
zz85 已提交
304

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

307 308
				var last = new THREE.Vector2( laste[ laste.length - 2 ], laste[ laste.length - 1 ] );
				var spts = [ last ];
Z
zz85 已提交
309

310
				var n = divisions * args[ 0 ].length;
311

312
				spts = spts.concat( args[ 0 ] );
313

314
				var spline = new THREE.SplineCurve( spts );
315

316
				for ( var j = 1; j <= n; j ++ ) {
317

318
					points.push( spline.getPointAt( j / n ) );
319

320
				}
321

322
				break;
323

324
			case 'arc':
325

326 327 328 329
				var aX = args[ 0 ], aY = args[ 1 ],
					aRadius = args[ 2 ],
					aStartAngle = args[ 3 ], aEndAngle = args[ 4 ],
					aClockwise = !! args[ 5 ];
330

331 332 333
				var deltaAngle = aEndAngle - aStartAngle;
				var angle;
				var tdivisions = divisions * 2;
Z
zz85 已提交
334

335
				for ( var j = 1; j <= tdivisions; j ++ ) {
Z
zz85 已提交
336

337
					var t = j / tdivisions;
Z
zz85 已提交
338

339
					if ( ! aClockwise ) {
340

341
						t = 1 - t;
342

343
					}
344

345
					angle = aStartAngle + t * deltaAngle;
346

347 348
					tx = aX + aRadius * Math.cos( angle );
					ty = aY + aRadius * Math.sin( angle );
349

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

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

354
				}
355

356
				//console.log(points);
357

358
				break;
359

360
			case 'ellipse':
361

362 363 364 365 366 367
				var aX = args[ 0 ], aY = args[ 1 ],
					xRadius = args[ 2 ],
					yRadius = args[ 3 ],
					aStartAngle = args[ 4 ], aEndAngle = args[ 5 ],
					aClockwise = !! args[ 6 ],
					aRotation = args[ 7 ];
Z
zz85 已提交
368

F
Fabian Lange 已提交
369

370 371 372
				var deltaAngle = aEndAngle - aStartAngle;
				var angle;
				var tdivisions = divisions * 2;
373

374 375
				var cos, sin;
				if ( aRotation !== 0 ) {
376

377 378
					cos = Math.cos( aRotation );
					sin = Math.sin( aRotation );
379

380
				}
381

382
				for ( var j = 1; j <= tdivisions; j ++ ) {
M
Mr.doob 已提交
383

384
					var t = j / tdivisions;
385

386
					if ( ! aClockwise ) {
387

388
						t = 1 - t;
389

390
					}
391

392
					angle = aStartAngle + t * deltaAngle;
393

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

397
					if ( aRotation !== 0 ) {
398

399
						var x = tx, y = ty;
400

401 402 403
						// Rotate the point about the center of the ellipse.
						tx = ( x - aX ) * cos - ( y - aY ) * sin + aX;
						ty = ( x - aX ) * sin + ( y - aY ) * cos + aY;
404

405
					}
406

407
					//console.log('t', t, 'angle', angle, 'tx', tx, 'ty', ty);
N
neko 已提交
408

409
					points.push( new THREE.Vector2( tx, ty ) );
410 411 412

				}

413
				//console.log(points);
414

415
				break;
Z
zz85 已提交
416

417
			} // end switch
Z
zz85 已提交
418

419
		}
420

421 422


423 424 425 426 427
		// Normalize to remove the closing point by default.
		var lastPoint = points[ points.length - 1 ];
		if ( Math.abs( lastPoint.x - points[ 0 ].x ) < Number.EPSILON &&
				 Math.abs( lastPoint.y - points[ 0 ].y ) < Number.EPSILON )
			points.splice( points.length - 1, 1 );
W
WestLangley 已提交
428

429
		if ( this.autoClose ) {
A
alteredq 已提交
430

431
			points.push( points[ 0 ] );
A
alteredq 已提交
432

433
		}
A
alteredq 已提交
434

435
		return points;
Z
zz85 已提交
436

437
	},
Z
zz85 已提交
438

439
	toShapes: function ( isCCW, noHoles ) {
440

441
		function extractSubpaths( inActions ) {
442

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

445
			for ( var i = 0, l = inActions.length; i < l; i ++ ) {
446

447
				var item = inActions[ i ];
448

449 450
				var args = item.args;
				var action = item.action;
451

452
				if ( action === 'moveTo' ) {
453

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

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

459
					}
460 461 462

				}

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

465 466
			}

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

469
				subPaths.push( lastPath );
470

471 472 473
			}

			// console.log(subPaths);
474

475
			return	subPaths;
476 477 478

		}

479
		function toShapesNoHoles( inSubpaths ) {
480

481
			var shapes = [];
G
gero3 已提交
482

483
			for ( var i = 0, l = inSubpaths.length; i < l; i ++ ) {
484

485
				var tmpPath = inSubpaths[ i ];
486

487 488 489
				var tmpShape = new THREE.Shape();
				tmpShape.actions = tmpPath.actions;
				tmpShape.curves = tmpPath.curves;
490

491
				shapes.push( tmpShape );
492

493
			}
494

495
			//console.log("shape", shapes);
496

497
			return shapes;
G
gero3 已提交
498

499 500
		}

501
		function isPointInsidePolygon( inPt, inPolygon ) {
502

503
			var polyLen = inPolygon.length;
G
gero3 已提交
504

505 506 507 508 509 510
			// inPt on polygon contour => immediate success    or
			// toggling of inside/outside at every single! intersection point of an edge
			//  with the horizontal line through inPt, left of inPt
			//  not counting lowerY endpoints of edges and whole edges on that line
			var inside = false;
			for ( var p = polyLen - 1, q = 0; q < polyLen; p = q ++ ) {
511

512 513
				var edgeLowPt  = inPolygon[ p ];
				var edgeHighPt = inPolygon[ q ];
G
gero3 已提交
514

515 516
				var edgeDx = edgeHighPt.x - edgeLowPt.x;
				var edgeDy = edgeHighPt.y - edgeLowPt.y;
517

518
				if ( Math.abs( edgeDy ) > Number.EPSILON ) {
G
gero3 已提交
519

520 521
					// not parallel
					if ( edgeDy < 0 ) {
522

523 524
						edgeLowPt  = inPolygon[ q ]; edgeDx = - edgeDx;
						edgeHighPt = inPolygon[ p ]; edgeDy = - edgeDy;
525

526 527
					}
					if ( ( inPt.y < edgeLowPt.y ) || ( inPt.y > edgeHighPt.y ) ) 		continue;
G
gero3 已提交
528

529
					if ( inPt.y === edgeLowPt.y ) {
G
gero3 已提交
530

531 532
						if ( inPt.x === edgeLowPt.x )		return	true;		// inPt is on contour ?
						// continue;				// no intersection or edgeLowPt => doesn't count !!!
G
gero3 已提交
533

534
					} else {
535

536 537 538 539
						var perpEdge = edgeDy * ( inPt.x - edgeLowPt.x ) - edgeDx * ( inPt.y - edgeLowPt.y );
						if ( perpEdge === 0 )				return	true;		// inPt is on contour ?
						if ( perpEdge < 0 ) 				continue;
						inside = ! inside;		// true intersection left of inPt
G
gero3 已提交
540

541
					}
G
gero3 已提交
542

543
				} else {
G
gero3 已提交
544

545 546 547 548 549 550
					// parallel or collinear
					if ( inPt.y !== edgeLowPt.y ) 		continue;			// parallel
					// edge lies on the same horizontal line as inPt
					if ( ( ( edgeHighPt.x <= inPt.x ) && ( inPt.x <= edgeLowPt.x ) ) ||
						 ( ( edgeLowPt.x <= inPt.x ) && ( inPt.x <= edgeHighPt.x ) ) )		return	true;	// inPt: Point on contour !
					// continue;
G
gero3 已提交
551

552
				}
G
gero3 已提交
553

554
			}
G
gero3 已提交
555

556 557
			return	inside;

558 559
		}

560
		var isClockWise = THREE.ShapeUtils.isClockWise;
G
gero3 已提交
561

562 563
		var subPaths = extractSubpaths( this.actions );
		if ( subPaths.length === 0 ) return [];
564

565
		if ( noHoles === true )	return	toShapesNoHoles( subPaths );
566 567


568
		var solid, tmpPath, tmpShape, shapes = [];
569

570
		if ( subPaths.length === 1 ) {
571

572 573 574 575 576 577
			tmpPath = subPaths[ 0 ];
			tmpShape = new THREE.Shape();
			tmpShape.actions = tmpPath.actions;
			tmpShape.curves = tmpPath.curves;
			shapes.push( tmpShape );
			return shapes;
Z
zz85 已提交
578

579
		}
580

581 582
		var holesFirst = ! isClockWise( subPaths[ 0 ].getPoints() );
		holesFirst = isCCW ? ! holesFirst : holesFirst;
583

584
		// console.log("Holes first", holesFirst);
585

586 587 588 589 590
		var betterShapeHoles = [];
		var newShapes = [];
		var newShapeHoles = [];
		var mainIdx = 0;
		var tmpPoints;
F
Fabian Lange 已提交
591

592 593
		newShapes[ mainIdx ] = undefined;
		newShapeHoles[ mainIdx ] = [];
594

595
		for ( var i = 0, l = subPaths.length; i < l; i ++ ) {
596

597 598 599 600
			tmpPath = subPaths[ i ];
			tmpPoints = tmpPath.getPoints();
			solid = isClockWise( tmpPoints );
			solid = isCCW ? ! solid : solid;
601

602
			if ( solid ) {
603

604
				if ( ( ! holesFirst ) && ( newShapes[ mainIdx ] ) )	mainIdx ++;
605

606 607 608
				newShapes[ mainIdx ] = { s: new THREE.Shape(), p: tmpPoints };
				newShapes[ mainIdx ].s.actions = tmpPath.actions;
				newShapes[ mainIdx ].s.curves = tmpPath.curves;
609

610 611
				if ( holesFirst )	mainIdx ++;
				newShapeHoles[ mainIdx ] = [];
F
Fabian Lange 已提交
612

613
				//console.log('cw', i);
614

615
			} else {
616

617
				newShapeHoles[ mainIdx ].push( { h: tmpPath, p: tmpPoints[ 0 ] } );
618

619
				//console.log('ccw', i);
620

621
			}
622

Z
zz85 已提交
623
		}
624

625 626
		// only Holes? -> probably all Shapes with wrong orientation
		if ( ! newShapes[ 0 ] )	return	toShapesNoHoles( subPaths );
627

628

629
		if ( newShapes.length > 1 ) {
630

631 632
			var ambiguous = false;
			var toChange = [];
G
gero3 已提交
633

634
			for ( var sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx ++ ) {
635

636
				betterShapeHoles[ sIdx ] = [];
G
gero3 已提交
637

638
			}
G
gero3 已提交
639

640
			for ( var sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx ++ ) {
M
Mr.doob 已提交
641

642
				var sho = newShapeHoles[ sIdx ];
G
gero3 已提交
643

644
				for ( var hIdx = 0; hIdx < sho.length; hIdx ++ ) {
M
Mr.doob 已提交
645

646 647
					var ho = sho[ hIdx ];
					var hole_unassigned = true;
G
gero3 已提交
648

649
					for ( var s2Idx = 0; s2Idx < newShapes.length; s2Idx ++ ) {
M
Mr.doob 已提交
650

651
						if ( isPointInsidePolygon( ho.p, newShapes[ s2Idx ].p ) ) {
G
gero3 已提交
652

653 654
							if ( sIdx !== s2Idx )	toChange.push( { froms: sIdx, tos: s2Idx, hole: hIdx } );
							if ( hole_unassigned ) {
G
gero3 已提交
655

656 657
								hole_unassigned = false;
								betterShapeHoles[ s2Idx ].push( ho );
G
gero3 已提交
658

659
							} else {
G
gero3 已提交
660

661
								ambiguous = true;
G
gero3 已提交
662

663
							}
G
gero3 已提交
664

665
						}
G
gero3 已提交
666

667
					}
668
					if ( hole_unassigned ) {
G
gero3 已提交
669

670
						betterShapeHoles[ sIdx ].push( ho );
G
gero3 已提交
671

672
					}
G
gero3 已提交
673 674 675

				}

Z
zz85 已提交
676
			}
677 678
			// console.log("ambiguous: ", ambiguous);
			if ( toChange.length > 0 ) {
G
gero3 已提交
679

680 681
				// console.log("to change: ", toChange);
				if ( ! ambiguous )	newShapeHoles = betterShapeHoles;
G
gero3 已提交
682

683
			}
G
gero3 已提交
684

685
		}
G
gero3 已提交
686

687
		var tmpHoles;
Z
zz85 已提交
688

689
		for ( var i = 0, il = newShapes.length; i < il; i ++ ) {
M
Mr.doob 已提交
690

691 692 693
			tmpShape = newShapes[ i ].s;
			shapes.push( tmpShape );
			tmpHoles = newShapeHoles[ i ];
G
gero3 已提交
694

695
			for ( var j = 0, jl = tmpHoles.length; j < jl; j ++ ) {
M
Mr.doob 已提交
696

697
				tmpShape.holes.push( tmpHoles[ j ].h );
G
gero3 已提交
698

699
			}
G
gero3 已提交
700

701
		}
G
gero3 已提交
702

703
		//console.log("shape", shapes);
704

705
		return shapes;
706

707
	}
708

709
} );