Path.js 8.7 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 );
J
Joshua Koo 已提交
10
	this.currentPoint = new THREE.Vector2();
11 12 13 14 15

	if ( points ) {

		this.fromPoints( points );

Z
zz85 已提交
16
	}
17

Z
zz85 已提交
18 19
};

20
THREE.Path.prototype = Object.assign( Object.create( THREE.CurvePath.prototype ), {
21

22
	constructor: THREE.Path,
23

24 25 26
	// Create path using straight lines to connect all points
	// - vectors: array of Vector2
	fromPoints: function ( vectors ) {
27

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

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

32
			this.lineTo( vectors[ i ].x, vectors[ i ].y );
33

34
		}
Z
zz85 已提交
35

36
	},
Z
zz85 已提交
37

38
	moveTo: function ( x, y ) {
39

J
Joshua Koo 已提交
40
		this.currentPoint.set( x, y ); // TODO consider referencing vectors instead of copying?
41

42
	},
Z
zz85 已提交
43

44
	lineTo: function ( x, y ) {
45

J
Joshua Koo 已提交
46
		var curve = new THREE.LineCurve( this.currentPoint.clone(), new THREE.Vector2( x, y ) );
47
		this.curves.push( curve );
48

J
Joshua Koo 已提交
49
		this.currentPoint.set( x, y );
50

51
	},
Z
zz85 已提交
52

53
	quadraticCurveTo: function ( aCPx, aCPy, aX, aY ) {
54

55
		var curve = new THREE.QuadraticBezierCurve(
J
Joshua Koo 已提交
56
			this.currentPoint.clone(),
57 58 59
			new THREE.Vector2( aCPx, aCPy ),
			new THREE.Vector2( aX, aY )
		);
M
Mr.doob 已提交
60

61
		this.curves.push( curve );
62

J
Joshua Koo 已提交
63
		this.currentPoint.set( aX, aY );
64

65
	},
Z
zz85 已提交
66

67
	bezierCurveTo: function ( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ) {
68

69
		var curve = new THREE.CubicBezierCurve(
J
Joshua Koo 已提交
70
			this.currentPoint.clone(),
71 72 73 74
			new THREE.Vector2( aCP1x, aCP1y ),
			new THREE.Vector2( aCP2x, aCP2y ),
			new THREE.Vector2( aX, aY )
		);
M
Mr.doob 已提交
75

76
		this.curves.push( curve );
77

J
Joshua Koo 已提交
78
		this.currentPoint.set( aX, aY );
79

80
	},
Z
zz85 已提交
81

82
	splineThru: function ( pts /*Array of Vector*/ ) {
83

J
Joshua Koo 已提交
84
		var npts = [ this.currentPoint.clone() ].concat( pts );
85

86 87
		var curve = new THREE.SplineCurve( npts );
		this.curves.push( curve );
88

J
Joshua Koo 已提交
89
		this.currentPoint.copy( pts[ pts.length - 1 ] );
90

91
	},
Z
zz85 已提交
92

93
	arc: function ( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) {
94

J
Joshua Koo 已提交
95 96
		var x0 = this.currentPoint.x;
		var y0 = this.currentPoint.y;
97

98 99
		this.absarc( aX + x0, aY + y0, aRadius,
			aStartAngle, aEndAngle, aClockwise );
100

101
	},
Z
zz85 已提交
102

103
	absarc: function ( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) {
104

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

107
	},
G
gero3 已提交
108

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

J
Joshua Koo 已提交
111 112
		var x0 = this.currentPoint.x;
		var y0 = this.currentPoint.y;
A
alteredq 已提交
113

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

116
	},
117

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

120
		var curve = new THREE.EllipseCurve( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation );
Z
zz85 已提交
121

