diff --git a/js/segments/Arc.js b/js/segments/Arc.js index 5000e82..56df743 100644 --- a/js/segments/Arc.js +++ b/js/segments/Arc.js @@ -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' ); @@ -848,6 +849,16 @@ define( function( require ) { return this.getAngleDifference() * this._radius; }, + /** + * We can handle this simply by returning ourselves. + * @override + * + * @returns {Array.} + */ + toPiecewiseLinearOrArcSegments: function() { + return [ this ]; + }, + /** * Returns an object form that can be turned back into a segment with the corresponding deserialize method. * @public @@ -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; } ); diff --git a/js/segments/Line.js b/js/segments/Line.js index 35cb1e5..9d83012 100644 --- a/js/segments/Line.js +++ b/js/segments/Line.js @@ -505,6 +505,16 @@ define( function( require ) { return this.start.distance( this.end ); }, + /** + * We can handle this simply by returning ourselves. + * @override + * + * @returns {Array.} + */ + toPiecewiseLinearOrArcSegments: function() { + return [ this ]; + }, + /** * Returns an object form that can be turned back into a segment with the corresponding deserialize method. * @public diff --git a/js/segments/Segment.js b/js/segments/Segment.js index 29bd1c0..b0b161c 100644 --- a/js/segments/Segment.js +++ b/js/segments/Segment.js @@ -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.} + */ + 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.} 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 ] ) ] ); } } );