phetsims / kite

A library for creating, manipulating and displaying 2D shapes in JavaScript.
MIT License
13 stars 6 forks source link

TypeScript conversion #100

Open jonathanolson opened 1 year ago

jonathanolson commented 1 year ago

There are an assortment of issues converting kite internals to TS, notably:

TS2615: Type of property 'edge' circularly references itself in mapped type '{ [key in "edge" | "startVertex" | "endVertex"]: NotNull ; }'.
TS2615: Type of property 'forwardHalf' circularly references itself in mapped type '{ startVertex: Vertex; endVertex: Vertex; segment: Segment; forwardHalf: ActiveHalfEdge; reversedHalf: ActiveHalfEdge; }'.

If TS can't handle Edge having ActiveHalfEdges and HalfEdge having ActiveEdges, then this will be a painful conversion. Possibly will be fixed in the future?

Stopping a quick conversion, progress was:

```diff Subject: [PATCH] Kite internals typescript --- Index: js/imports.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/js/imports.ts b/js/imports.ts --- a/js/imports.ts (revision 36eadc34844462641aa8b8dc8f7bae5262e27f21) +++ b/js/imports.ts (date 1677785165379) @@ -22,21 +22,30 @@ export { default as svgPath } from './parser/svgPath.js'; export { default as Segment } from './segments/Segment.js'; -export type { ClosestToPointResult, PiecewiseLinearOptions } from './segments/Segment.js'; +export type { ClosestToPointResult, PiecewiseLinearOptions, SerializedSegment } from './segments/Segment.js'; export { default as Line } from './segments/Line.js'; +export type { SerializedLine } from './segments/Line.js'; export { default as Quadratic } from './segments/Quadratic.js'; +export type { SerializedQuadratic } from './segments/Quadratic.js'; export { default as Cubic } from './segments/Cubic.js'; +export type { SerializedCubic } from './segments/Cubic.js'; export { default as Arc } from './segments/Arc.js'; +export type { SerializedArc } from './segments/Arc.js'; export { default as EllipticalArc } from './segments/EllipticalArc.js'; +export type { SerializedEllipticalArc } from './segments/EllipticalArc.js'; export { default as Subpath } from './util/Subpath.js'; export { default as Shape } from './Shape.js'; -export { default as HalfEdge } from './ops/HalfEdge.js'; +export { default as HalfEdge, isActiveHalfEdge } from './ops/HalfEdge.js'; +export type { SerializedHalfEdge, ActiveHalfEdge } from './ops/HalfEdge.js'; export { default as Vertex } from './ops/Vertex.js'; -export { default as Edge } from './ops/Edge.js'; +export type { SerializedVertex, VertexInternalData } from './ops/Vertex.js'; +export { default as Edge, isActiveEdge } from './ops/Edge.js'; +export type { SerializedEdge, ActiveEdge, EdgeInternalData } from './ops/Edge.js'; export { default as Face } from './ops/Face.js'; export { default as Loop } from './ops/Loop.js'; +export type { SerializedLoop } from './ops/Loop.js'; export { default as Boundary } from './ops/Boundary.js'; export { default as BoundsIntersection } from './ops/BoundsIntersection.js'; export { default as SegmentTree } from './ops/SegmentTree.js'; Index: js/segments/EllipticalArc.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/js/segments/EllipticalArc.ts b/js/segments/EllipticalArc.ts --- a/js/segments/EllipticalArc.ts (revision 36eadc34844462641aa8b8dc8f7bae5262e27f21) +++ b/js/segments/EllipticalArc.ts (date 1677784394600) @@ -24,7 +24,7 @@ // constants const toDegrees = Utils.toDegrees; -type SerializedEllipticalArc = { +export type SerializedEllipticalArc = { type: 'EllipticalArc'; centerX: number; centerY: number; @@ -860,7 +860,7 @@ /** * Returns an object form that can be turned back into a segment with the corresponding deserialize method. */ - public serialize(): SerializedEllipticalArc { + public override serialize(): SerializedEllipticalArc { return { type: 'EllipticalArc', centerX: this._center.x, Index: js/segments/Cubic.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/js/segments/Cubic.ts b/js/segments/Cubic.ts --- a/js/segments/Cubic.ts (revision 36eadc34844462641aa8b8dc8f7bae5262e27f21) +++ b/js/segments/Cubic.ts (date 1677784390803) @@ -31,7 +31,7 @@ return t >= 0 && t <= 1; } -type SerializedCubic = { +export type SerializedCubic = { type: 'Cubic'; startX: number; startY: number; @@ -852,7 +852,7 @@ /** * Returns an object form that can be turned back into a segment with the corresponding deserialize method. */ - public serialize(): SerializedCubic { + public override serialize(): SerializedCubic { return { type: 'Cubic', startX: this._start.x, Index: js/ops/Boundary.js IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/js/ops/Boundary.js b/js/ops/Boundary.ts rename from js/ops/Boundary.js rename to js/ops/Boundary.ts --- a/js/ops/Boundary.js (revision 36eadc34844462641aa8b8dc8f7bae5262e27f21) +++ b/js/ops/Boundary.ts (date 1677785386280) @@ -7,27 +7,40 @@ * @author Jonathan Olson */ -import Bounds2 from '../../../dot/js/Bounds2.js'; +import Bounds2, { Bounds2StateObject } from '../../../dot/js/Bounds2.js'; import Ray2 from '../../../dot/js/Ray2.js'; +import Transform3 from '../../../dot/js/Transform3.js'; import Vector2 from '../../../dot/js/Vector2.js'; import cleanArray from '../../../phet-core/js/cleanArray.js'; import Pool from '../../../phet-core/js/Pool.js'; -import { kite, Subpath } from '../imports.js'; +import { ActiveHalfEdge, kite, Subpath } from '../imports.js'; let globaId = 0; +export type SerializedBoundary = { + type: 'Boundary'; + id: number; + halfEdges: number[]; // half-edge IDs + signedArea: number; + bounds: Bounds2StateObject; + childBoundaries: number[]; // boundary IDs +}; + class Boundary { + + public readonly id = ++globaId; + + public halfEdges: ActiveHalfEdge[] = []; + public signedArea!: number; + public bounds!: Bounds2; + public childBoundaries: Boundary[] = []; + /** - * @public (kite-internal) + * (kite-internal) * * NOTE: Use Boundary.pool.create for most usage instead of using the constructor directly. - * - * @param {Array.} halfEdges */ - constructor( halfEdges ) { - // @public {number} - this.id = ++globaId; - + public constructor( halfEdges: ActiveHalfEdge[] ) { // NOTE: most object properties are declared/documented in the initialize method. Please look there for most // definitions. this.initialize( halfEdges ); @@ -36,22 +49,11 @@ /** * Similar to a usual constructor, but is set up so it can be called multiple times (with dispose() in-between) to * support pooling. - * @private - * - * @param {Array.} halfEdges - * @returns {Boundary} - This reference for chaining */ - initialize( halfEdges ) { - // @public {Array.} + public initialize( halfEdges: ActiveHalfEdge[] ): this { this.halfEdges = halfEdges; - - // @public {number} this.signedArea = this.computeSignedArea(); - - // @public {Bounds2} this.bounds = this.computeBounds(); - - // @public {Array.} this.childBoundaries = cleanArray( this.childBoundaries ); return this; @@ -59,11 +61,8 @@ /** * Returns an object form that can be turned back into a segment with the corresponding deserialize method. - * @public - * - * @returns {Object} */ - serialize() { + public serialize(): SerializedBoundary { return { type: 'Boundary', id: this.id, @@ -77,9 +76,8 @@ /** * Removes references (so it can allow other objects to be GC'ed or pooled), and frees itself to the pool so it * can be reused. - * @public */ - dispose() { + public dispose(): void { this.halfEdges = []; cleanArray( this.childBoundaries ); this.freeToPool(); @@ -88,26 +86,20 @@ /** * Returns whether this boundary is essentially "counter-clockwise" (in the non-reversed coordinate system) with * positive signed area, or "clockwise" with negative signed area. - * @public * * Boundaries are treated as "inner" boundaries when they are counter-clockwise, as the path followed will generally * follow the inside of a face (given how the "next" edge of a vertex is computed). - * - * @returns {number} */ - isInner() { + public isInner(): boolean { return this.signedArea > 0; } /** * Returns the signed area of this boundary, given its half edges. - * @public * * Each half-edge has its own contribution to the signed area, which are summed together. - * - * @returns {number} */ - computeSignedArea() { + public computeSignedArea(): number { let signedArea = 0; for ( let i = 0; i < this.halfEdges.length; i++ ) { signedArea += this.halfEdges[ i ].signedAreaFragment; @@ -117,11 +109,8 @@ /** * Returns the bounds of the boundary (the union of each of the boundary's segments' bounds). - * @public - * - * @returns {Bounds2} */ - computeBounds() { + public computeBounds(): Bounds2 { const bounds = Bounds2.NOTHING.copy(); for ( let i = 0; i < this.halfEdges.length; i++ ) { @@ -133,16 +122,14 @@ /** * Returns a point on the boundary which, when the shape (and point) are transformed with the given transform, would * be a point with the minimal y value. - * @public * * Will only return one point, even if there are multiple points that have the same minimal y values for the * boundary. The point may be at the end of one of the edges/segments (at a vertex), but also may somewhere in the * middle of an edge/segment. * - * @param {Transform3} transform - Transform used because we want the inverse also. - * @returns {Vector2} + * @param transform - Transform used because we want the inverse also. */ - computeExtremePoint( transform ) { + public computeExtremePoint( transform: Transform3 ): Vector2 { assert && assert( this.halfEdges.length > 0, 'There is no extreme point if we have no edges' ); // Transform all of the segments into the new transformed coordinate space. @@ -187,7 +174,6 @@ /** * Returns a ray (position and direction) pointing away from our boundary at an "extreme" point, so that the ray * will be guaranteed not to intersect this boundary. - * @public * * The ray's position will be slightly offset from the boundary, so that it will not technically intersect the * boundary where the extreme point lies. The extreme point will be chosen such that it would have the smallest @@ -197,11 +183,8 @@ * in the negative-y direction (e.g. a vector of (0,-1)). This should guarantee it is facing away from the * boundary, and will be consistent in direction with other extreme rays (needed for its use case with the * boundary graph). - * - * @param {Transform3} transform - * @returns {Ray2} */ - computeExtremeRay( transform ) { + public computeExtremeRay( transform: Transform3 ): Ray2 { const extremePoint = this.computeExtremePoint( transform ); const orientation = transform.inverseDelta2( new Vector2( 0, -1 ) ).normalized(); return new Ray2( extremePoint.plus( orientation.timesScalar( 1e-4 ) ), orientation ); @@ -209,12 +192,8 @@ /** * Returns whether this boundary includes the specified half-edge. - * @public - * - * @param {HalfEdge} halfEdge - * @returns {boolean} */ - hasHalfEdge( halfEdge ) { + public hasHalfEdge( halfEdge: ActiveHalfEdge ): boolean { for ( let i = 0; i < this.halfEdges.length; i++ ) { if ( this.halfEdges[ i ] === halfEdge ) { return true; @@ -225,11 +204,8 @@ /** * Converts this boundary to a Subpath, so that we can construct things like Shape objects from it. - * @public - * - * @returns {Subpath} */ - toSubpath() { + public toSubpath(): Subpath { const segments = []; for ( let i = 0; i < this.halfEdges.length; i++ ) { segments.push( this.halfEdges[ i ].getDirectionalSegment() ); @@ -237,13 +213,11 @@ return new Subpath( segments, null, true ); } - // @public - freeToPool() { + public freeToPool(): void { Boundary.pool.freeToPool( this ); } - // @public - static pool = new Pool( Boundary ); + public static pool = new Pool( Boundary ); } kite.register( 'Boundary', Boundary ); Index: js/ops/HalfEdge.js IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/js/ops/HalfEdge.js b/js/ops/HalfEdge.ts rename from js/ops/HalfEdge.js rename to js/ops/HalfEdge.ts --- a/js/ops/HalfEdge.js (revision 36eadc34844462641aa8b8dc8f7bae5262e27f21) +++ b/js/ops/HalfEdge.ts (date 1677785104628) @@ -7,25 +7,56 @@ * @author Jonathan Olson */ -import Vector2 from '../../../dot/js/Vector2.js'; +import Vector2, { Vector2StateObject } from '../../../dot/js/Vector2.js'; import Pool from '../../../phet-core/js/Pool.js'; -import { kite } from '../imports.js'; +import IntentionalAny from '../../../phet-core/js/types/IntentionalAny.js'; +import WithoutNull from '../../../phet-core/js/types/WithoutNull.js'; +import { ActiveEdge, Face, kite, Segment, Vertex } from '../imports.js'; let globaId = 0; +export type ActiveHalfEdge = WithoutNull; +export const isActiveHalfEdge = ( halfEdge: HalfEdge ): halfEdge is ActiveHalfEdge => halfEdge.edge !== null; + +type Data = IntentionalAny; + +export type SerializedHalfEdge = { + type: 'HalfEdge'; + id: number; + edge: number; // edge.id + face: null | number; // face.id + isReversed: boolean; + signedAreaFragment: number; + startVertex: number; // startVertex.id + endVertex: number; // endVertex.id + sortVector: Vector2StateObject; + data: Data | null; +}; + class HalfEdge { + + public readonly id = ++globaId; + + public edge!: ActiveEdge | null; // Null if disposed (in pool) + public face!: Face | null; + public isReversed!: boolean; + public signedAreaFragment!: number; + public startVertex!: Vertex | null; // Null if disposed (in pool) + public endVertex!: Vertex | null; // Null if disposed (in pool) + + // Available for arbitrary client usage. -- Keep JSONable + public data!: Data | null; + + // Used for vertex sorting in Vertex.js. X is angle of end tangent (shifted), + // Y is curvature at end. See Vertex edge sort for more information. + public readonly sortVector = new Vector2( 0, 0 ); + /** - * @public (kite-internal) + * (kite-internal) * * NOTE: Use HalfEdge.pool.create for most usage instead of using the constructor directly. - * - * @param {Edge} edge - * @param {boolean} isReversed */ - constructor( edge, isReversed ) { - // @public {number} - this.id = ++globaId; - + public constructor( edge: ActiveEdge, isReversed: boolean ) { // NOTE: most object properties are declared/documented in the initialize method. Please look there for most // definitions. this.initialize( edge, isReversed ); @@ -34,37 +65,14 @@ /** * Similar to a usual constructor, but is set up so it can be called multiple times (with dispose() in-between) to * support pooling. - * @private - * - * @param {Edge} edge - * @param {boolean} isReversed - * @returns {HalfEdge} - This reference for chaining */ - initialize( edge, isReversed ) { - assert && assert( edge instanceof kite.Edge ); - assert && assert( typeof isReversed === 'boolean' ); - - // @public {Edge|null} - Null if disposed (in pool) + public initialize( edge: ActiveEdge, isReversed: boolean ): this { this.edge = edge; - - // @public {Face|null} - Filled in later, contains a face reference this.face = null; - - // @public {boolean} this.isReversed = isReversed; - - // @public {number} this.signedAreaFragment = edge.signedAreaFragment * ( isReversed ? -1 : 1 ); - - // @public {Vertex|null} this.startVertex = null; this.endVertex = null; - - // @public {Vector2} - Used for vertex sorting in Vertex.js. X is angle of end tangent (shifted), - // Y is curvature at end. See Vertex edge sort for more information. - this.sortVector = this.sortVector || new Vector2( 0, 0 ); - - // @public {*} - Available for arbitrary client usage. --- KEEP JSON this.data = null; this.updateReferences(); // Initializes vertex references @@ -74,31 +82,30 @@ /** * Returns an object form that can be turned back into a segment with the corresponding deserialize method. - * @public - * - * @returns {Object} */ - serialize() { + public serialize(): SerializedHalfEdge { + assert && assert( isActiveHalfEdge( this ) ); + const activeThis = this as ActiveHalfEdge; + return { type: 'HalfEdge', - id: this.id, - edge: this.edge.id, - face: this.face === null ? null : this.face.id, - isReversed: this.isReversed, - signedAreaFragment: this.signedAreaFragment, - startVertex: this.startVertex === null ? null : this.startVertex.id, - endVertex: this.endVertex === null ? null : this.endVertex.id, - sortVector: Vector2.Vector2IO.toStateObject( this.sortVector ), - data: this.data + id: activeThis.id, + edge: activeThis.edge.id, + face: activeThis.face === null ? null : activeThis.face.id, + isReversed: activeThis.isReversed, + signedAreaFragment: activeThis.signedAreaFragment, + startVertex: activeThis.startVertex.id, + endVertex: activeThis.endVertex.id, + sortVector: Vector2.Vector2IO.toStateObject( activeThis.sortVector ), + data: activeThis.data }; } /** * Removes references (so it can allow other objects to be GC'ed or pooled), and frees itself to the pool so it * can be reused. - * @public */ - dispose() { + public dispose(): void { this.edge = null; this.face = null; this.startVertex = null; @@ -109,100 +116,101 @@ /** * Returns the next half-edge, walking around counter-clockwise as possible. Assumes edges have been sorted. - * @public * - * @param {function} [filter] - function( {Edge} ) => {boolean}. If it returns false, the edge will be skipped, and - * not returned by getNext + * @param [filter] - If it returns false, the edge will be skipped, and not returned by getNext */ - getNext( filter ) { + public getNext( filter?: ( edge: ActiveEdge ) => boolean ): HalfEdge { + assert && assert( isActiveHalfEdge( this ) ); + const activeThis = this as ActiveHalfEdge; + // Starting at 1, forever incrementing (we will bail out with normal conditions) for ( let i = 1; ; i++ ) { - let index = this.endVertex.incidentHalfEdges.indexOf( this ) - i; + let index = activeThis.endVertex.incidentHalfEdges.indexOf( activeThis ) - i; if ( index < 0 ) { - index += this.endVertex.incidentHalfEdges.length; + index += activeThis.endVertex.incidentHalfEdges.length; } - const halfEdge = this.endVertex.incidentHalfEdges[ index ].getReversed(); + const halfEdge = activeThis.endVertex.incidentHalfEdges[ index ].getReversed(); if ( filter && !filter( halfEdge.edge ) ) { continue; } - assert && assert( this.endVertex === halfEdge.startVertex ); + assert && assert( activeThis.endVertex === halfEdge.startVertex ); return halfEdge; } } /** * Update possibly reversed vertex references. - * @private */ - updateReferences() { - this.startVertex = this.isReversed ? this.edge.endVertex : this.edge.startVertex; - this.endVertex = this.isReversed ? this.edge.startVertex : this.edge.endVertex; + public updateReferences(): void { + assert && assert( isActiveHalfEdge( this ) ); + const activeThis = this as ActiveHalfEdge; + + activeThis.startVertex = activeThis.isReversed ? activeThis.edge.endVertex : activeThis.edge.startVertex; + activeThis.endVertex = activeThis.isReversed ? activeThis.edge.startVertex : activeThis.edge.endVertex; assert && assert( this.startVertex ); assert && assert( this.endVertex ); } /** * Returns the tangent of the edge at the end vertex (in the direction away from the vertex). - * @public - * - * @returns {Vector2} */ - getEndTangent() { + public getEndTangent(): Vector2 { + assert && assert( isActiveHalfEdge( this ) ); + const activeThis = this as ActiveHalfEdge; + if ( this.isReversed ) { - return this.edge.segment.startTangent; + return activeThis.edge.segment.startTangent; } else { - return this.edge.segment.endTangent.negated(); + return activeThis.edge.segment.endTangent.negated(); } } /** * Returns the curvature of the edge at the end vertex. - * @public - * - * @returns {number} */ - getEndCurvature() { + public getEndCurvature(): number { + assert && assert( isActiveHalfEdge( this ) ); + const activeThis = this as ActiveHalfEdge; + if ( this.isReversed ) { - return -this.edge.segment.curvatureAt( 0 ); + return -activeThis.edge.segment.curvatureAt( 0 ); } else { - return this.edge.segment.curvatureAt( 1 ); + return activeThis.edge.segment.curvatureAt( 1 ); } } /** * Returns the opposite half-edge for the same edge. - * @public - * - * @returns {HalfEdge} */ - getReversed() { - return this.isReversed ? this.edge.forwardHalf : this.edge.reversedHalf; + public getReversed(): ActiveHalfEdge { + assert && assert( isActiveHalfEdge( this ) ); + const activeThis = this as ActiveHalfEdge; + + return activeThis.isReversed ? activeThis.edge.forwardHalf : activeThis.edge.reversedHalf; } /** * Returns a segment that starts at our startVertex and ends at our endVertex (may be reversed to accomplish that). - * @public - * - * @returns {Segment} */ - getDirectionalSegment() { - if ( this.isReversed ) { - return this.edge.segment.reversed(); + public getDirectionalSegment(): Segment { + assert && assert( isActiveHalfEdge( this ) ); + const activeThis = this as ActiveHalfEdge; + + if ( activeThis.isReversed ) { + return activeThis.edge.segment.reversed(); } else { - return this.edge.segment; + return activeThis.edge.segment; } } - // @public - freeToPool() { + public freeToPool(): void { HalfEdge.pool.freeToPool( this ); } - // @public - static pool = new Pool( HalfEdge ); + public static pool = new Pool( HalfEdge ); } kite.register( 'HalfEdge', HalfEdge ); Index: js/ops/Loop.js IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/js/ops/Loop.js b/js/ops/Loop.ts rename from js/ops/Loop.js rename to js/ops/Loop.ts --- a/js/ops/Loop.js (revision 36eadc34844462641aa8b8dc8f7bae5262e27f21) +++ b/js/ops/Loop.ts (date 1677784670670) @@ -16,22 +16,32 @@ import cleanArray from '../../../phet-core/js/cleanArray.js'; import Pool from '../../../phet-core/js/Pool.js'; -import { kite, Subpath } from '../imports.js'; +import { ActiveHalfEdge, kite, Subpath } from '../imports.js'; let globaId = 0; +export type SerializedLoop = { + type: 'Loop'; + id: number; + shapeId: number; + closed: boolean; + halfEdges: number[]; // half-edge IDs +}; + class Loop { + + public readonly id = ++globaId; + + public shapeId!: number; + public closed!: boolean; + public halfEdges: ActiveHalfEdge[] = []; + /** - * @public (kite-internal) + * (kite-internal) * * NOTE: Use Loop.pool.create for most usage instead of using the constructor directly. - * - * @param {number} shapeId - * @param {boolean} closed */ - constructor( shapeId, closed ) { - // @public {number} - this.id = ++globaId; + public constructor( shapeId: number, closed: boolean ) { // NOTE: most object properties are declared/documented in the initialize method. Please look there for most // definitions. @@ -41,23 +51,10 @@ /** * Similar to a usual constructor, but is set up so it can be called multiple times (with dispose() in-between) to * support pooling. - * @private - * - * @param {number} shapeId - * @param {boolean} closed - * @returns {Loop} - This reference for chaining */ - initialize( shapeId, closed ) { - assert && assert( typeof shapeId === 'number' ); - assert && assert( typeof closed === 'boolean' ); - - // @public {number} + public initialize( shapeId: number, closed: boolean ): this { this.shapeId = shapeId; - - // @public {boolean} this.closed = closed; - - // @public {Array.} this.halfEdges = cleanArray( this.halfEdges ); return this; @@ -65,11 +62,8 @@ /** * Returns an object form that can be turned back into a segment with the corresponding deserialize method. - * @public - * - * @returns {Object} */ - serialize() { + public serialize(): SerializedLoop { return { type: 'Loop', id: this.id, @@ -81,11 +75,8 @@ /** * Returns a Subpath equivalent to this loop. - * @public - * - * @returns {Subpath} */ - toSubpath() { + public toSubpath(): Subpath { const segments = []; for ( let i = 0; i < this.halfEdges.length; i++ ) { segments.push( this.halfEdges[ i ].getDirectionalSegment() ); @@ -96,20 +87,17 @@ /** * Removes references (so it can allow other objects to be GC'ed or pooled), and frees itself to the pool so it * can be reused. - * @public */ - dispose() { + public dispose(): void { cleanArray( this.halfEdges ); this.freeToPool(); } - // @public - freeToPool() { + public freeToPool(): void { Loop.pool.freeToPool( this ); } - // @public - static pool = new Pool( Loop ); + public static pool = new Pool( Loop ); } kite.register( 'Loop', Loop ); Index: js/util/Subpath.js IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/js/util/Subpath.js b/js/util/Subpath.js --- a/js/util/Subpath.js (revision 36eadc34844462641aa8b8dc8f7bae5262e27f21) +++ b/js/util/Subpath.js (date 1677785384207) @@ -21,7 +21,7 @@ * NOTE: No arguments required (they are usually used for copy() usage or creation with new segments) * * @param {Array.} [segments] - * @param {Array.} [points] + * @param {Array. | null} [points] * @param {boolean} [closed] */ constructor( segments, points, closed ) { Index: js/ops/Edge.js IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/js/ops/Edge.js b/js/ops/Edge.ts rename from js/ops/Edge.js rename to js/ops/Edge.ts --- a/js/ops/Edge.js (revision 36eadc34844462641aa8b8dc8f7bae5262e27f21) +++ b/js/ops/Edge.ts (date 1677785178722) @@ -7,24 +7,61 @@ */ import Pool from '../../../phet-core/js/Pool.js'; -import { HalfEdge, kite, Line, Segment, Vertex } from '../imports.js'; +import IntentionalAny from '../../../phet-core/js/types/IntentionalAny.js'; +import WithoutNull from '../../../phet-core/js/types/WithoutNull.js'; +import { ActiveHalfEdge, HalfEdge, kite, Line, Segment, SerializedHalfEdge, SerializedSegment, Vertex } from '../imports.js'; let globaId = 0; +export type ActiveEdge = WithoutNull; +export const isActiveEdge = ( edge: Edge ): edge is ActiveEdge => edge.segment !== null; + +export type EdgeInternalData = { + removedId?: number; + segmentId?: number; +}; + +type Data = IntentionalAny; + +export type SerializedEdge = { + type: 'Edge'; + id: number; + segment: SerializedSegment; + startVertex: number; + endVertex: number; + signedAreaFragment: number; + forwardHalf: SerializedHalfEdge; + reversedHalf: SerializedHalfEdge; + visited: boolean; + data: Data | null; +}; + class Edge { + + public readonly id = ++globaId; + + public segment!: Segment | null; // Null if disposed (in pool) + public startVertex!: Vertex | null; // Null if disposed (in pool) + public endVertex!: Vertex | null; // Null if disposed (in pool) + public signedAreaFragment!: number; + public forwardHalf!: ActiveHalfEdge | null; // Null if disposed (in pool) + public reversedHalf!: ActiveHalfEdge | null; // Null if disposed (in pool) + + // Used for depth-first search + public visited!: boolean; + + // Available for arbitrary client usage. -- Keep JSONable + public data!: Data | null; + + // (kite-internal) + public internalData!: EdgeInternalData; + /** - * @public (kite-internal) + * (kite-internal) * * NOTE: Use Edge.pool.create for most usage instead of using the constructor directly. - * - * @param {Segment} segment - * @param {Vertex} startVertex - * @param {Vertex} endVertex */ - constructor( segment, startVertex, endVertex ) { - // @public {number} - this.id = ++globaId; - + public constructor( segment: Segment, startVertex: Vertex, endVertex: Vertex ) { // NOTE: most object properties are declared/documented in the initialize method. Please look there for most // definitions. this.initialize( segment, startVertex, endVertex ); @@ -33,81 +70,59 @@ /** * Similar to a usual constructor, but is set up so it can be called multiple times (with dispose() in-between) to * support pooling. - * @private - * - * @param {Segment} segment - * @param {Vertex} startVertex - * @param {Vertex} endVertex - * @returns {Edge} - This reference for chaining */ - initialize( segment, startVertex, endVertex ) { - assert && assert( segment instanceof Segment ); - assert && assert( startVertex instanceof Vertex ); - assert && assert( endVertex instanceof Vertex ); + public initialize( segment: Segment, startVertex: Vertex, endVertex: Vertex ): this { assert && assert( segment.start.distance( startVertex.point ) < 1e-3 ); assert && assert( segment.end.distance( endVertex.point ) < 1e-3 ); - // @public {Segment|null} - Null when disposed (in pool) this.segment = segment; - - // @public {Vertex|null} - Null when disposed (in pool) this.startVertex = startVertex; this.endVertex = endVertex; - - // @public {number} this.signedAreaFragment = segment.getSignedAreaFragment(); - - // @public {HalfEdge|null} - Null when disposed (in pool) - this.forwardHalf = HalfEdge.pool.create( this, false ); - this.reversedHalf = HalfEdge.pool.create( this, true ); - - // @public {boolean} - Used for depth-first search + this.forwardHalf = HalfEdge.pool.create( this as ActiveEdge, false ) as ActiveHalfEdge; + this.reversedHalf = HalfEdge.pool.create( this as ActiveEdge, true ) as ActiveHalfEdge; this.visited = false; - - // @public {*} - Available for arbitrary client usage. -- Keep JSONable this.data = null; - - // @public {*} - kite-internal - this.internalData = { - - }; + this.internalData = {}; return this; } /** * Returns an object form that can be turned back into a segment with the corresponding deserialize method. - * @public - * - * @returns {Object} */ - serialize() { + public serialize(): SerializedEdge { + assert && assert( isActiveEdge( this ) ); + const activeThis = this as ActiveEdge; + return { type: 'Edge', - id: this.id, - segment: this.segment.serialize(), - startVertex: this.startVertex === null ? null : this.startVertex.id, - endVertex: this.endVertex === null ? null : this.endVertex.id, - signedAreaFragment: this.signedAreaFragment, - forwardHalf: this.forwardHalf.serialize(), - reversedHalf: this.reversedHalf.serialize(), - visited: this.visited, - data: this.data + id: activeThis.id, + segment: activeThis.segment.serialize(), + startVertex: activeThis.startVertex.id, + endVertex: activeThis.endVertex.id, + signedAreaFragment: activeThis.signedAreaFragment, + forwardHalf: activeThis.forwardHalf.serialize(), + reversedHalf: activeThis.reversedHalf.serialize(), + visited: activeThis.visited, + data: activeThis.data }; } /** * Removes references (so it can allow other objects to be GC'ed or pooled), and frees itself to the pool so it * can be reused. - * @public */ - dispose() { + public dispose(): void { + assert && assert( isActiveEdge( this ) ); + const activeThis = this as ActiveEdge; + this.segment = null; this.startVertex = null; this.endVertex = null; - this.forwardHalf.dispose(); - this.reversedHalf.dispose(); + activeThis.forwardHalf.dispose(); + activeThis.reversedHalf.dispose(); this.forwardHalf = null; this.reversedHalf = null; @@ -119,36 +134,34 @@ /** * Returns the other vertex associated with an edge. - * @public - * - * @param {Vertex} vertex - * @returns {Vertex} */ - getOtherVertex( vertex ) { + public getOtherVertex( vertex: Vertex ): Vertex { assert && assert( vertex === this.startVertex || vertex === this.endVertex ); + assert && assert( isActiveEdge( this ) ); + const activeThis = this as ActiveEdge; - return this.startVertex === vertex ? this.endVertex : this.startVertex; + return activeThis.startVertex === vertex ? activeThis.endVertex : activeThis.startVertex; } /** * Update possibly reversed vertex references (since they may be updated) - * @public */ - updateReferences() { - this.forwardHalf.updateReferences(); - this.reversedHalf.updateReferences(); + public updateReferences(): void { + assert && assert( isActiveEdge( this ) ); + const activeThis = this as ActiveEdge; - assert && assert( !( this.segment instanceof Line ) || this.startVertex !== this.endVertex, + activeThis.forwardHalf.updateReferences(); + activeThis.reversedHalf.updateReferences(); + + assert && assert( !( activeThis.segment instanceof Line ) || activeThis.startVertex !== activeThis.endVertex, 'No line segments for same vertices' ); } - // @public - freeToPool() { + public freeToPool(): void { Edge.pool.freeToPool( this ); } - // @public - static pool = new Pool( Edge ); + public static pool = new Pool( Edge ); } kite.register( 'Edge', Edge ); Index: js/segments/Quadratic.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/js/segments/Quadratic.ts b/js/segments/Quadratic.ts --- a/js/segments/Quadratic.ts (revision 36eadc34844462641aa8b8dc8f7bae5262e27f21) +++ b/js/segments/Quadratic.ts (date 1677784401514) @@ -24,7 +24,7 @@ return t >= 0 && t <= 1; } -type SerializedQuadratic = { +export type SerializedQuadratic = { type: 'Quadratic'; startX: number; startY: number; @@ -597,7 +597,7 @@ /** * Returns an object form that can be turned back into a segment with the corresponding deserialize method. */ - public serialize(): SerializedQuadratic { + public override serialize(): SerializedQuadratic { return { type: 'Quadratic', startX: this._start.x, Index: js/segments/Arc.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/js/segments/Arc.ts b/js/segments/Arc.ts --- a/js/segments/Arc.ts (revision 36eadc34844462641aa8b8dc8f7bae5262e27f21) +++ b/js/segments/Arc.ts (date 1677784386025) @@ -16,7 +16,7 @@ // TODO: See if we should use this more const TWO_PI = Math.PI * 2; -type SerializedArc = { +export type SerializedArc = { type: 'Arc'; centerX: number; centerY: number; @@ -747,7 +747,7 @@ /** * Returns an object form that can be turned back into a segment with the corresponding deserialize method. */ - public serialize(): SerializedArc { + public override serialize(): SerializedArc { return { type: 'Arc', centerX: this._center.x, Index: js/segments/Segment.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/js/segments/Segment.ts b/js/segments/Segment.ts --- a/js/segments/Segment.ts (revision 36eadc34844462641aa8b8dc8f7bae5262e27f21) +++ b/js/segments/Segment.ts (date 1677784466793) @@ -18,7 +18,7 @@ import Vector2 from '../../../dot/js/Vector2.js'; import optionize from '../../../phet-core/js/optionize.js'; import IntentionalAny from '../../../phet-core/js/types/IntentionalAny.js'; -import { Arc, BoundsIntersection, EllipticalArc, kite, Line, RayIntersection, SegmentIntersection, Shape, Subpath } from '../imports.js'; +import { Arc, BoundsIntersection, EllipticalArc, kite, Line, RayIntersection, SegmentIntersection, SerializedArc, SerializedCubic, SerializedEllipticalArc, SerializedLine, SerializedQuadratic, Shape, Subpath } from '../imports.js'; type DashValues = { @@ -47,6 +47,8 @@ distanceSquared: number; }; +export type SerializedSegment = SerializedArc | SerializedCubic | SerializedEllipticalArc | SerializedLine | SerializedQuadratic; + export type PiecewiseLinearOptions = { // how many levels to force subdivisions minLevels?: number; @@ -768,6 +770,11 @@ } return true; } + + /** + * Returns an object form that can be turned back into a segment with the corresponding deserialize method. + */ + public abstract serialize(): SerializedSegment; } kite.register( 'Segment', Segment ); Index: js/segments/Line.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/js/segments/Line.ts b/js/segments/Line.ts --- a/js/segments/Line.ts (revision 36eadc34844462641aa8b8dc8f7bae5262e27f21) +++ b/js/segments/Line.ts (date 1677784398244) @@ -15,7 +15,7 @@ const scratchVector2 = new Vector2( 0, 0 ); -type SerializedLine = { +export type SerializedLine = { type: 'Line'; startX: number; startY: number; @@ -446,7 +446,7 @@ /** * Returns an object form that can be turned back into a segment with the corresponding deserialize method. */ - public serialize(): SerializedLine { + public override serialize(): SerializedLine { return { type: 'Line', startX: this._start.x, Index: js/ops/Vertex.js IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/js/ops/Vertex.js b/js/ops/Vertex.ts rename from js/ops/Vertex.js rename to js/ops/Vertex.ts --- a/js/ops/Vertex.js (revision 36eadc34844462641aa8b8dc8f7bae5262e27f21) +++ b/js/ops/Vertex.ts (date 1677785845244) @@ -8,23 +8,57 @@ * @author Jonathan Olson */ -import Vector2 from '../../../dot/js/Vector2.js'; +import Vector2, { Vector2StateObject } from '../../../dot/js/Vector2.js'; import cleanArray from '../../../phet-core/js/cleanArray.js'; import Pool from '../../../phet-core/js/Pool.js'; -import { kite, Line } from '../imports.js'; +import { ActiveHalfEdge, kite, Line } from '../imports.js'; let globaId = 0; +export type VertexInternalData = { + removedId?: number; + segmentId?: number; +}; + +export type SerializedVertex = { + type: 'Vertex'; + id: number; + point: Vector2StateObject; + incidentHalfEdges: number[]; // half-edge IDs + visited: boolean; + visitIndex: number; + lowIndex: number; +}; + class Vertex { + + public readonly id = ++globaId; + + public point!: Vector2; + + // Records the half-edge that points to (ends at) this vertex. + public incidentHalfEdges: ActiveHalfEdge[] = []; + + // Used for depth-first search + public visited!: boolean; + + // Visit index for bridge detection (more efficient to have inline here) + public visitIndex!: number; + + // Low index for bridge detection (more efficient to have inline here) + public lowIndex!: number; + + // (kite-internal) + public internalData!: VertexInternalData; + /** - * @public (kite-internal) + * (kite-internal) * * NOTE: Use Vertex.pool.create for most usage instead of using the constructor directly. * - * @param {Vector2} point - The point where the vertex should be located. + * @param point - The point where the vertex should be located. */ - constructor( point ) { - // @public {number} + public constructor( point: Vector2 ) { this.id = ++globaId; // NOTE: most object properties are declared/documented in the initialize method. Please look there for most @@ -35,47 +69,22 @@ /** * Similar to a usual constructor, but is set up so it can be called multiple times (with dispose() in-between) to * support pooling. - * @private - * - * @param {Vector2} point - * @returns {Vertex} - This reference for chaining */ - initialize( point ) { - assert && assert( point instanceof Vector2 ); - - // @public {Vector2} + public initialize( point: Vector2 ): this { this.point = point; - - // @public {Array.} - Records the half-edge that points to (ends at) this vertex. this.incidentHalfEdges = cleanArray( this.incidentHalfEdges ); - - // @public {boolean} - Used for depth-first search this.visited = false; - - // @public {number} - Visit index for bridge detection (more efficient to have inline here) this.visitIndex = 0; - - // @public {number} - Low index for bridge detection (more efficient to have inline here) this.lowIndex = 0; - - // @public {*} - Available for arbitrary client usage. -- Keep JSONable - this.data = null; - - // @public {*} - kite-internal - this.internalData = { - - }; + this.internalData = {}; return this; } /** * Returns an object form that can be turned back into a segment with the corresponding deserialize method. - * @public - * - * @returns {Object} */ - serialize() { + public serialize(): SerializedVertex { return { type: 'Vertex', id: this.id, @@ -90,9 +99,8 @@ /** * Removes references (so it can allow other objects to be GC'ed or pooled), and frees itself to the pool so it * can be reused. - * @public */ - dispose() { + public dispose(): void { this.point = Vector2.ZERO; cleanArray( this.incidentHalfEdges ); this.freeToPool(); @@ -100,9 +108,8 @@ /** * Sorts the edges in increasing angle order. - * @public */ - sortEdges() { + public sortEdges(): void { const vectors = []; // x coordinate will be "angle", y coordinate will be curvature for ( let i = 0; i < this.incidentHalfEdges.length; i++ ) { const halfEdge = this.incidentHalfEdges[ i ]; @@ -138,13 +145,8 @@ /** * Compare two edges for sortEdges. Should have executed that first, as it relies on information looked up in that * process. - * @public - * - * @param {Edge} halfEdgeA - * @param {Edge} halfEdgeB - * @returns {number} */ - static edgeComparison( halfEdgeA, halfEdgeB ) { + public static edgeComparison( halfEdgeA: ActiveHalfEdge, halfEdgeB: ActiveHalfEdge ): number { const angleA = halfEdgeA.sortVector.x; const angleB = halfEdgeB.sortVector.x; @@ -167,13 +169,11 @@ } } - // @public - freeToPool() { + public freeToPool(): void { Vertex.pool.freeToPool( this ); } - // @public - static pool = new Pool( Vertex ); + public static pool = new Pool( Vertex ); } kite.register( 'Vertex', Vertex ); ```