J
Joshua Koo 已提交
122
		if ( this.curves.length > 0 ) {
Z
zz85 已提交
123

J
Joshua Koo 已提交
124 125
			// if a previous curve is present, attempt to join
			var firstPoint = curve.getPoint( 0 );
Z
zz85 已提交
126

J
Joshua Koo 已提交
127
			if ( ! firstPoint.equals( this.currentPoint ) ) {
128

J
Joshua Koo 已提交
129
				this.lineTo( firstPoint.x, firstPoint.y );
130

J
Joshua Koo 已提交
131
			}
Z
zz85 已提交
132

133
		}
134

J
Joshua Koo 已提交
135
		this.curves.push( curve );
136

J
Joshua Koo 已提交
137 138
		var lastPoint = curve.getPoint( 1 );
		this.currentPoint.copy( lastPoint );
139

J
Joshua Koo 已提交
140
	}
A
alteredq 已提交
141

J
Joshua Koo 已提交
142
} );
A
alteredq 已提交
143 144


J
Joshua Koo 已提交
145 146 147 148 149
// minimal class for proxing functions to Path. Replaces old "extractSubpaths()"
THREE.ShapePath = function() {
	this.subPaths = [];
	this.currentPath = null;
}
Z
zz85 已提交
150

J
Joshua Koo 已提交
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
THREE.ShapePath.prototype = {
	moveTo: function ( x, y ) {
		this.currentPath = new THREE.Path();
		this.subPaths.push(this.currentPath);
		this.currentPath.moveTo( x, y );
	},
	lineTo: function ( x, y ) {
		this.currentPath.lineTo( x, y );
	},
	quadraticCurveTo: function ( aCPx, aCPy, aX, aY ) {
		this.currentPath.quadraticCurveTo( aCPx, aCPy, aX, aY );
	},
	bezierCurveTo: function ( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ) {
		this.currentPath.bezierCurveTo( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY );
	},
	splineThru: function ( pts ) {
		this.currentPath.splineThru( pts );
168
	},
Z
zz85 已提交
169

170
	toShapes: function ( isCCW, noHoles ) {
171

172
		function toShapesNoHoles( inSubpaths ) {
173

174
			var shapes = [];
G
gero3 已提交
175

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

178
				var tmpPath = inSubpaths[ i ];
179

180 181
				var tmpShape = new THREE.Shape();
				tmpShape.curves = tmpPath.curves;
182

183
				shapes.push( tmpShape );
184

185
			}
186

187
			return shapes;
G
gero3 已提交
188

189 190
		}

191
		function isPointInsidePolygon( inPt, inPolygon ) {
192

193
			var polyLen = inPolygon.length;
G
gero3 已提交
194

195 196 197 198 199 200
			// 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 ++ ) {
201

202 203
				var edgeLowPt  = inPolygon[ p ];
				var edgeHighPt = inPolygon[ q ];
G
gero3 已提交
204

205 206
				var edgeDx = edgeHighPt.x - edgeLowPt.x;
				var edgeDy = edgeHighPt.y - edgeLowPt.y;
207

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

210 211
					// not parallel
					if ( edgeDy < 0 ) {
212

213 214
						edgeLowPt  = inPolygon[ q ]; edgeDx = - edgeDx;
						edgeHighPt = inPolygon[ p ]; edgeDy = - edgeDy;
215

216 217
					}
					if ( ( inPt.y < edgeLowPt.y ) || ( inPt.y > edgeHighPt.y ) ) 		continue;
G
gero3 已提交
218

219
					if ( inPt.y === edgeLowPt.y ) {
G
gero3 已提交
220

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

224
					} else {
225

226 227 228 229
						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 已提交
230

231
					}
G
gero3 已提交
232

233
				} else {
G
gero3 已提交
234

235 236 237 238 239 240
					// 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 已提交
241

242
				}
G
gero3 已提交
243

244
			}
G
gero3 已提交
245

246 247
			return	inside;

248 249
		}

250
		var isClockWise = THREE.ShapeUtils.isClockWise;
G
gero3 已提交
251

