Shape¶
Overview¶
Shape handling
Shapes are internally made up of Subpaths, which contain a series of segments, and are optionally closed. Familiarity with how Canvas handles subpaths is helpful for understanding this code.
Canvas spec: http://www.w3.org/TR/2dcontext/ SVG spec: http://www.w3.org/TR/SVG/expanded-toc.html http://www.w3.org/TR/SVG/paths.html#PathData (for paths) Notes for elliptical arcs: http://www.w3.org/TR/SVG/implnote.html#PathElementImplementationNotes Notes for painting strokes: https://svgwg.org/svg2-draft/painting.html
TODO: add nonzero / evenodd support when browsers support it https://github.com/phetsims/kite/issues/76 TODO: docs
@author Jonathan Olson <jonathan.olson@colorado.edu>
Class Shape¶
Constructor¶
new Shape( subpaths? : Subpath[] | string, bounds? : Bounds2 )¶
Instance Methods¶
moveTo( x : number, y : number ) : this¶
Moves to a point given by the coordinates x and y
moveToRelative( x : number, y : number ) : this¶
Moves a relative displacement (x,y) from last point
moveToPointRelative( displacement : Vector2 ) : this¶
Moves a relative displacement (point) from last point
moveToPoint( point : Vector2 ) : this¶
Adds to this shape a subpath that moves (no joint) it to a point
lineTo( x : number, y : number ) : this¶
Adds to this shape a straight line from last point to the coordinate (x,y)
lineToRelative( x : number, y : number ) : this¶
Adds to this shape a straight line displaced by a relative amount x, and y from last point
@param x - horizontal displacement @param y - vertical displacement
lineToPointRelative( displacement : Vector2 ) : this¶
Adds to this shape a straight line displaced by a relative displacement (point)
lineToPoint( point : Vector2 ) : this¶
Adds to this shape a straight line from this lastPoint to point
horizontalLineTo( x : number ) : this¶
Adds a horizontal line (x represents the x-coordinate of the end point)
horizontalLineToRelative( x : number ) : this¶
Adds a horizontal line (x represent a horizontal displacement)
verticalLineTo( y : number ) : this¶
Adds a vertical line (y represents the y-coordinate of the end point)
verticalLineToRelative( y : number ) : this¶
Adds a vertical line (y represents a vertical displacement)
zigZagTo( endX : number, endY : number, amplitude : number, numberZigZags : number, symmetrical : boolean ) : this¶
Zig-zags between the current point and the specified point
@param endX - the end of the shape @param endY - the end of the shape @param amplitude - the vertical amplitude of the zig zag wave @param numberZigZags - the number of oscillations @param symmetrical - flag for drawing a symmetrical zig zag
zigZagToPoint( endPoint : Vector2, amplitude : number, numberZigZags : number, symmetrical : boolean ) : this¶
Zig-zags between the current point and the specified point. Implementation moved from circuit-construction-kit-common on April 22, 2019.
@param endPoint - the end of the shape @param amplitude - the vertical amplitude of the zig zag wave, signed to choose initial direction @param numberZigZags - the number of complete oscillations @param symmetrical - flag for drawing a symmetrical zig zag
quadraticCurveTo( cpx : number, cpy : number, x : number, y : number ) : this¶
Adds a quadratic curve to this shape
The curve is guaranteed to pass through the coordinate (x,y) but does not pass through the control point
@param cpx - control point horizontal coordinate @param cpy - control point vertical coordinate @param x @param y
quadraticCurveToRelative( cpx : number, cpy : number, x : number, y : number ) : this¶
Adds a quadratic curve to this shape. The control and final points are specified as displacment from the last point in this shape
@param cpx - control point horizontal coordinate @param cpy - control point vertical coordinate @param x - final x position of the quadratic curve @param y - final y position of the quadratic curve
quadraticCurveToPointRelative( controlPoint : Vector2, point : Vector2 ) : this¶
Adds a quadratic curve to this shape. The control and final points are specified as displacement from the last point in this shape
@param controlPoint @param point - the quadratic curve passes through this point
smoothQuadraticCurveTo( x : number, y : number ) : this¶
Adds a quadratic curve to this shape. The quadratic curves passes through the x and y coordinate. The shape should join smoothly with the previous subpaths
TODO: consider a rename to put 'smooth' farther back? https://github.com/phetsims/kite/issues/76
@param x - final x position of the quadratic curve @param y - final y position of the quadratic curve
smoothQuadraticCurveToRelative( x : number, y : number ) : this¶
Adds a quadratic curve to this shape. The quadratic curves passes through the x and y coordinate. The shape should join smoothly with the previous subpaths
@param x - final x position of the quadratic curve @param y - final y position of the quadratic curve
quadraticCurveToPoint( controlPoint : Vector2, point : Vector2 ) : this¶
Adds a quadratic bezier curve to this shape.
@param controlPoint @param point - the quadratic curve passes through this point
cubicCurveTo( cp1x : number, cp1y : number, cp2x : number, cp2y : number, x : number, y : number ) : this¶
Adds a cubic bezier curve to this shape.
@param cp1x - control point 1, horizontal coordinate @param cp1y - control point 1, vertical coordinate @param cp2x - control point 2, horizontal coordinate @param cp2y - control point 2, vertical coordinate @param x - final x position of the cubic curve @param y - final y position of the cubic curve
cubicCurveToRelative( cp1x : number, cp1y : number, cp2x : number, cp2y : number, x : number, y : number ) : this¶
@param cp1x - control point 1, horizontal displacement @param cp1y - control point 1, vertical displacement @param cp2x - control point 2, horizontal displacement @param cp2y - control point 2, vertical displacement @param x - final horizontal displacement @param y - final vertical displacment
cubicCurveToPointRelative( control1 : Vector2, control2 : Vector2, point : Vector2 ) : this¶
@param control1 - control displacement 1 @param control2 - control displacement 2 @param point - final displacement
smoothCubicCurveTo( cp2x : number, cp2y : number, x : number, y : number ) : this¶
@param cp2x - control point 2, horizontal coordinate @param cp2y - control point 2, vertical coordinate @param x @param y
smoothCubicCurveToRelative( cp2x : number, cp2y : number, x : number, y : number ) : this¶
@param cp2x - control point 2, horizontal coordinate @param cp2y - control point 2, vertical coordinate @param x @param y
cubicCurveToPoint( control1 : Vector2, control2 : Vector2, point : Vector2 ) : this¶
arc( centerX : number, centerY : number, radius : number, startAngle : number, endAngle : number, anticlockwise? : boolean ) : this¶
@param centerX - horizontal coordinate of the center of the arc @param centerY - Center of the arc @param radius - How far from the center the arc will be @param startAngle - Angle (radians) of the start of the arc @param endAngle - Angle (radians) of the end of the arc @param [anticlockwise] - Decides which direction the arc takes around the center
arcPoint( center : Vector2, radius : number, startAngle : number, endAngle : number, anticlockwise? : boolean ) : this¶
@param center - Center of the arc (every point on the arc is equally far from the center) @param radius - How far from the center the arc will be @param startAngle - Angle (radians) of the start of the arc @param endAngle - Angle (radians) of the end of the arc @param [anticlockwise] - Decides which direction the arc takes around the center
ellipticalArc( centerX : number, centerY : number, radiusX : number, radiusY : number, rotation : number, startAngle : number, endAngle : number, anticlockwise? : boolean ) : this¶
Creates an elliptical arc
@param centerX - horizontal coordinate of the center of the arc @param centerY - vertical coordinate of the center of the arc @param radiusX - semi axis @param radiusY - semi axis @param rotation - rotation of the elliptical arc with respect to the positive x axis. @param startAngle @param endAngle @param [anticlockwise]
ellipticalArcPoint( center : Vector2, radiusX : number, radiusY : number, rotation : number, startAngle : number, endAngle : number, anticlockwise? : boolean ) : this¶
Creates an elliptic arc
@param center @param radiusX @param radiusY @param rotation - rotation of the arc with respect to the positive x axis. @param startAngle - @param endAngle @param [anticlockwise]
close() : this¶
Adds a subpath that joins the last point of this shape to the first point to form a closed shape
newSubpath() : this¶
Moves to the next subpath, but without adding any points to it (like a moveTo would do).
This is particularly helpful for cases where you don't want to have to compute the explicit starting point of the next subpath. For instance, if you want three disconnected circles: - shape.circle( 50, 50, 20 ).newSubpath().circle( 100, 100, 20 ).newSubpath().circle( 150, 50, 20 )
See https://github.com/phetsims/kite/issues/72 for more info.
makeImmutable() : this¶
Makes this Shape immutable, so that attempts to further change the Shape will fail. This allows clients to avoid adding change listeners to this Shape.
isImmutable() : boolean¶
Returns whether this Shape is immutable (see makeImmutable for details).
ellipticalArcToRelative( radiusX : number, radiusY : number, rotation : number, largeArc : boolean, sweep : boolean, x : number, y : number ) : this¶
Matches SVG's elliptical arc from http://www.w3.org/TR/SVG/paths.html
WARNING: rotation (for now) is in DEGREES. This will probably change in the future.
@param radiusX - Semi-major axis size @param radiusY - Semi-minor axis size @param rotation - Rotation of the ellipse (its semi-major axis) @param largeArc - Whether the arc will go the longest route around the ellipse. @param sweep - Whether the arc made goes from start to end "clockwise" (opposite of anticlockwise flag) @param x - End point X position @param y - End point Y position
ellipticalArcTo( radiusX : number, radiusY : number, rotation : number, largeArc : boolean, sweep : boolean, x : number, y : number ) : this¶
Matches SVG's elliptical arc from http://www.w3.org/TR/SVG/paths.html
WARNING: rotation (for now) is in DEGREES. This will probably change in the future.
@param radiusX - Semi-major axis size @param radiusY - Semi-minor axis size @param rotation - Rotation of the ellipse (its semi-major axis) @param largeArc - Whether the arc will go the longest route around the ellipse. @param sweep - Whether the arc made goes from start to end "clockwise" (opposite of anticlockwise flag) @param x - End point X position @param y - End point Y position
circle( center : Vector2, radius : number ) : this¶
Draws a circle using the arc() call
circle( centerX : number, centerY : number, radius : number ) : this¶
circle( centerX : Vector2 | number, centerY : number, radius? : number ) : this¶
ellipse( center : Vector2, radiusX : number, radiusY : number, rotation : number ) : this¶
Draws an ellipse using the ellipticalArc() call
The rotation is about the centerX, centerY.
ellipse( centerX : number, centerY : number, radiusX : number, radiusY : number, rotation : number ) : this¶
ellipse( centerX : Vector2 | number, centerY : number, radiusX : number, radiusY : number, rotation? : number ) : this¶
rect( x : number, y : number, width : number, height : number ) : this¶
Creates a rectangle shape
@param x - left position @param y - bottom position (in non inverted cartesian system) @param width @param height
roundRect( x : number, y : number, width : number, height : number, arcw : number, arch : number ) : this¶
Creates a round rectangle. All arguments are number.
@param x @param y @param width - width of the rectangle @param height - height of the rectangle @param arcw - arc width @param arch - arc height
polygon( vertices : Vector2[] ) : this¶
Creates a polygon from an array of vertices.
cardinalSpline( positions : Vector2[], providedOptions? : CardinalSplineOptions ) : this¶
This is a convenience function that allows to generate Cardinal splines from a position array. Cardinal spline differs from Bezier curves in that all defined points on a Cardinal spline are on the path itself.
It includes a tension parameter to allow the client to specify how tightly the path interpolates between points. One can think of the tension as the tension in a rubber band around pegs. however unlike a rubber band the tension can be negative. the tension ranges from -1 to 1
copy() : Shape¶
Returns a copy of this shape
writeToContext( context : CanvasRenderingContext2D )¶
Writes out this shape's path to a canvas 2d context. does NOT include the beginPath()!
getSVGPath() : string¶
Returns something like "M150 0 L75 200 L225 200 Z" for a triangle (to be used with a SVG path element's 'd' attribute)
transformed( matrix : Matrix3 ) : Shape¶
Returns a new Shape that is transformed by the associated matrix
nonlinearTransformed( providedOptions? : NonlinearTransformedOptions ) : Shape¶
Converts this subpath to a new shape made of many line segments (approximating the current shape) with the transformation applied.
polarToCartesian( options? : NonlinearTransformedOptions ) : Shape¶
Maps points by treating their x coordinate as polar angle, and y coordinate as polar magnitude. See http://en.wikipedia.org/wiki/Polar_coordinate_system
Please see Shape.nonlinearTransformed for more documentation on adaptive discretization options (minLevels, maxLevels, distanceEpsilon, curveEpsilon)
Example: A line from (0,10) to (pi,10) will be transformed to a circular arc from (10,0) to (-10,0) passing through (0,10).
toPiecewiseLinear( options? : NonlinearTransformedOptions ) : Shape¶
Converts each segment into lines, using an adaptive (midpoint distance subdivision) method.
NOTE: uses nonlinearTransformed method internally, but since we don't provide a pointMap or methodName, it won't create anything but line segments. See nonlinearTransformed for documentation of options
containsPoint( point : Vector2 ) : boolean¶
Is this point contained in this shape
intersection( ray : Ray2 ) : RayIntersection[]¶
Hit-tests this shape with the ray. An array of all intersections of the ray with this shape will be returned. For this function, intersections will be returned sorted by the distance from the ray's position.
interiorIntersectsLineSegment( startPoint : Vector2, endPoint : Vector2 ) : boolean¶
Returns whether the provided line segment would have some part on top or touching the interior (filled area) of this shape.
This differs somewhat from an intersection of the line segment with the Shape's path, as we will return true ("intersection") if the line segment is entirely contained in the interior of the Shape's path.
@param startPoint - One end of the line segment @param endPoint - The other end of the line segment
windingIntersection( ray : Ray2 ) : number¶
Returns the winding number for intersection with a ray
intersectsBounds( bounds : Bounds2 ) : boolean¶
Whether the path of the Shape intersects (or is contained in) the provided bounding box. Computed by checking intersections with all four edges of the bounding box, or whether the Shape is totally contained within the bounding box.
getStrokedShape( lineStyles : LineStyles ) : Shape¶
Returns a new Shape that is an outline of the stroked path of this current Shape. currently not intended to be nested (doesn't do intersection computations yet)
TODO: rename stroked( lineStyles )? https://github.com/phetsims/kite/issues/76
getOffsetShape( distance : number ) : Shape¶
Gets a shape offset by a certain amount.
getDashedShape( lineDash : number[], lineDashOffset : number, providedOptions? : GetDashedShapeOptions ) : Shape¶
Returns a copy of this subpath with the dash "holes" removed (has many subpaths usually).
getBounds() : Bounds2¶
Returns the bounds of this shape. It is the bounding-box union of the bounds of each subpath contained.
getStrokedBounds( lineStyles : LineStyles ) : Bounds2¶
Returns the bounds for a stroked version of this shape. The input lineStyles are used to determine the size and style of the stroke, and then the bounds of the stroked shape are returned.
getSimplifiedAreaShape() : Shape¶
Returns a simplified form of this shape.
Runs it through the normal CAG process, which should combine areas where possible, handles self-intersection, etc.
NOTE: Currently (2017-10-04) adjacent segments may get simplified only if they are lines. Not yet complete.
getBoundsWithTransform( matrix : Matrix3, lineStyles? : LineStyles ) : Bounds2¶
getApproximateArea( numSamples : number ) : number¶
Return an approximate value of the area inside of this Shape (where containsPoint is true) using Monte-Carlo.
NOTE: Generally, use getArea(). This can be used for verification, but takes a large number of samples.
@param numSamples - How many times to randomly check for inclusion of points.
getNonoverlappingArea() : number¶
Return the area inside the Shape (where containsPoint is true), assuming there is no self-intersection or overlap, and the same orientation (winding order) is used. Should also support holes (with opposite orientation), assuming they don't intersect the containing subpath.
getArea() : number¶
Returns the area inside the shape.
NOTE: This requires running it through a lot of computation to determine a non-overlapping non-self-intersecting form first. If the Shape is "simple" enough, getNonoverlappingArea would be preferred.
getApproximateCentroid( numSamples : number ) : Vector2¶
Return the approximate location of the centroid of the Shape (the average of all points where containsPoint is true) using Monte-Carlo methods.
@param numSamples - How many times to randomly check for inclusion of points.
getClosestPoints( point : Vector2 ) : ClosestToPointResult[]¶
Returns an array of potential closest point results on the Shape to the given point.
getClosestPoint( point : Vector2 ) : Vector2¶
Returns a single point ON the Shape boundary that is closest to the given point (picks an arbitrary one if there are multiple).
invalidatePoints()¶
Should be called after mutating the x/y of Vector2 points that were passed in to various Shape calls, so that derived information computed (bounds, etc.) will be correct, and any clients (e.g. Scenery Paths) will be notified of the updates.
toString() : string¶
getLastPoint() : Vector2¶
Gets the last point in the last subpath, or null if it doesn't exist
shapeUnion( shape : Shape ) : Shape¶
Returns a new shape that contains a union of the two shapes (a point in either shape is in the resulting shape).
shapeIntersection( shape : Shape ) : Shape¶
Returns a new shape that contains the intersection of the two shapes (a point in both shapes is in the resulting shape).
shapeDifference( shape : Shape ) : Shape¶
Returns a new shape that contains the difference of the two shapes (a point in the first shape and NOT in the second shape is in the resulting shape).
shapeXor( shape : Shape ) : Shape¶
Returns a new shape that contains the xor of the two shapes (a point in only one shape is in the resulting shape).
shapeClip( shape : Shape, options? : { includeExterior?: boolean; includeBoundary: boolean; includeInterior: boolean } ) : Shape¶
Returns a new shape that only contains portions of segments that are within the passed-in shape's area.
// TODO: convert Graph to TS and get the types from there https://github.com/phetsims/kite/issues/76
getArcLength( distanceEpsilon? : number, curveEpsilon? : number, maxLevels? : number ) : number¶
Returns the (sometimes approximate) arc length of all the shape's subpaths combined.
serialize() : SerializedShape¶
Returns an object form that can be turned back into a segment with the corresponding deserialize method.
Instance Properties¶
subpaths : Subpath[]¶
(readonly)
Lower-level piecewise mathematical description using segments, also individually immutable
invalidatedEmitter : TinyEmitter¶
(readonly)
Static Methods¶
deserialize( obj : SerializedShape ) : Shape¶
Returns a Shape from the serialized representation.
rectangle( x : number, y : number, width : number, height : number ) : Shape¶
Creates a rectangle
roundRect( x : number, y : number, width : number, height : number, arcw : number, arch : number ) : Shape¶
Creates a round rectangle {Shape}, with {number} arguments. Uses circular or elliptical arcs if given.
roundedRectangleWithRadii( x : number, y : number, width : number, height : number, cornerRadii? : Partial<CornerRadiiOptions> ) : Shape¶
Creates a rounded rectangle, where each corner can have a different radius. The radii default to 0, and may be set using topLeft, topRight, bottomLeft and bottomRight in the options. If the specified radii are larger than the dimension on that side, they radii are reduced proportionally, see https://github.com/phetsims/under-pressure/issues/151
E.g.:
var cornerRadius = 20; var rect = Shape.roundedRectangleWithRadii( 0, 0, 200, 100, { topLeft: cornerRadius, topRight: cornerRadius } );
@param x - Left edge position @param y - Top edge position @param width - Width of rectangle @param height - Height of rectangle @param [cornerRadii] - Optional object with potential radii for each corner.
boundsOffsetWithRadii( bounds : Bounds2, offsets : OffsetsOptions, radii? : CornerRadiiOptions ) : Shape¶
Returns a Shape from a bounds, offset (expanded) by certain amounts, and with certain corner radii.
polygon( vertices : Vector2[] ) : Shape¶
Creates a closed polygon from an array of vertices by connecting them by a series of lines. The lines are joining the adjacent vertices in the array.
bounds( bounds : Bounds2 ) : Shape¶
Creates a rectangular shape from bounds
lineSegment( x1 : number, y1 : number, x2 : number, y2 : number ) : Shape¶
Creates a line segment, using either (x1,y1,x2,y2) or ({x1,y1},{x2,y2}) arguments
lineSegment( p1 : Vector2, p2 : Vector2 ) : Shape¶
lineSegment( a : Vector2 | number, b : Vector2 | number, c? : number, d? : number ) : Shape¶
regularPolygon( sides : number, radius : number ) : Shape¶
Returns a regular polygon of radius and number of sides The regular polygon is oriented such that the first vertex lies on the positive x-axis.
@param sides - an integer @param radius
circle( centerX : number, centerY : number, radius : number ) : Shape¶
Creates a circle supports both circle( centerX, centerY, radius ), circle( center, radius ), and circle( radius ) with the center default to 0,0
circle( center : Vector2, radius : number ) : Shape¶
circle( radius : number ) : Shape¶
circle( a : Vector2 | number, b? : number, c? : number ) : Shape¶
ellipse( centerX : number, centerY : number, radiusX : number, radiusY : number, rotation : number ) : Shape¶
Supports ellipse( centerX, centerY, radiusX, radiusY, rotation ), ellipse( center, radiusX, radiusY, rotation ), and ellipse( radiusX, radiusY, rotation ) with the center default to 0,0 and rotation of 0. The rotation is about the centerX, centerY.
ellipse( center : Vector2, radiusX : number, radiusY : number, rotation : number ) : Shape¶
ellipse( radiusX : number, radiusY : number, rotation : number ) : Shape¶
ellipse( a : Vector2 | number, b : number, c : number, d? : number, e? : number ) : Shape¶
arc( centerX : number, centerY : number, radius : number, startAngle : number, endAngle : number, anticlockwise? : boolean ) : Shape¶
Supports both arc( centerX, centerY, radius, startAngle, endAngle, anticlockwise ) and arc( center, radius, startAngle, endAngle, anticlockwise )
@param radius - How far from the center the arc will be @param startAngle - Angle (radians) of the start of the arc @param endAngle - Angle (radians) of the end of the arc @param [anticlockwise] - Decides which direction the arc takes around the center
arc( center : Vector2, radius : number, startAngle : number, endAngle : number, anticlockwise? : boolean ) : Shape¶
arc( a : Vector2 | number, b : number, c : number, d : number, e? : number | boolean, f? : boolean ) : Shape¶
union( shapes : Shape[] ) : Shape¶
Returns the union of an array of shapes.
intersection( shapes : Shape[] ) : Shape¶
Returns the intersection of an array of shapes.
xor( shapes : Shape[] ) : Shape¶
Returns the xor of an array of shapes.
segments( segments : Segment[], closed? : boolean ) : Shape¶
Returns a new Shape constructed by appending a list of segments together.
Static Properties¶
rect¶
roundRectangle¶
Type CornerRadiiOptions¶
- topLeft: number
- topRight: number
- bottomRight: number
- bottomLeft: number
Type NonlinearTransformedOptions¶
- includeCurvature?: boolean
whether to include a default curveEpsilon (usually off by default) - & PiecewiseLinearOptions
Type SerializedShape¶
a normalized vector for non-zero winding checks var weirdDir = v( Math.PI, 22 / 7 );
- type: "Shape"
- subpaths: SerializedSubpath[]