Skip to content

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

import { Shape } from 'scenerystack/kite';

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

import type { CornerRadiiOptions } from 'scenerystack/kite';
  • topLeft: number
  • topRight: number
  • bottomRight: number
  • bottomLeft: number

Type NonlinearTransformedOptions

import type { NonlinearTransformedOptions } from 'scenerystack/kite';
  • 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 );

import type { SerializedShape } from 'scenerystack/kite';
  • type: "Shape"
  • subpaths: SerializedSubpath[]

Source Code

See the source for Shape.ts in the kite repository.