提交 6df2c331 编写于 作者: J Joshua Koo 提交者: Mr.doob

Shapes refactor (#9079)

* Consistent getPoints and getSpacedPoints on ellipse

They work as getSpacedPoints used to (but with the final point)
The arc between aStartAngle and aEndAngle is always forced in the 0, 2
PI area, as getSpacedPoints did (but not getPoints)

* Refactor Path class

- got rid of .actions
- getPoints() moved to CurvePath
- toShapes() moved to new class ShapePath

* Path: simplify usage of this.currentPoint

* Optimize LineCurve.getPoint(1) as suggested by @rfm1201 in #8952

- this minimizes some floating point errors when t = 1

* Fix up missing changes to CurvePath and EllipseCurve

- as commented by @rfm1201
- using f040453 as reference

* Path: update currentPoint for .splineThru(), add automatic lineTo()

- for ellipse

* CurvePath: address some removeDuplicatesInAPath() concerns

- also cleanup some old reference to .actions

* Path/CurvePath getPoints() shouldn't return sequence of duplicate points

* In path.absellipse, lineTo is drawn only to connect existing path

- when necessary
上级 fbf78109
......@@ -44,7 +44,7 @@ const DIGIT_0 = 48, DIGIT_9 = 57, COMMA = 44, SPACE = 32, PERIOD = 46, MINUS = 4
exports.transformSVGPath =
function transformSVGPath(pathStr) {
var path = new THREE.Shape();
var path = new THREE.ShapePath();
var idx = 1, len = pathStr.length, activeCmd,
x = 0, y = 0, nx = 0, ny = 0, firstX = null, firstY = null,
......
......@@ -628,7 +628,7 @@ THREE.ShapeUtils = {
if ( allPointsMap[ key ] !== undefined ) {
console.warn( "THREE.Shape: Duplicate point", key );
console.warn( "THREE.ShapeUtils: Duplicate point", key, i );
}
......
......@@ -28,8 +28,6 @@ THREE.CurvePath.prototype = Object.assign( Object.create( THREE.Curve.prototype
closePath: function () {
// TODO Test
// and verify for vector3 (needs to implement equals)
// Add a line curve if start and end of lines are not connected
var startPoint = this.curves[ 0 ].getPoint( 0 );
var endPoint = this.curves[ this.curves.length - 1 ].getPoint( 1 );
......@@ -66,7 +64,8 @@ THREE.CurvePath.prototype = Object.assign( Object.create( THREE.Curve.prototype
var diff = curveLengths[ i ] - d;
var curve = this.curves[ i ];
var u = 1 - diff / curve.getLength();
var segmentLength = curve.getLength();
var u = segmentLength === 0 ? 0 : 1 - diff / segmentLength;
return curve.getPointAt( u );
......@@ -133,6 +132,66 @@ THREE.CurvePath.prototype = Object.assign( Object.create( THREE.Curve.prototype
},
getSpacedPoints: function ( divisions ) {
if ( ! divisions ) divisions = 40;
var points = [];
for ( var i = 0; i <= divisions; i ++ ) {
points.push( this.getPoint( i / divisions ) );
}
if ( this.autoClose ) {
points.push( points[ 0 ] );
}
return points;
},
getPoints: function ( divisions ) {
divisions = divisions || 12;
var points = [], tmp, last, curve;
for ( var i = 0, curves = this.curves; i < curves.length; i ++ ) {
curve = curves[i];
var pts = curve.getPoints( curve instanceof THREE.LineCurve ? 1 : divisions );
for ( var j = 0; j < pts.length; j++ ) {
var tmp = pts[ j ];
if ( last && last.equals( tmp ) ) continue; // ensures no consecutive points are duplicates
points.push( tmp );
last = tmp;
}
}
if ( points[ points.length - 1 ].equals( points[ 0 ] ) ) {
points.pop();
}
if ( this.autoClose ) {
points.push( points[ 0 ] );
}
return points;
},
/**************************************************************
* Create Geometries Helpers
**************************************************************/
......
......@@ -40,7 +40,7 @@ Object.assign( THREE.Font.prototype, {
if ( ! glyph ) return;
var path = new THREE.Path();
var path = new THREE.ShapePath();
var pts = [], b2 = THREE.ShapeUtils.b2, b3 = THREE.ShapeUtils.b3;
var x, y, cpx, cpy, cpx0, cpy0, cpx1, cpy1, cpx2, cpy2, laste;
......
......@@ -7,8 +7,7 @@
THREE.Path = function ( points ) {
THREE.CurvePath.call( this );
this.actions = [];
this.currentPoint = new THREE.Vector2();
if ( points ) {
......@@ -22,11 +21,8 @@ THREE.Path.prototype = Object.assign( Object.create( THREE.CurvePath.prototype )
constructor: THREE.Path,
// TODO Clean up PATH API
// Create path using straight lines to connect all points
// - vectors: array of Vector2
fromPoints: function ( vectors ) {
this.moveTo( vectors[ 0 ].x, vectors[ 0 ].y );
......@@ -41,52 +37,37 @@ THREE.Path.prototype = Object.assign( Object.create( THREE.CurvePath.prototype )
moveTo: function ( x, y ) {
this.actions.push( { action: 'moveTo', args: [ x, y ] } );
this.currentPoint.set( x, y ); // TODO consider referencing vectors instead of copying?
},
lineTo: function ( x, y ) {
var lastargs = this.actions[ this.actions.length - 1 ].args;
var x0 = lastargs[ lastargs.length - 2 ];
var y0 = lastargs[ lastargs.length - 1 ];
var curve = new THREE.LineCurve( new THREE.Vector2( x0, y0 ), new THREE.Vector2( x, y ) );
var curve = new THREE.LineCurve( this.currentPoint.clone(), new THREE.Vector2( x, y ) );
this.curves.push( curve );
this.actions.push( { action: 'lineTo', args: [ x, y ] } );
this.currentPoint.set( x, y );
},
quadraticCurveTo: function ( aCPx, aCPy, aX, aY ) {
var lastargs = this.actions[ this.actions.length - 1 ].args;
var x0 = lastargs[ lastargs.length - 2 ];
var y0 = lastargs[ lastargs.length - 1 ];
var curve = new THREE.QuadraticBezierCurve(
new THREE.Vector2( x0, y0 ),
this.currentPoint.clone(),
new THREE.Vector2( aCPx, aCPy ),
new THREE.Vector2( aX, aY )
);
this.curves.push( curve );
this.actions.push( { action: 'quadraticCurveTo', args: [ aCPx, aCPy, aX, aY ] } );
this.currentPoint.set( aX, aY );
},
bezierCurveTo: function ( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ) {
var lastargs = this.actions[ this.actions.length - 1 ].args;
var x0 = lastargs[ lastargs.length - 2 ];
var y0 = lastargs[ lastargs.length - 1 ];
var curve = new THREE.CubicBezierCurve(
new THREE.Vector2( x0, y0 ),
this.currentPoint.clone(),
new THREE.Vector2( aCP1x, aCP1y ),
new THREE.Vector2( aCP2x, aCP2y ),
new THREE.Vector2( aX, aY )
......@@ -94,38 +75,25 @@ THREE.Path.prototype = Object.assign( Object.create( THREE.CurvePath.prototype )
this.curves.push( curve );
this.actions.push( { action: 'bezierCurveTo', args: [ aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ] } );
this.currentPoint.set( aX, aY );
},
splineThru: function ( pts /*Array of Vector*/ ) {
var args = Array.prototype.slice.call( arguments );
var lastargs = this.actions[ this.actions.length - 1 ].args;
var x0 = lastargs[ lastargs.length - 2 ];
var y0 = lastargs[ lastargs.length - 1 ];
var npts = [ new THREE.Vector2( x0, y0 ) ];
Array.prototype.push.apply( npts, pts );
var npts = [ this.currentPoint.clone() ].concat( pts );
var curve = new THREE.SplineCurve( npts );
this.curves.push( curve );
var lastPoint = pts[ pts.length - 1 ];
args.push( lastPoint.x );
args.push( lastPoint.y );
this.actions.push( { action: 'splineThru', args: args } );
this.currentPoint.copy( pts[ pts.length - 1 ] );
},
arc: function ( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) {
var lastargs = this.actions[ this.actions.length - 1 ].args;
var x0 = lastargs[ lastargs.length - 2 ];
var y0 = lastargs[ lastargs.length - 1 ];
var x0 = this.currentPoint.x;
var y0 = this.currentPoint.y;
this.absarc( aX + x0, aY + y0, aRadius,
aStartAngle, aEndAngle, aClockwise );
......@@ -140,9 +108,8 @@ THREE.Path.prototype = Object.assign( Object.create( THREE.CurvePath.prototype )
ellipse: function ( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) {
var lastargs = this.actions[ this.actions.length - 1 ].args;
var x0 = lastargs[ lastargs.length - 2 ];
var y0 = lastargs[ lastargs.length - 1 ];
var x0 = this.currentPoint.x;
var y0 = this.currentPoint.y;
this.absellipse( aX + x0, aY + y0, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation );
......@@ -150,336 +117,58 @@ THREE.Path.prototype = Object.assign( Object.create( THREE.CurvePath.prototype )
absellipse: function ( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) {
var args = [
aX, aY,
xRadius, yRadius,
aStartAngle, aEndAngle,
aClockwise,
aRotation || 0 // aRotation is optional.
];
var curve = new THREE.EllipseCurve( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation );
this.curves.push( curve );
var lastPoint = curve.getPoint( 1 );
args.push( lastPoint.x );
args.push( lastPoint.y );
this.actions.push( { action: 'ellipse', args: args } );
},
getSpacedPoints: function ( divisions ) {
if ( ! divisions ) divisions = 40;
var points = [];
for ( var i = 0; i < divisions; i ++ ) {
points.push( this.getPoint( i / divisions ) );
//if ( !this.getPoint( i / divisions ) ) throw "DIE";
}
if ( this.autoClose ) {
points.push( points[ 0 ] );
}
return points;
},
getPoints: function ( divisions ) {
divisions = divisions || 12;
var b2 = THREE.ShapeUtils.b2;
var b3 = THREE.ShapeUtils.b3;
var points = [];
var cpx, cpy, cpx2, cpy2, cpx1, cpy1, cpx0, cpy0,
laste, tx, ty;
for ( var i = 0, l = this.actions.length; i < l; i ++ ) {
var item = this.actions[ i ];
var action = item.action;
var args = item.args;
switch ( action ) {
case 'moveTo':
points.push( new THREE.Vector2( args[ 0 ], args[ 1 ] ) );
break;
case 'lineTo':
points.push( new THREE.Vector2( args[ 0 ], args[ 1 ] ) );
break;
case 'quadraticCurveTo':
cpx = args[ 2 ];
cpy = args[ 3 ];
cpx1 = args[ 0 ];
cpy1 = args[ 1 ];
if ( points.length > 0 ) {
laste = points[ points.length - 1 ];
cpx0 = laste.x;
cpy0 = laste.y;
} else {
laste = this.actions[ i - 1 ].args;
cpx0 = laste[ laste.length - 2 ];
cpy0 = laste[ laste.length - 1 ];
}
for ( var j = 1; j <= divisions; j ++ ) {
var t = j / divisions;
tx = b2( t, cpx0, cpx1, cpx );
ty = b2( t, cpy0, cpy1, cpy );
points.push( new THREE.Vector2( tx, ty ) );
}
break;
case 'bezierCurveTo':
cpx = args[ 4 ];
cpy = args[ 5 ];
cpx1 = args[ 0 ];
cpy1 = args[ 1 ];
cpx2 = args[ 2 ];
cpy2 = args[ 3 ];
if ( points.length > 0 ) {
laste = points[ points.length - 1 ];
cpx0 = laste.x;
cpy0 = laste.y;
} else {
laste = this.actions[ i - 1 ].args;
cpx0 = laste[ laste.length - 2 ];
cpy0 = laste[ laste.length - 1 ];
}
for ( var j = 1; j <= divisions; j ++ ) {
var t = j / divisions;
tx = b3( t, cpx0, cpx1, cpx2, cpx );
ty = b3( t, cpy0, cpy1, cpy2, cpy );
points.push( new THREE.Vector2( tx, ty ) );
}
break;
case 'splineThru':
laste = this.actions[ i - 1 ].args;
if ( this.curves.length > 0 ) {
var last = new THREE.Vector2( laste[ laste.length - 2 ], laste[ laste.length - 1 ] );
var spts = [ last ];
// if a previous curve is present, attempt to join
var firstPoint = curve.getPoint( 0 );
var n = divisions * args[ 0 ].length;
if ( ! firstPoint.equals( this.currentPoint ) ) {
spts = spts.concat( args[ 0 ] );
this.lineTo( firstPoint.x, firstPoint.y );
var spline = new THREE.SplineCurve( spts );
for ( var j = 1; j <= n; j ++ ) {
points.push( spline.getPointAt( j / n ) );
}
break;
case 'arc':
var aX = args[ 0 ], aY = args[ 1 ],
aRadius = args[ 2 ],
aStartAngle = args[ 3 ], aEndAngle = args[ 4 ],
aClockwise = !! args[ 5 ];
var deltaAngle = aEndAngle - aStartAngle;
var angle;
var tdivisions = divisions * 2;
for ( var j = 1; j <= tdivisions; j ++ ) {
var t = j / tdivisions;
if ( ! aClockwise ) {
t = 1 - t;
}
angle = aStartAngle + t * deltaAngle;
tx = aX + aRadius * Math.cos( angle );
ty = aY + aRadius * Math.sin( angle );
//console.log('t', t, 'angle', angle, 'tx', tx, 'ty', ty);
points.push( new THREE.Vector2( tx, ty ) );
}
//console.log(points);
break;
case 'ellipse':
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 ];
var deltaAngle = aEndAngle - aStartAngle;
var angle;
var tdivisions = divisions * 2;
var cos, sin;
if ( aRotation !== 0 ) {
cos = Math.cos( aRotation );
sin = Math.sin( aRotation );
}
for ( var j = 1; j <= tdivisions; j ++ ) {
var t = j / tdivisions;
if ( ! aClockwise ) {
t = 1 - t;
}
angle = aStartAngle + t * deltaAngle;
tx = aX + xRadius * Math.cos( angle );
ty = aY + yRadius * Math.sin( angle );
if ( aRotation !== 0 ) {
var x = tx, y = ty;
// 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;
}
//console.log('t', t, 'angle', angle, 'tx', tx, 'ty', ty);
points.push( new THREE.Vector2( tx, ty ) );
}
//console.log(points);
break;
} // end switch
}
}
this.curves.push( curve );
var lastPoint = curve.getPoint( 1 );
this.currentPoint.copy( lastPoint );
// 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 );
if ( this.autoClose ) {
}
points.push( points[ 0 ] );
} );
}
return points;
// minimal class for proxing functions to Path. Replaces old "extractSubpaths()"
THREE.ShapePath = function() {
this.subPaths = [];
this.currentPath = null;
}
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 );
},
toShapes: function ( isCCW, noHoles ) {
function extractSubpaths( inActions ) {
var subPaths = [], lastPath = new THREE.Path();
for ( var i = 0, l = inActions.length; i < l; i ++ ) {
var item = inActions[ i ];
var args = item.args;
var action = item.action;
if ( action === 'moveTo' ) {
if ( lastPath.actions.length !== 0 ) {
subPaths.push( lastPath );
lastPath = new THREE.Path();
}
}
lastPath[ action ].apply( lastPath, args );
}
if ( lastPath.actions.length !== 0 ) {
subPaths.push( lastPath );
}
// console.log(subPaths);
return subPaths;
}
function toShapesNoHoles( inSubpaths ) {
var shapes = [];
......@@ -489,15 +178,12 @@ THREE.Path.prototype = Object.assign( Object.create( THREE.CurvePath.prototype )
var tmpPath = inSubpaths[ i ];
var tmpShape = new THREE.Shape();
tmpShape.actions = tmpPath.actions;
tmpShape.curves = tmpPath.curves;
shapes.push( tmpShape );
}
//console.log("shape", shapes);
return shapes;
}
......@@ -563,7 +249,7 @@ THREE.Path.prototype = Object.assign( Object.create( THREE.CurvePath.prototype )
var isClockWise = THREE.ShapeUtils.isClockWise;
var subPaths = extractSubpaths( this.actions );
var subPaths = this.subPaths;
if ( subPaths.length === 0 ) return [];
if ( noHoles === true ) return toShapesNoHoles( subPaths );
......@@ -575,7 +261,6 @@ THREE.Path.prototype = Object.assign( Object.create( THREE.CurvePath.prototype )
tmpPath = subPaths[ 0 ];
tmpShape = new THREE.Shape();
tmpShape.actions = tmpPath.actions;
tmpShape.curves = tmpPath.curves;
shapes.push( tmpShape );
return shapes;
......@@ -608,7 +293,6 @@ THREE.Path.prototype = Object.assign( Object.create( THREE.CurvePath.prototype )
if ( ( ! holesFirst ) && ( newShapes[ mainIdx ] ) ) mainIdx ++;
newShapes[ mainIdx ] = { s: new THREE.Shape(), p: tmpPoints };
newShapes[ mainIdx ].s.actions = tmpPath.actions;
newShapes[ mainIdx ].s.curves = tmpPath.curves;
if ( holesFirst ) mainIdx ++;
......@@ -709,5 +393,4 @@ THREE.Path.prototype = Object.assign( Object.create( THREE.CurvePath.prototype )
return shapes;
}
} );
}
......@@ -2,7 +2,7 @@
* Ellipse curve
**************************************************************/
THREE.EllipseCurve = function ( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) {
THREE.EllipseCurve = function( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) {
this.aX = aX;
this.aY = aY;
......@@ -14,7 +14,7 @@ THREE.EllipseCurve = function ( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle
this.aEndAngle = aEndAngle;
this.aClockwise = aClockwise;
this.aRotation = aRotation || 0;
};
......@@ -22,25 +22,37 @@ THREE.EllipseCurve = function ( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle
THREE.EllipseCurve.prototype = Object.create( THREE.Curve.prototype );
THREE.EllipseCurve.prototype.constructor = THREE.EllipseCurve;
THREE.EllipseCurve.prototype.getPoint = function ( t ) {
THREE.EllipseCurve.prototype.getPoint = function( t ) {
var twoPi = Math.PI * 2;
var deltaAngle = this.aEndAngle - this.aStartAngle;
var samePoints = Math.abs( deltaAngle ) < Number.EPSILON;
// ensures that deltaAngle is 0 .. 2 PI
while ( deltaAngle < 0 ) deltaAngle += twoPi;
while ( deltaAngle > twoPi ) deltaAngle -= twoPi;
if ( deltaAngle < 0 ) deltaAngle += Math.PI * 2;
if ( deltaAngle > Math.PI * 2 ) deltaAngle -= Math.PI * 2;
if ( deltaAngle < Number.EPSILON ) {
var angle;
if ( samePoints ) {
if ( this.aClockwise === true ) {
deltaAngle = 0;
angle = this.aEndAngle + ( 1 - t ) * ( Math.PI * 2 - deltaAngle );
} else {
} else {
deltaAngle = twoPi;
angle = this.aStartAngle + t * deltaAngle;
}
}
if ( this.aClockwise === true && deltaAngle != twoPi && ! samePoints ) {
deltaAngle = deltaAngle - twoPi;
}
var angle = this.aStartAngle + t * deltaAngle;
var x = this.aX + this.xRadius * Math.cos( angle );
var y = this.aY + this.yRadius * Math.sin( angle );
......@@ -49,11 +61,12 @@ THREE.EllipseCurve.prototype.getPoint = function ( t ) {
var cos = Math.cos( this.aRotation );
var sin = Math.sin( this.aRotation );
var tx = x, ty = y;
var tx = x - this.aX;
var ty = y - this.aY;
// Rotate the point about the center of the ellipse.
x = ( tx - this.aX ) * cos - ( ty - this.aY ) * sin + this.aX;
y = ( tx - this.aX ) * sin + ( ty - this.aY ) * cos + this.aY;
x = tx * cos - ty * sin + this.aX;
y = tx * sin + ty * cos + this.aY;
}
......
......@@ -14,6 +14,12 @@ THREE.LineCurve.prototype.constructor = THREE.LineCurve;
THREE.LineCurve.prototype.getPoint = function ( t ) {
if ( t === 1 ) {
return this.v2.clone();
}
var point = this.v2.clone().sub( this.v1 );
point.multiplyScalar( t ).add( this.v1 );
......
......@@ -13,6 +13,12 @@ THREE.LineCurve3 = THREE.Curve.create(
function ( t ) {
if ( t === 1 ) {
return this.v2.clone();
}
var vector = new THREE.Vector3();
vector.subVectors( this.v2, this.v1 ); // diff
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册