Skip to content

Shape

Under Construction

This documentation is auto-generated, and is a work in progress. Please see the source code at https://github.com/phetsims/kite/blob/main/js/Shape.ts for the most up-to-date information.

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.

fromGraph( graph : Graph ) : Shape

Returns a Shape that creates a subpath for each filled face (with the desired holes).

Generally should be called on a graph created with createFilledSubGraph().

fromSegment( segment : Segment ) : Shape

Static Properties

rect

roundRectangle

Type CornerRadiiOptions

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

Class Graph

A multigraph whose edges are segments.

Supports general shape simplification, overlap/intersection removal and computation. General output would include Shapes (from CAG - Constructive Area Geometry) and triangulations.

See Graph.binaryResult for the general procedure for CAG.

TODO: Use https://github.com/mauriciosantos/Buckets-JS for priority queue, implement simple sweep line https://github.com/phetsims/kite/issues/76 with "enters" and "leaves" entries in the queue. When edge removed, remove "leave" from queue. and add any replacement edges. Applies to overlap and intersection handling. NOTE: This should impact performance a lot, as we are currently over-scanning and re-scanning a lot. Intersection is currently (by far?) the performance bottleneck. TODO: Collapse non-Line adjacent edges together. Similar logic to overlap for each segment time, hopefully can factor this out. TODO: Properly handle sorting edges around a vertex when two edges have the same tangent out. We'll need to use curvature, or do tricks to follow both curves by an 'epsilon' and sort based on that. TODO: Consider separating out epsilon values (may be a general Kite thing rather than just ops) TODO: Loop-Blinn output and constrained Delaunay triangulation

@author Jonathan Olson <jonathan.olson@colorado.edu>

import { Graph } from 'scenerystack/kite';

Constructor

new Graph()

Instance Methods

serialize() : SerializedGraph

Returns an object form that can be turned back into a segment with the corresponding deserialize method.

addShape( shapeId : number, shape : Shape, options? : GraphAddOptions )

Adds a Shape (with a given ID for CAG purposes) to the graph.

@param shapeId - The ID which should be shared for all paths/shapes that should be combined with respect to the winding number of faces. For CAG, independent shapes should be given different IDs (so they have separate winding numbers recorded).

addSubpath( shapeId : number, subpath : Subpath, providedOptions? : GraphAddOptions )

Adds a subpath of a Shape (with a given ID for CAG purposes) to the graph.

@param shapeId - See addShape() documentation

computeSimplifiedFaces()

Simplifies edges/vertices, computes boundaries and faces (with the winding map).

computeFaceInclusion( windingMapFilter : ( windingMap: Record<number, number> ) => boolean )

Sets whether each face should be filled or unfilled based on a filter function.

The windingMapFilter will be called on each face's winding map, and will use the return value as whether the face is filled or not.

The winding map is an {Object} associated with each face that has a key for every shapeId that was used in addShape/addSubpath, and the value for those keys is the winding number of the face given all paths with the shapeId.