J
Joshua Koo 已提交
252
		var subPaths = this.subPaths;
253
		if ( subPaths.length === 0 ) return [];
254

255
		if ( noHoles === true )	return	toShapesNoHoles( subPaths );
256 257


258
		var solid, tmpPath, tmpShape, shapes = [];
259

260
		if ( subPaths.length === 1 ) {
261

262 263 264 265 266
			tmpPath = subPaths[ 0 ];
			tmpShape = new THREE.Shape();
			tmpShape.curves = tmpPath.curves;
			shapes.push( tmpShape );
			return shapes;
Z
zz85 已提交
267

268
		}
269

270 271
		var holesFirst = ! isClockWise( subPaths[ 0 ].getPoints() );
		holesFirst = isCCW ? ! holesFirst : holesFirst;
272

273
		// console.log("Holes first", holesFirst);
274

275 276 277 278 279
		var betterShapeHoles = [];
		var newShapes = [];
		var newShapeHoles = [];
		var mainIdx = 0;
		var tmpPoints;
F
Fabian Lange 已提交
280

281 282
		newShapes[ mainIdx ] = undefined;
		newShapeHoles[ mainIdx ] = [];
283

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

286 287 288 289
			tmpPath = subPaths[ i ];
			tmpPoints = tmpPath.getPoints();
			solid = isClockWise( tmpPoints );
			solid = isCCW ? ! solid : solid;
290

291
			if ( solid ) {
292

293
				if ( ( ! holesFirst ) && ( newShapes[ mainIdx ] ) )	mainIdx ++;
294

295 296
				newShapes[ mainIdx ] = { s: new THREE.Shape(), p: tmpPoints };
				newShapes[ mainIdx ].s.curves = tmpPath.curves;
297

298 299
				if ( holesFirst )	mainIdx ++;
				newShapeHoles[ mainIdx ] = [];
F
Fabian Lange 已提交
300

301
				//console.log('cw', i);
302

303
			} else {
304

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

307
				//console.log('ccw', i);
308

309
			}
310

Z
zz85 已提交
311
		}
312

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

316

317
		if ( newShapes.length > 1 ) {
318

319 320
			var ambiguous = false;
			var toChange = [];
G
gero3 已提交
321

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

324
				betterShapeHoles[ sIdx ] = [];
G
gero3 已提交
325

326
			}
G
gero3 已提交
327

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

330
				var sho = newShapeHoles[ sIdx ];
G
gero3 已提交
331

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

334 335
					var ho = sho[ hIdx ];
					var hole_unassigned = true;
G
gero3 已提交
336

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

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

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

344 345
								hole_unassigned = false;
								betterShapeHoles[ s2Idx ].push( ho );
G
gero3 已提交
346

347
							} else {
G
gero3 已提交
348

349
								ambiguous = true;
G
gero3 已提交
350

351
							}
G
gero3 已提交
352

353
						}
G
gero3 已提交
354

355
					}
356
					if ( hole_unassigned ) {
G
gero3 已提交
357

358
						betterShapeHoles[ sIdx ].push( ho );
G
gero3 已提交
359

360
					}
G
gero3 已提交
361 362 363

				}

Z
zz85 已提交
364
			}
365 366
			// console.log("ambiguous: ", ambiguous);
			if ( toChange.length > 0 ) {
G
gero3 已提交
367

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

371
			}
G
gero3 已提交
372

373
		}
G
gero3 已提交
374

375
		var tmpHoles;
Z
zz85 已提交
376

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

379 380 381
			tmpShape = newShapes[ i ].s;
			shapes.push( tmpShape );
			tmpHoles = newShapeHoles[ i ];
G
gero3 已提交
382

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

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

387
			}
G
gero3 已提交
388

389
		}
G
gero3 已提交
390

391
		//console.log("shape", shapes);
392

393
		return shapes;
394

395
	}
J
Joshua Koo 已提交
396
}