Skip to content

Commit

Permalink
Adding segment.toPiecewiseLinearOrArcSegments()
Browse files Browse the repository at this point in the history
  • Loading branch information
jonathanolson committed Oct 19, 2017
1 parent 067109a commit 2c921cb
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 0 deletions.
49 changes: 49 additions & 0 deletions js/segments/Arc.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ define( function( require ) {
var Bounds2 = require( 'DOT/Bounds2' );
var inherit = require( 'PHET_CORE/inherit' );
var kite = require( 'KITE/kite' );
var Line = require( 'KITE/segments/Line' );
var Overlap = require( 'KITE/util/Overlap' );
var RayIntersection = require( 'KITE/util/RayIntersection' );
var Segment = require( 'KITE/segments/Segment' );
Expand Down Expand Up @@ -848,6 +849,16 @@ define( function( require ) {
return this.getAngleDifference() * this._radius;
},

/**
* We can handle this simply by returning ourselves.
* @override
*
* @returns {Array.<Segment>}
*/
toPiecewiseLinearOrArcSegments: function() {
return [ this ];
},

/**
* Returns an object form that can be turned back into a segment with the corresponding deserialize method.
* @public
Expand Down Expand Up @@ -1103,5 +1114,43 @@ define( function( require ) {
return results;
};

/**
* Creates an Arc (or if straight enough a Line) segment that goes from the startPoint to the endPoint, touching
* the middlePoint somewhere between the two.
* @public
*
* @param {Vector2} startPoint
* @param {Vector2} middlePoint
* @param {Vector2} endPoint
* @returns {Segment}
*/
Arc.createFromPoints = function( startPoint, middlePoint, endPoint ) {
var center = Util.circleCenterFromPoints( startPoint, middlePoint, endPoint );

// Close enough
if ( center === null ) {
return new Line( startPoint, endPoint );
}
else {
var startDiff = startPoint.minus( center );
var middleDiff = middlePoint.minus( center );
var endDiff = endPoint.minus( center );
var startAngle = startDiff.angle();
var middleAngle = middleDiff.angle();
var endAngle = endDiff.angle();

var radius = ( startDiff.magnitude() + middleDiff.magnitude() + endDiff.magnitude() ) / 3;

// Try anticlockwise first. TODO: Don't require creation of extra Arcs
var arc = new Arc( center, radius, startAngle, endAngle, false );
if ( arc.containsAngle( middleAngle ) ) {
return arc;
}
else {
return new Arc( center, radius, startAngle, endAngle, true );
}
}
};

return Arc;
} );
10 changes: 10 additions & 0 deletions js/segments/Line.js
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,16 @@ define( function( require ) {
return this.start.distance( this.end );
},

/**
* We can handle this simply by returning ourselves.
* @override
*
* @returns {Array.<Segment>}
*/
toPiecewiseLinearOrArcSegments: function() {
return [ this ];
},

/**
* Returns an object form that can be turned back into a segment with the corresponding deserialize method.
* @public
Expand Down
83 changes: 83 additions & 0 deletions js/segments/Segment.js
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,89 @@ define( function( require ) {
subdividedSegments[ 1 ].toPiecewiseLinearSegments( options, minLevels - 1, maxLevels - 1, segments, middle, end );
}
return segments;
},

/**
* Returns a list of Line and/or Arc segments that approximates this segment.
* @public
*
* @param {Object} [options]
* @returns {Array.<Segment>}
*/
toPiecewiseLinearOrArcSegments: function( options ) {
options = _.extend( {
minLevels: 2,
maxLevels: 7,
curvatureThreshold: 0.02,
errorThreshold: 10,
errorPoints: [ 0.25, 0.75 ],
}, options );

var segments = [];
this.toPiecewiseLinearOrArcRecursion( options, options.minLevels, options.maxLevels, segments,
0, 1,
this.positionAt( 0 ), this.positionAt( 1 ),
this.curvatureAt( 0 ), this.curvatureAt( 1 ) );
return segments;
},

/**
* Helper function for toPiecewiseLinearOrArcSegments.
* @private
*
* @param {Object} options
* @param {number} minLevels
* @param {number} maxLevels
* @param {Array.<Segment>} segments - We will push resulting segments to here
* @param {number} startT
* @param {number} endT
* @param {Vector2} startPoint
* @param {Vector2} endPoint
* @param {number} startCurvature
* @param {number} endCurvature
*/
toPiecewiseLinearOrArcRecursion: function( options, minLevels, maxLevels, segments, startT, endT, startPoint, endPoint, startCurvature, endCurvature ) {
var middleT = ( startT + endT ) / 2;
var middlePoint = this.positionAt( middleT );
var middleCurvature = this.curvatureAt( middleT );

if ( maxLevels <= 0 || ( minLevels <= 0 && Math.abs( startCurvature - middleCurvature ) + Math.abs( middleCurvature - endCurvature ) < options.curvatureThreshold * 2 ) ) {
var segment = kite.Arc.createFromPoints( startPoint, middlePoint, endPoint );
var needsSplit = false;
if ( segment instanceof kite.Arc ) {
var radiusSquared = segment.radius * segment.radius;
for ( var i = 0; i < options.errorPoints.length; i++ ) {
var t = options.errorPoints[ i ];
var point = this.positionAt( startT * ( 1 - t ) + endT * t );
if ( Math.abs( point.distanceSquared( segment.center ) - radiusSquared ) > options.errorThreshold ) {
needsSplit = true;
break;
}
}
}
if ( !needsSplit ) {
segments.push( segment );
return;
}
}
this.toPiecewiseLinearOrArcRecursion( options, minLevels - 1, maxLevels - 1, segments,
startT, middleT,
startPoint, middlePoint,
startCurvature, middleCurvature );
this.toPiecewiseLinearOrArcRecursion( options, minLevels - 1, maxLevels - 1, segments,
middleT, endT,
middlePoint, endPoint,
middleCurvature, endCurvature );
},

/**
* Returns a Shape containing just this one segment.
* @public
*
* @returns {Shape}
*/
toShape: function() {
return new kite.Shape( [ new kite.Subpath( [ this ] ) ] );
}
} );

Expand Down

0 comments on commit 2c921cb

Please sign in to comment.