For example, imagine you added two shapeIds (0 and 1), and the iteration is on a face that is included in one loop specified with shapeId:0 (inside a counter-clockwise curve), and is outside of any segments specified by the second loop (shapeId:1). Then the winding map will be: { 0: 1 // shapeId:0 has a winding number of 1 for this face (generally filled) 1: 0 // shapeId:1 has a winding number of 0 for this face (generally not filled) }

Generally, winding map filters can be broken down into two steps: 1. Given the winding number for each shapeId, compute whether that loop was originally filled. Normally, this is done with a non-zero rule (any winding number is filled, except zero). SVG also provides an even-odd rule (odd numbers are filled, even numbers are unfilled). 2. Given booleans for each shapeId from step 1, compute CAG operations based on boolean formulas. Say you wanted to take the union of shapeIds 0 and 1, then remove anything in shapeId 2. Given the booleans above, this can be directly computed as (filled0 || filled1) && !filled2.

createFilledSubGraph() : Graph

Create a new Graph object based only on edges in this graph that separate a "filled" face from an "unfilled" face.

This is a convenient way to "collapse" adjacent filled and unfilled faces together, and compute the curves and holes properly, given a filled "normal" graph.

facesToShape() : Shape

Returns a Shape that creates a subpath for each filled face (with the desired holes).

Generally should be called on a graph created with createFilledSubGraph().

dispose()

Releases owned objects to their pools, and clears references that may have been picked up from external sources.

computeBoundaryTree()

Given the inner and outer boundaries, it computes a tree representation to determine what boundaries are holes of what other boundaries, then sets up face holes with the result.

This information is stored in the childBoundaries array of Boundary, and is then read out to set up faces.

Instance Properties

vertices : Vertex[]

edges : Edge[]

innerBoundaries : Boundary[]

outerBoundaries : Boundary[]

boundaries : Boundary[]

shapeIds : number[]

loops : Loop[]

unboundedFace : Face

faces : Face[]

Static Methods

deserialize( obj : SerializedGraph ) : Graph

Recreate a Graph based on serialized state from serialize()

isInternal( point : Vector2, t : number, segment : Segment, distanceThreshold : number, tThreshold : number ) : boolean

BINARY_NONZERO_UNION( windingMap : Record<number, number> ) : boolean

"Union" binary winding map filter for use with Graph.binaryResult.

This combines both shapes together so that a point is in the resulting shape if it was in either of the input shapes.

@param windingMap - See computeFaceInclusion for more details

BINARY_NONZERO_INTERSECTION( windingMap : Record<number, number> ) : boolean

"Intersection" binary winding map filter for use with Graph.binaryResult.

This combines both shapes together so that a point is in the resulting shape if it was in both of the input shapes.

@param windingMap - See computeFaceInclusion for more details

BINARY_NONZERO_DIFFERENCE( windingMap : Record<number, number> ) : boolean

"Difference" binary winding map filter for use with Graph.binaryResult.

This combines both shapes together so that a point is in the resulting shape if it was in the first shape AND was NOT in the second shape.

@param windingMap - See computeFaceInclusion for more details

BINARY_NONZERO_XOR( windingMap : Record<number, number> ) : boolean

"XOR" binary winding map filter for use with Graph.binaryResult.

This combines both shapes together so that a point is in the resulting shape if it is only in exactly one of the input shapes. It's like the union minus intersection.

@param windingMap - See computeFaceInclusion for more details

binaryResult( shapeA : Shape, shapeB : Shape, windingMapFilter : ( windingMap: Record<number, number> ) => boolean ) : Shape

Returns the resulting Shape obtained by combining the two shapes given with the filter.

unionNonZero( shapes : Shape[] ) : Shape

Returns the union of an array of shapes.

intersectionNonZero( shapes : Shape[] ) : Shape

Returns the intersection of an array of shapes.

xorNonZero( shapes : Shape[] ) : Shape

Returns the xor of an array of shapes.

TODO: reduce code duplication? https://github.com/phetsims/kite/issues/76

simplifyNonZero( shape : Shape ) : Shape

Returns a simplified Shape obtained from running it through the simplification steps with non-zero output.

clipShape( clipAreaShape : Shape, shape : Shape, providedOptions? : GraphClipOptions ) : Shape

Returns a clipped version of shape that contains only the parts that are within the area defined by clipAreaShape

Type GraphAddOptions

import type { GraphAddOptions } from 'scenerystack/kite';
  • ensureClosed?: boolean

Type GraphClipOptions

import type { GraphClipOptions } from 'scenerystack/kite';
  • includeExterior?: boolean
    Respectively whether segments should be in the returned shape if they are in the exterior of the clipAreaShape (outside), on the boundary, or in the interior.
  • includeBoundary?: boolean
  • includeInterior?: boolean

Type NonlinearTransformedOptions

import type { NonlinearTransformedOptions } from 'scenerystack/kite';
  • includeCurvature?: boolean
    whether to include a default curveEpsilon (usually off by default)
  • & PiecewiseLinearOptions

Type SerializedGraph

import type { SerializedGraph } from 'scenerystack/kite';

Type SerializedShape

a normalized vector for non-zero winding checks var weirdDir = v( Math.PI, 22 / 7 );

import type { SerializedShape } from 'scenerystack/kite';

Source Code

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