danceflow-geometry 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,94 @@
1
+ # danceflow-geometry
2
+
3
+ Geometry algorithms for dance choreography formations.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install danceflow-geometry
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```javascript
14
+ import {
15
+ calculateCentroid,
16
+ spreadFromCentroid,
17
+ arrangeInLine,
18
+ arrangeInCircle,
19
+ isPointInPolygon
20
+ } from 'danceflow-geometry';
21
+
22
+ // Calculate center of dancers
23
+ const positions = [{ x: 100, y: 100 }, { x: 200, y: 150 }];
24
+ const center = calculateCentroid(positions);
25
+
26
+ // Spread dancers from center
27
+ const spread = spreadFromCentroid(positions, 1.5);
28
+ ```
29
+
30
+ ## Functions
31
+
32
+ ### Centroid
33
+ - `calculateCentroid(positions)` - Find center point of a group of positions
34
+ - `scaleAroundCenter(positions, scale)` - Scale positions around their centroid
35
+
36
+ ### Spreader
37
+ - `spreadFromCentroid(positions, factor)` - Expand/contract from center point
38
+ - `spreadHorizontal(positions, factor)` - Spread horizontally only
39
+ - `spreadVertical(positions, factor)` - Spread vertically only
40
+
41
+ ### Line Arrangement
42
+ - `arrangeInLine(positions, start, end)` - Arrange in a straight line
43
+ - `arrangeInColumn(positions, start, spacing)` - Arrange in a vertical column
44
+ - `arrangeInDiagonal(positions, start, angle, spacing)` - Arrange diagonally
45
+
46
+ ### Circle Arrangement
47
+ - `arrangeInCircle(positions, center, radius)` - Arrange in a circle
48
+ - `arrangeInArc(positions, center, radius, startAngle, endAngle)` - Arrange in an arc
49
+ - `arrangeInSpiral(positions, center, startRadius, endRadius)` - Arrange in a spiral
50
+
51
+ ### Mirror & Flip
52
+ - `mirrorHorizontal(positions, axisX)` - Mirror across vertical axis
53
+ - `mirrorVertical(positions, axisY)` - Mirror across horizontal axis
54
+ - `flipFormation(positions)` - Flip entire formation
55
+
56
+ ### Rotation
57
+ - `rotateFormation(positions, angle)` - Rotate around centroid
58
+ - `rotateAroundPoint(positions, point, angle)` - Rotate around specific point
59
+
60
+ ### Polygon
61
+ - `isPointInPolygon(point, polygon)` - Point-in-polygon test (ray casting)
62
+ - `createPolygonPath(points)` - Create closed polygon path
63
+
64
+ ### Bezier Curves
65
+ - `cubicBezier(t, p0, p1, p2, p3)` - Cubic bezier interpolation
66
+ - `quadraticBezier(t, p0, p1, p2)` - Quadratic bezier interpolation
67
+ - `calculateOptimalArcHeight(start, end)` - Calculate optimal arc control point
68
+
69
+ ### Spline
70
+ - `catmullRomSpline(points, t)` - Catmull-Rom spline interpolation
71
+ - `createSmoothPath(points, segments)` - Create smooth path through points
72
+
73
+ ### Collision Detection
74
+ - `detectCollisions(positions, radius)` - Find overlapping positions
75
+ - `resolveOverlaps(positions, minDistance)` - Push apart overlapping positions
76
+
77
+ ## Types
78
+
79
+ ```typescript
80
+ interface Point {
81
+ x: number;
82
+ y: number;
83
+ }
84
+
85
+ interface DancerPosition {
86
+ x: number;
87
+ y: number;
88
+ facing: number;
89
+ }
90
+ ```
91
+
92
+ ## License
93
+
94
+ MIT
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Bezier curve mathematics for swap arc animations
3
+ * Implements cubic Bezier interpolation for smooth dancer transition paths
4
+ *
5
+ * Mathematical Foundation:
6
+ * Cubic Bezier curve formula: P(t) = (1-t)³P₀ + 3(1-t)²tP₁ + 3(1-t)t²P₂ + t³P₃
7
+ *
8
+ * CRITICAL: Stage coordinates have Y-axis pointing DOWN
9
+ * This affects the perpendicular vector calculation for arc control points
10
+ *
11
+ * Where:
12
+ * - P₀ = start position (pos1)
13
+ * - P₃ = end position (pos2)
14
+ * - P₁, P₂ = control points (calculated perpendicular to pos1-pos2 line)
15
+ * - t ∈ [0, 1] = interpolation parameter
16
+ *
17
+ * Performance target: <5ms for typical swap operations
18
+ * Coordinate system: ALL calculations in Stage coordinates (0-1000)
19
+ */
20
+ import type { Point, DancerPosition } from './types';
21
+ /**
22
+ * Calculate a single point on a cubic Bezier curve
23
+ *
24
+ * Math: P(t) = (1-t)³P₀ + 3(1-t)²tP₁ + 3(1-t)t²P₂ + t³P₃
25
+ *
26
+ * @param t - Interpolation parameter [0, 1]
27
+ * @param p0 - Start point
28
+ * @param p1 - First control point
29
+ * @param p2 - Second control point
30
+ * @param p3 - End point
31
+ * @returns Point on curve at parameter t
32
+ *
33
+ * @example
34
+ * const start = { x: 100, y: 100 };
35
+ * const end = { x: 200, y: 100 };
36
+ * const ctrl1 = { x: 125, y: 50 };
37
+ * const ctrl2 = { x: 175, y: 50 };
38
+ * const midpoint = cubicBezier(0.5, start, ctrl1, ctrl2, end);
39
+ * // → { x: 150, y: 62.5 } (approximate)
40
+ */
41
+ export declare function cubicBezier(t: number, p0: Point, p1: Point, p2: Point, p3: Point): Point;
42
+ /**
43
+ * Calculate control points for a symmetric arc above the line connecting two points
44
+ *
45
+ * CRITICAL: Adapted for Stage Y-down coordinate system
46
+ * Positive arcHeight creates an arc UPWARD (negative Y direction)
47
+ * Negative arcHeight creates an arc DOWNWARD (positive Y direction)
48
+ *
49
+ * Algorithm:
50
+ * 1. Find midpoint M between pos1 and pos2
51
+ * 2. Calculate direction vector D = pos2 - pos1
52
+ * 3. Calculate perpendicular vector P = (-D.y, D.x) normalized
53
+ * 4. NEGATE P for Y-down coordinates (arc upward is negative Y)
54
+ * 5. Place control points symmetrically: M + P × arcHeight
55
+ *
56
+ * @param pos1 - Start position (Stage coordinates)
57
+ * @param pos2 - End position (Stage coordinates)
58
+ * @param arcHeight - Height of arc perpendicular to line (Stage units, positive = upward)
59
+ * @returns Tuple of [controlPoint1, controlPoint2]
60
+ *
61
+ * @example
62
+ * const start = { x: 100, y: 100, facing: 0 };
63
+ * const end = { x: 200, y: 100, facing: 0 };
64
+ * const [cp1, cp2] = calculateArcControlPoints(start, end, 50);
65
+ * // Arc curves upward by 50 units above the midpoint (Y decreases)
66
+ */
67
+ export declare function calculateArcControlPoints(pos1: DancerPosition, pos2: DancerPosition, arcHeight: number): [Point, Point];
68
+ /**
69
+ * Generate smooth arc path between two dancer positions
70
+ *
71
+ * This is the primary function for swap arc animations. It creates a cubic Bezier
72
+ * curve that arcs above the straight line between two positions.
73
+ *
74
+ * Performance: O(n) where n = number of interpolation points (default 20)
75
+ *
76
+ * @param pos1 - Start position (Stage coordinates)
77
+ * @param pos2 - End position (Stage coordinates)
78
+ * @param arcHeight - Arc height perpendicular to line (Stage units, default 80, positive = upward)
79
+ * @param numPoints - Number of interpolation points (default 20)
80
+ * @returns Array of points defining the arc path
81
+ *
82
+ * @example
83
+ * const dancer1 = { x: 100, y: 500, facing: 0 };
84
+ * const dancer2 = { x: 900, y: 500, facing: 0 };
85
+ * const arcPath = calculateSwapArcPath(dancer1, dancer2);
86
+ * // Returns 20 points forming smooth arc from (100,500) to (900,500)
87
+ * // with peak at approximately (500, 420) for 80-unit arc height (upward)
88
+ *
89
+ * @example
90
+ * // Custom arc height for longer distances
91
+ * const dancer1 = { x: 100, y: 100, facing: 0 };
92
+ * const dancer2 = { x: 900, y: 900, facing: 0 };
93
+ * const arcPath = calculateSwapArcPath(dancer1, dancer2, 120);
94
+ * // Higher arc for longer distance swap
95
+ */
96
+ export declare function calculateSwapArcPath(pos1: DancerPosition, pos2: DancerPosition, arcHeight?: number, numPoints?: number): Point[];
97
+ /**
98
+ * Calculate optimal arc height based on distance between positions
99
+ *
100
+ * This provides a visually pleasing arc that scales with the swap distance.
101
+ * Longer swaps get proportionally higher arcs.
102
+ *
103
+ * Formula: arcHeight = min(distance × 0.15, maxHeight)
104
+ *
105
+ * @param pos1 - Start position
106
+ * @param pos2 - End position
107
+ * @param maxHeight - Maximum arc height (default 150 Stage units)
108
+ * @returns Optimal arc height in Stage units
109
+ *
110
+ * @example
111
+ * const close = { x: 100, y: 100, facing: 0 };
112
+ * const nearby = { x: 150, y: 100, facing: 0 };
113
+ * calculateOptimalArcHeight(close, nearby); // → ~7.5 (15% of 50 units)
114
+ *
115
+ * @example
116
+ * const far1 = { x: 100, y: 100, facing: 0 };
117
+ * const far2 = { x: 900, y: 900, facing: 0 };
118
+ * calculateOptimalArcHeight(far1, far2); // → 150 (clamped to max)
119
+ */
120
+ export declare function calculateOptimalArcHeight(pos1: DancerPosition, pos2: DancerPosition, maxHeight?: number): number;
121
+ /**
122
+ * Calculate the length of a Bezier curve using numerical approximation
123
+ *
124
+ * Uses the same interpolation as the path generation to ensure consistency.
125
+ * This is useful for timing calculations in animations.
126
+ *
127
+ * @param pos1 - Start position
128
+ * @param pos2 - End position
129
+ * @param arcHeight - Arc height
130
+ * @param numSamples - Number of samples for length calculation (default 20)
131
+ * @returns Approximate arc length in Stage units
132
+ *
133
+ * @example
134
+ * const start = { x: 100, y: 100, facing: 0 };
135
+ * const end = { x: 200, y: 100, facing: 0 };
136
+ * const length = calculateArcLength(start, end, 50);
137
+ * // Returns approximate arc length (>100, <150 due to arc)
138
+ */
139
+ export declare function calculateArcLength(pos1: DancerPosition, pos2: DancerPosition, arcHeight?: number, numSamples?: number): number;
140
+ /**
141
+ * Get position along arc at specific time parameter
142
+ *
143
+ * This is useful for animation where you need a specific point along the arc
144
+ * without generating the full path.
145
+ *
146
+ * @param pos1 - Start position
147
+ * @param pos2 - End position
148
+ * @param t - Time parameter [0, 1]
149
+ * @param arcHeight - Arc height
150
+ * @returns Point at parameter t along the arc
151
+ *
152
+ * @example
153
+ * const start = { x: 100, y: 100, facing: 0 };
154
+ * const end = { x: 200, y: 100, facing: 0 };
155
+ * const halfway = getArcPositionAtTime(start, end, 0.5, 50);
156
+ * // Returns point at 50% of arc (peak of curve)
157
+ */
158
+ export declare function getArcPositionAtTime(pos1: DancerPosition, pos2: DancerPosition, t: number, arcHeight?: number): Point;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Geometric utility functions
3
+ * Based on plan Phase 4.2
4
+ */
5
+ import type { Point, DancerPosition } from './types';
6
+ /**
7
+ * Calculate the geometric centroid (center) of a set of positions
8
+ * @param positions - Array of positions
9
+ * @returns Centroid point
10
+ */
11
+ export declare function calculateCentroid(positions: readonly DancerPosition[]): Point;
12
+ /**
13
+ * Scale positions around a centroid point
14
+ * Used by the spreader tool
15
+ * @param positions - Array of positions to scale
16
+ * @param scaleFactor - Scale factor (1.0 = no change, >1 = spread out, <1 = bring together)
17
+ * @param center - Center point (optional, will calculate if not provided)
18
+ * @returns Scaled positions
19
+ */
20
+ export declare function scaleAroundCenter(positions: readonly DancerPosition[], scaleFactor: number, center?: Point): DancerPosition[];
21
+ /**
22
+ * Check if a point is inside a polygon (for lasso selection)
23
+ * Uses ray casting algorithm
24
+ * @param point - Point to test
25
+ * @param polygon - Array of polygon vertices
26
+ * @returns True if point is inside polygon
27
+ */
28
+ export declare function pointInPolygon(point: Point, polygon: readonly Point[]): boolean;
29
+ /**
30
+ * Calculate distance between two points
31
+ * @param p1 - First point
32
+ * @param p2 - Second point
33
+ * @returns Distance between points
34
+ */
35
+ export declare function distance(p1: Point, p2: Point): number;
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Circle/Arc Arrangement Mathematical Operations
3
+ *
4
+ * Distributes dancers evenly around a circle or arc using polar coordinates
5
+ *
6
+ * Math: Polar to Cartesian Conversion (adapted for Stage Y-down coordinates)
7
+ * x = cx + r * cos(θ)
8
+ * y = cy + r * sin(θ)
9
+ *
10
+ * CRITICAL: Stage coordinates have Y-axis pointing DOWN
11
+ * In standard math, 0° points East and angles increase counterclockwise
12
+ * In Stage coords, 0° points North (up) and angles increase clockwise
13
+ * We adjust angles by -90° to align with Stage coordinate system
14
+ *
15
+ * Where:
16
+ * (cx, cy) = Center of circle
17
+ * r = Radius
18
+ * θ = Angle in radians (adjusted for Stage Y-down)
19
+ *
20
+ * Angular Distribution:
21
+ * θ_i = θ_start + (i / (n-1)) * (θ_end - θ_start) [for n > 1]
22
+ * θ_i = θ_start + i * (2π / n) [for full circle]
23
+ *
24
+ * CRITICAL: All coordinates in Stage space (0-1000 units)
25
+ * Performance: O(n) - Must complete in <5ms for 50 dancers
26
+ */
27
+ import type { DancerPosition, Point, CircleArrangementOptions } from './types';
28
+ /**
29
+ * Arrange dancers in a circle or arc formation
30
+ *
31
+ * Math: x = cx + r * cos(θ), y = cy + r * sin(θ)
32
+ * CRITICAL: Adapted for Stage Y-down coordinate system
33
+ *
34
+ * Handles edge cases:
35
+ * - 1 dancer: Places at start angle
36
+ * - 2 dancers: Places at start and end angles
37
+ * - 3+ dancers: Distributes evenly around arc
38
+ *
39
+ * Full circle mode:
40
+ * - Dancers evenly spaced around 360°
41
+ * - Last dancer does NOT overlap first dancer
42
+ *
43
+ * Arc mode:
44
+ * - Dancers distributed from start to end angle
45
+ * - If distributeEvenly=false, includes dancer at end angle
46
+ *
47
+ * @param positions - Current dancer positions (used for determining center)
48
+ * @param options - Circle arrangement configuration
49
+ * @returns New positions arranged in circle/arc
50
+ *
51
+ * @example
52
+ * // Full circle
53
+ * const circle = arrangeInCircle(currentPositions, {
54
+ * preset: 'full',
55
+ * radius: 300,
56
+ * faceDirection: 'center'
57
+ * });
58
+ *
59
+ * // Half circle (semicircle)
60
+ * const semicircle = arrangeInCircle(currentPositions, {
61
+ * preset: 'half',
62
+ * startAngle: 0, // Start at top
63
+ * faceDirection: 'outward'
64
+ * });
65
+ *
66
+ * // Custom arc (120°)
67
+ * const arc = arrangeInCircle(currentPositions, {
68
+ * preset: 'custom',
69
+ * arcAngle: 120,
70
+ * startAngle: 30,
71
+ * faceDirection: 'tangent'
72
+ * });
73
+ *
74
+ * // Custom center and radius
75
+ * const custom = arrangeInCircle(currentPositions, {
76
+ * preset: 'full',
77
+ * center: { x: 500, y: 500 },
78
+ * radius: 250,
79
+ * faceDirection: 90 // Face East
80
+ * });
81
+ */
82
+ export declare function arrangeInCircle(positions: readonly DancerPosition[], options?: CircleArrangementOptions): DancerPosition[];
83
+ /**
84
+ * Arrange dancers in concentric circles
85
+ * Useful for large groups - distributes dancers across multiple rings
86
+ *
87
+ * @param positions - Current dancer positions
88
+ * @param options - Concentric circle options
89
+ * @returns New positions in concentric circles
90
+ *
91
+ * @example
92
+ * // 20 dancers in 2 rings
93
+ * const concentric = arrangeInConcentricCircles(positions, {
94
+ * rings: 2,
95
+ * radiusStep: 100
96
+ * });
97
+ */
98
+ export declare function arrangeInConcentricCircles(positions: readonly DancerPosition[], options: {
99
+ rings: number;
100
+ radiusStep?: number;
101
+ innerRadius?: number;
102
+ center?: Point;
103
+ faceDirection?: 'center' | 'outward' | 'tangent' | 'counter-tangent' | number;
104
+ }): DancerPosition[];
105
+ /**
106
+ * Validate that circle arrangement won't exceed stage bounds
107
+ *
108
+ * @param positions - Positions to arrange
109
+ * @param options - Circle arrangement options
110
+ * @param stageWidth - Stage width
111
+ * @param stageHeight - Stage height
112
+ * @param margin - Safety margin from edges
113
+ * @returns True if arrangement is safe
114
+ */
115
+ export declare function isCircleArrangementSafe(positions: readonly DancerPosition[], options: CircleArrangementOptions, stageWidth?: number, stageHeight?: number, margin?: number): boolean;
116
+ /**
117
+ * Calculate arc parameters from three points (for UI interaction)
118
+ * User clicks/drags three points to define an arc
119
+ *
120
+ * @param p1 - First point (start of arc)
121
+ * @param p2 - Second point (point on arc)
122
+ * @param p3 - Third point (end of arc)
123
+ * @returns Center, radius, start angle, and arc angle or null if points are collinear
124
+ *
125
+ * @example
126
+ * // User drags arc through three points
127
+ * const arc = calculateArcFromThreePoints(
128
+ * { x: 100, y: 500 },
129
+ * { x: 500, y: 200 },
130
+ * { x: 900, y: 500 }
131
+ * );
132
+ * // Use arc.center, arc.radius, arc.startAngle, arc.arcAngle
133
+ */
134
+ export declare function calculateArcFromThreePoints(p1: Point, p2: Point, p3: Point): {
135
+ center: Point;
136
+ radius: number;
137
+ startAngle: number;
138
+ arcAngle: number;
139
+ } | null;
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Efficient collision detection for circular dancers
3
+ * Implements AABB broad phase and spatial hashing from math_scratchpad.md
4
+ */
5
+ import type { Point, DancerPosition } from './types';
6
+ export declare const DANCER_RADIUS = 20;
7
+ /**
8
+ * Axis-Aligned Bounding Box
9
+ */
10
+ export interface AABB {
11
+ minX: number;
12
+ maxX: number;
13
+ minY: number;
14
+ maxY: number;
15
+ }
16
+ /**
17
+ * Collision pair result
18
+ */
19
+ export interface CollisionPair {
20
+ id1: string;
21
+ id2: string;
22
+ distance: number;
23
+ penetration: number;
24
+ }
25
+ /**
26
+ * Dancer with ID for collision detection
27
+ */
28
+ export interface DancerWithId {
29
+ id: string;
30
+ position: DancerPosition;
31
+ }
32
+ /**
33
+ * Create AABB for a circular dancer
34
+ * Based on Proof 2: AABB Collision Detection in math_scratchpad.md
35
+ *
36
+ * @param position - Dancer position
37
+ * @param radius - Dancer radius (default: 20)
38
+ * @returns AABB bounding box
39
+ */
40
+ export declare function createAABB(position: Point, radius?: number): AABB;
41
+ /**
42
+ * Check if two AABBs overlap
43
+ * O(1) operation for fast broad-phase culling
44
+ *
45
+ * @param aabb1 - First bounding box
46
+ * @param aabb2 - Second bounding box
47
+ * @returns True if AABBs overlap
48
+ */
49
+ export declare function checkAABBOverlap(aabb1: AABB, aabb2: AABB): boolean;
50
+ /**
51
+ * Check if two circles collide (narrow phase)
52
+ * Only call this after AABB overlap test passes
53
+ *
54
+ * @param pos1 - First circle center
55
+ * @param pos2 - Second circle center
56
+ * @param radius1 - First circle radius (default: 20)
57
+ * @param radius2 - Second circle radius (default: 20)
58
+ * @returns True if circles overlap
59
+ */
60
+ export declare function checkCircleCollision(pos1: Point, pos2: Point, radius1?: number, radius2?: number): boolean;
61
+ /**
62
+ * Calculate exact distance between two points
63
+ *
64
+ * @param pos1 - First point
65
+ * @param pos2 - Second point
66
+ * @returns Euclidean distance
67
+ */
68
+ export declare function calculateDistance(pos1: Point, pos2: Point): number;
69
+ /**
70
+ * Detect all collisions between dancers (naive O(N²) algorithm)
71
+ * Use this for small dancer counts (< 50)
72
+ *
73
+ * @param dancers - Array of dancer IDs and positions
74
+ * @param radius - Dancer radius (default: 20)
75
+ * @returns Array of collision pairs
76
+ */
77
+ export declare function detectCollisions(dancers: readonly DancerWithId[], radius?: number): CollisionPair[];
78
+ /**
79
+ * Spatial hash grid for efficient collision detection (O(N) average case)
80
+ * Use this for large dancer counts (>= 50)
81
+ *
82
+ * Based on spatial hashing algorithm from math_scratchpad.md
83
+ */
84
+ export declare class SpatialHashGrid {
85
+ private readonly radius;
86
+ private readonly cellSize;
87
+ private grid;
88
+ /**
89
+ * Create a new spatial hash grid
90
+ *
91
+ * @param radius - Dancer radius (default: 20)
92
+ * @param cellSize - Grid cell size (default: 2 * radius = 40)
93
+ */
94
+ constructor(radius?: number, cellSize?: number);
95
+ /**
96
+ * Compute hash key for a position
97
+ *
98
+ * @param x - X coordinate
99
+ * @param y - Y coordinate
100
+ * @returns Grid cell key
101
+ */
102
+ private hashKey;
103
+ /**
104
+ * Insert a dancer into the spatial grid
105
+ *
106
+ * @param id - Dancer ID
107
+ * @param position - Dancer position
108
+ */
109
+ insert(id: string, position: DancerPosition): void;
110
+ /**
111
+ * Get all dancers in the same or adjacent cells
112
+ *
113
+ * @param position - Query position
114
+ * @returns Array of nearby dancers
115
+ */
116
+ getNearby(position: DancerPosition): DancerWithId[];
117
+ /**
118
+ * Clear all entries from the grid
119
+ */
120
+ clear(): void;
121
+ /**
122
+ * Detect all collisions using spatial hashing
123
+ *
124
+ * @param dancers - Array of dancer IDs and positions
125
+ * @returns Array of collision pairs
126
+ */
127
+ detectCollisions(dancers: readonly DancerWithId[]): CollisionPair[];
128
+ /**
129
+ * Get statistics about the grid (for debugging/optimization)
130
+ *
131
+ * @returns Grid statistics
132
+ */
133
+ getStats(): {
134
+ totalCells: number;
135
+ occupiedCells: number;
136
+ totalDancers: number;
137
+ avgDancersPerCell: number;
138
+ maxDancersInCell: number;
139
+ };
140
+ }
141
+ /**
142
+ * Auto-select collision detection algorithm based on dancer count
143
+ * Uses spatial hashing for N >= 50, naive algorithm otherwise
144
+ *
145
+ * @param dancers - Array of dancer IDs and positions
146
+ * @param radius - Dancer radius (default: 20)
147
+ * @returns Array of collision pairs
148
+ */
149
+ export declare function detectCollisionsAuto(dancers: readonly DancerWithId[], radius?: number): CollisionPair[];
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function M(t){if(t.length===0)return{x:0,y:0};const n=t.reduce((r,e)=>({x:r.x+e.x,y:r.y+e.y}),{x:0,y:0});return{x:n.x/t.length,y:n.y/t.length}}function J(t,n,r){const e=r||M(t);return t.map(c=>({x:e.x+(c.x-e.x)*n,y:e.y+(c.y-e.y)*n,facing:c.facing}))}function Q(t,n){let r=!1;for(let e=0,c=n.length-1;e<n.length;c=e++){const a=n[e].x,i=n[e].y,s=n[c].x,o=n[c].y;i>t.y!=o>t.y&&t.x<(s-a)*(t.y-i)/(o-i)+a&&(r=!r)}return r}function W(t,n){const r=n.x-t.x,e=n.y-t.y;return Math.sqrt(r*r+e*e)}function k(t,n,r,e,c=0){if(t.length===0)return[];const a=e||M(t),i=c*Math.PI/180,s=Math.cos(i),o=Math.sin(i);return t.map(l=>{let u=l.x-a.x,x=l.y-a.y;if(c!==0){const h=u*s-x*o,f=u*o+x*s;u=h,x=f}if(u*=n,x*=r,c!==0){const h=u*s+x*o,f=-u*o+x*s;u=h,x=f}return{x:u+a.x,y:x+a.y,facing:l.facing}})}function Z(t,n){const{direction:r,scaleFactor:e,center:c,aspectRatio:a=1,angle:i=0}=n;let s,o,l;switch(r){case"radial":s=e,o=e,l=0;break;case"horizontal":s=e,o=1,l=0;break;case"vertical":s=1,o=e,l=0;break;case"diagonal":s=e,o=e,l=i||45;break;default:throw new Error(`Unknown spread direction: ${r}`)}return a!==1&&(s*=a),k(t,s,o,c,l)}function p(t,n,r){if(t.length===0)return 1;const e=r||M(t),a=t.reduce((i,s)=>{const o=s.x-e.x,l=s.y-e.y;return i+Math.sqrt(o*o+l*l)},0)/t.length;return a===0?1:n/a}function tt(t){if(t.length===0)return{minX:0,maxX:0,minY:0,maxY:0,width:0,height:0};let n=1/0,r=-1/0,e=1/0,c=-1/0;return t.forEach(a=>{n=Math.min(n,a.x),r=Math.max(r,a.x),e=Math.min(e,a.y),c=Math.max(c,a.y)}),{minX:n,maxX:r,minY:e,maxY:c,width:r-n,height:c-e}}function nt(t,n,r,e,c){if(t.length===0)return!0;const a=c||M(t);return t.every(i=>{const s=i.x-a.x,o=i.y-a.y,l=a.x+s*n,u=a.y+o*n,x=20;return l>=x&&l<=r-x&&u>=x&&u<=e-x})}function j(t,n,r){if(t.length===0)throw new Error("Cannot arrange line with 0 dancers");const e=M(t),a=(r||Math.max(400,t.length*50))/2;switch(n){case"horizontal":return{start:{x:e.x-a,y:e.y},end:{x:e.x+a,y:e.y}};case"vertical":return{start:{x:e.x,y:e.y-a},end:{x:e.x,y:e.y+a}};case"diagonal-45":return{start:{x:e.x-a*Math.cos(Math.PI/4),y:e.y-a*Math.sin(Math.PI/4)},end:{x:e.x+a*Math.cos(Math.PI/4),y:e.y+a*Math.sin(Math.PI/4)}};case"diagonal-135":return{start:{x:e.x-a*Math.cos(3*Math.PI/4),y:e.y-a*Math.sin(3*Math.PI/4)},end:{x:e.x+a*Math.cos(3*Math.PI/4),y:e.y+a*Math.sin(3*Math.PI/4)}};default:throw new Error(`Unknown line orientation: ${n}`)}}function q(t,n,r,e){if(typeof e=="number")return e;const c=Math.atan2(r.y,r.x)*(180/Math.PI);switch(e){case"auto":case"forward":return(c+90)%360;case"backward":return(c+270)%360;case"center":return t<n/2?(c+90)%360:(c+270)%360;default:return 0}}function O(t,n){const r=t.length;if(r===0)return[];const{orientation:e,spacing:c,startPoint:a,endPoint:i,faceDirection:s="auto",lineLength:o}=n;let l,u;if(a&&i)l=a,u=i;else if(c&&r>1){const h=c*(r-1),f=M(t),g=h/2,d=j(t,e,h),y=d.end.x-d.start.x,A=d.end.y-d.start.y,m=Math.sqrt(y*y+A*A);if(m===0)throw new Error("Invalid line orientation");const I=y/m*g,P=A/m*g;l={x:f.x-I,y:f.y-P},u={x:f.x+I,y:f.y+P}}else{const h=j(t,e,o);l=h.start,u=h.end}const x={x:u.x-l.x,y:u.y-l.y};return r===1?[{x:(l.x+u.x)/2,y:(l.y+u.y)/2,facing:q(0,1,x,s)}]:t.map((h,f)=>{const g=f/(r-1),d=l.x+g*(u.x-l.x),y=l.y+g*(u.y-l.y);return{x:d,y,facing:q(f,r,x,s)}})}function et(t,n,r=!1){const e=n.x-t.x,c=n.y-t.y,i=(Math.atan2(c,e)*(180/Math.PI)%360+360)%360;let s,o=t,l=n;if(r)if(i>=337.5||i<22.5){s="horizontal";const u=(t.y+n.y)/2;o={x:t.x,y:u},l={x:n.x,y:u}}else if(i>=22.5&&i<67.5)s="diagonal-45";else if(i>=67.5&&i<112.5){s="vertical";const u=(t.x+n.x)/2;o={x:u,y:t.y},l={x:u,y:n.y}}else if(i>=112.5&&i<157.5)s="diagonal-135";else if(i>=157.5&&i<202.5){s="horizontal";const u=(t.y+n.y)/2;o={x:t.x,y:u},l={x:n.x,y:u}}else if(i>=202.5&&i<247.5)s="diagonal-45";else if(i>=247.5&&i<292.5){s="vertical";const u=(t.x+n.x)/2;o={x:u,y:t.y},l={x:u,y:n.y}}else s="diagonal-135";else Math.abs(e)>Math.abs(c)*2?s="horizontal":Math.abs(c)>Math.abs(e)*2?s="vertical":e*c>0?s="diagonal-45":s="diagonal-135";return{orientation:s,start:o,end:l}}function rt(t,n,r=1e3,e=1e3,c=20){return t.length===0?!0:O(t,n).every(i=>i.x>=c&&i.x<=r-c&&i.y>=c&&i.y<=e-c)}function Y(t){return t*Math.PI/180}function S(t){return t*180/Math.PI}function ct(t){switch(t){case"full":return 360;case"half":return 180;case"quarter":return 90;case"three-quarter":return 270;case"custom":return 180;default:return 360}}function at(t,n,r,e){if(typeof e=="number")return e;switch(e){case"center":{const c=n.x-t.x,a=n.y-t.y,i=Math.atan2(-a,c);return((S(i)+90)%360+360)%360}case"outward":{const c=t.x-n.x,a=t.y-n.y,i=Math.atan2(-a,c);return((S(i)+90)%360+360)%360}case"tangent":{const c=r+Math.PI/2;return((S(c)+90)%360+360)%360}case"counter-tangent":{const c=r-Math.PI/2;return((S(c)+90)%360+360)%360}default:return 0}}function it(t,n,r=1e3,e=1e3,c=50){const a=Math.min(t.x-c,r-t.x-c),i=Math.min(t.y-c,e-t.y-c),s=Math.min(a,i),l=n*40/(2*Math.PI);return Math.min(s,l,400)}function v(t,n={}){const r=t.length;if(r===0)return[];const{preset:e="full",arcAngle:c,startAngle:a=0,radius:i,center:s,faceDirection:o="center",distributeEvenly:l=!0}=n,u=s||M(t),x=c!==void 0?c:ct(e),h=i!==void 0?i:it(u,r);if(h<=0)throw new Error("Circle radius must be positive");const f=Y(a-90),g=Math.abs(x%360)<.001;let d;if(r===1)d=[f];else if(g){const y=2*Math.PI/r;d=Array.from({length:r},(A,m)=>f+m*y)}else if(l){const y=Y(x);d=Array.from({length:r},(A,m)=>{const I=m/(r-1);return f+I*y})}else{const A=Y(x)/r;d=Array.from({length:r},(m,I)=>f+I*A)}return d.map(y=>{const A=u.x+h*Math.cos(y),m=u.y+h*Math.sin(y);return{x:A,y:m,facing:at({x:A,y:m},u,y,o)}})}function st(t,n){const r=t.length;if(r===0)return[];const{rings:e,radiusStep:c=100,innerRadius:a=150,center:i,faceDirection:s="center"}=n;if(e<1)throw new Error("Must have at least 1 ring");const o=i||M(t),l=Math.ceil(r/e),u=[];let x=0;for(let h=0;h<e&&x<r;h++){const f=a+h*c,g=Math.min(l,r-x),d=Array.from({length:g},()=>({x:0,y:0,facing:0})),y=v(d,{preset:"full",radius:f,center:o,faceDirection:s});u.push(...y),x+=g}return u}function ot(t,n,r=1e3,e=1e3,c=20){return t.length===0?!0:v(t,n).every(i=>i.x>=c&&i.x<=r-c&&i.y>=c&&i.y<=e-c)}function lt(t,n,r){const e=t.x,c=t.y,a=n.x,i=n.y,s=r.x,o=r.y,l=2*(e*(i-o)+a*(o-c)+s*(c-i));if(Math.abs(l)<.001)return null;const u=((e*e+c*c)*(i-o)+(a*a+i*i)*(o-c)+(s*s+o*o)*(c-i))/l,x=((e*e+c*c)*(s-a)+(a*a+i*i)*(e-s)+(s*s+o*o)*(a-e))/l,h={x:u,y:x},f=t.x-h.x,g=t.y-h.y,d=Math.sqrt(f*f+g*g),y=Math.atan2(-(t.y-h.y),t.x-h.x);let m=Math.atan2(-(r.y-h.y),r.x-h.x)-y;m<0&&(m+=2*Math.PI);const I=(S(y)+90+360)%360,P=S(m);return{center:h,radius:d,startAngle:I,arcAngle:P}}function ut(t){return(360-t)%360}function xt(t){return(180-t+360)%360}function w(t,n){if(t.length===0)return[];const{axis:e,axisPosition:c,center:a,mirrorFacing:i=!0}=n;let s;if(c!==void 0)s=c;else if(a)s=e==="horizontal"?a.x:a.y;else{const o=M(t);s=e==="horizontal"?o.x:o.y}return t.map(o=>{let l=o.x,u=o.y,x=o.facing;return e==="horizontal"?(l=2*s-o.x,i&&(x=ut(o.facing))):(u=2*s-o.y,i&&(x=xt(o.facing))),{x:l,y:u,facing:x}})}function ht(t,n){const r=w(t,n);return[...t,...r]}function N(t,n,r=!0){if(t.length===0)return[];const c=n||M(t);return t.map(a=>{const i=2*c.x-a.x,s=2*c.y-a.y,o=r?(a.facing+180)%360:a.facing;return{x:i,y:s,facing:o}})}function ft(t,n){const r=n||M(t),e=t,c=w(t,{axis:"horizontal",center:r}),a=w(t,{axis:"vertical",center:r}),i=N(t,r);return[...e,...c,...a,...i]}function yt(t,n,r=1e3,e=1e3,c=20){return t.length===0?!0:w(t,n).every(i=>i.x>=c&&i.x<=r-c&&i.y>=c&&i.y<=e-c)}function dt(t,n){const r=Math.abs(n.x-t.x),e=Math.abs(n.y-t.y);return r<e?{axis:"horizontal",axisPosition:(t.x+n.x)/2}:{axis:"vertical",axisPosition:(t.y+n.y)/2}}function gt(t,n,r=10){const e=[],c=new Set;for(let a=0;a<t.length;a++){if(c.has(a))continue;const i=w([t[a]],n)[0];for(let s=a+1;s<t.length;s++){if(c.has(s))continue;const o=t[s].x-i.x,l=t[s].y-i.y;if(Math.sqrt(o*o+l*l)<r){e.push([a,s]),c.add(a),c.add(s);break}}}return e}function mt(t){return t*Math.PI/180}function Mt(t){return(t%360+360)%360}function At(t,n,r){const e=Math.cos(r),c=Math.sin(r),a=t.x-n.x,i=t.y-n.y;return{x:n.x+a*e-i*c,y:n.y-(a*c-i*e)}}function b(t,n){if(t.length===0)return[];const{angle:e,center:c,rotateFacing:a=!0}=n,i=c||M(t),s=mt(e);return t.map(o=>{const l=At({x:o.x,y:o.y},i,s);return{x:l.x,y:l.y,facing:a?Mt(o.facing+e):o.facing}})}function It(t,n,r){return b(t,{angle:n,center:r})}function Rt(t,n,r){if(n<1)throw new Error("Number of copies must be at least 1");if(t.length===0)return[];const e=r||M(t),c=360/n,a=[];for(let i=0;i<n;i++){const s=i*c,o=b(t,{angle:s,center:e});a.push(...o)}return a}function St(t,n,r,e,c){if(e<2)throw new Error("Animation requires at least 2 steps");const a=c||M(t),i=[];for(let s=0;s<e;s++){const o=s/(e-1),l=n+o*(r-n),u=b(t,{angle:l,center:a});i.push(u)}return i}function wt(t,n,r){const e=Math.atan2(-(n.y-t.y),n.x-t.x);let a=Math.atan2(-(r.y-t.y),r.x-t.x)-e;return a>Math.PI?a-=2*Math.PI:a<-Math.PI&&(a+=2*Math.PI),a*180/Math.PI}function Ct(t,n=45){return Math.round(t/n)*n}function bt(t,n,r=1e3,e=1e3,c=20){return t.length===0?!0:b(t,n).every(i=>i.x>=c&&i.x<=r-c&&i.y>=c&&i.y<=e-c)}function Pt(t){if(t.length===0)return 0;let n=1/0,r=-1/0,e=1/0,c=-1/0;t.forEach(s=>{n=Math.min(n,s.x),r=Math.max(r,s.x),e=Math.min(e,s.y),c=Math.max(c,s.y)});const a=r-n,i=c-e;return Math.sqrt(a*a+i*i)}function Xt(t,n=1e3,r=1e3,e=20){const c=t.x-e,a=n-t.x-e,i=t.y-e,s=r-t.y-e;return Math.min(c,a,i,s)}function z(t){if(t.length<2)return{minX:0,maxX:0,minY:0,maxY:0};let n=1/0,r=-1/0,e=1/0,c=-1/0;for(let a=0;a<t.length;a+=2){const i=t[a],s=t[a+1];n=Math.min(n,i),r=Math.max(r,i),e=Math.min(e,s),c=Math.max(c,s)}return{minX:n,maxX:r,minY:e,maxY:c}}function Yt(t,n){if(n.length<6)return!1;let r=!1;const e=n.length/2;for(let c=0,a=e-1;c<e;a=c++){const i=n[c*2],s=n[c*2+1],o=n[a*2],l=n[a*2+1];s>t.y!=l>t.y&&t.x<(o-i)*(t.y-s)/(l-s)+i&&(r=!r)}return r}function $(t,n,r){if(n.length<6)return!1;const e=r||z(n);if(t.x<e.minX||t.x>e.maxX||t.y<e.minY||t.y>e.maxY)return!1;let c=!1;const a=n.length/2;for(let i=0,s=a-1;i<a;s=i++){const o=n[i*2],l=n[i*2+1],u=n[s*2],x=n[s*2+1];if(l===x)continue;l>t.y!=x>t.y&&t.x<(u-o)*(t.y-l)/(x-l)+o&&(c=!c)}return c}function vt(t,n){if(n.length<6)return t.map(()=>!1);const r=z(n);return t.map(e=>$(e,n,r))}function zt(t){if(t.length<6)return 0;let n=0;const r=t.length/2;for(let e=0;e<r;e++){const c=(e+1)%r,a=t[e*2],i=t[e*2+1],s=t[c*2],o=t[c*2+1];n+=a*o-s*i}return n/2}function Bt(t){if(t.length<6)return{x:0,y:0};let n=0,r=0,e=0;const c=t.length/2;for(let a=0;a<c;a++){const i=(a+1)%c,s=t[a*2],o=t[a*2+1],l=t[i*2],u=t[i*2+1],x=s*u-l*o;n+=(s+l)*x,r+=(o+u)*x,e+=x}if(e/=2,Math.abs(e)<1e-4){let a=0,i=0;for(let s=0;s<t.length;s+=2)a+=t[s],i+=t[s+1];return{x:a/c,y:i/c}}return{x:n/(6*e),y:r/(6*e)}}function Ft(t,n){if(t.length<6)return[...t];const r=[];r.push(t[0],t[1]);for(let e=2;e<t.length;e+=2){const c=r[r.length-2],a=r[r.length-1],i=t[e],s=t[e+1],o=i-c,l=s-a;Math.sqrt(o*o+l*l)>=n&&r.push(i,s)}return r}const B=80,U=20;function F(t,n,r,e,c){const a=t*t,i=a*t,s=1-t,o=s*s,u=o*s,x=3*o*t,h=3*s*a,f=i;return{x:u*n.x+x*r.x+h*e.x+f*c.x,y:u*n.y+x*r.y+h*e.y+f*c.y}}function T(t,n,r){const e=(t.x+n.x)/2,c=(t.y+n.y)/2,a=n.x-t.x,s=-(n.y-t.y),o=a,l=Math.sqrt(s*s+o*o);if(l<1e-4){const y={x:e,y:c};return[y,y]}const u=s/l,x=o/l,h=-u*r,f=-x*r,g={x:e+h,y:c+f},d={x:e+h,y:c+f};return[g,d]}function _(t,n,r=B,e=U){if(e<2)throw new Error("Number of interpolation points must be at least 2");const[c,a]=T(t,n,r),i=[];for(let s=0;s<e;s++){const o=s/(e-1),l=F(o,{x:t.x,y:t.y},c,a,{x:n.x,y:n.y});i.push(l)}return i}function Tt(t,n,r=150){const e=n.x-t.x,c=n.y-t.y,i=Math.sqrt(e*e+c*c)*.15;return Math.min(i,r)}function Dt(t,n,r=B,e=U){const c=_(t,n,r,e);let a=0;for(let i=1;i<c.length;i++){const s=c[i].x-c[i-1].x,o=c[i].y-c[i-1].y;a+=Math.sqrt(s*s+o*o)}return a}function Et(t,n,r,e=B){const c=Math.max(0,Math.min(1,r)),[a,i]=T(t,n,e);return F(c,{x:t.x,y:t.y},a,i,{x:n.x,y:n.y})}function X(t,n,r,e,c){const a=c*c,i=a*c,s=.5*(2*n.x+(-t.x+r.x)*c+(2*t.x-5*n.x+4*r.x-e.x)*a+(-t.x+3*n.x-3*r.x+e.x)*i),o=.5*(2*n.y+(-t.y+r.y)*c+(2*t.y-5*n.y+4*r.y-e.y)*a+(-t.y+3*n.y-3*r.y+e.y)*i);return{x:s,y:o}}function Lt(t,n=20){if(t.length<2)return[...t];if(t.length===2)return[t[0],t[1]];const r=[];for(let e=0;e<t.length-1;e++){const c=e===0?t[0]:t[e-1],a=t[e],i=t[e+1],s=e===t.length-2?t[e+1]:t[e+2];for(let o=0;o<n;o++){const l=o/n,u=X(c,a,i,s,l);r.push(u)}}return r.push(t[t.length-1]),r}function H(t,n,r,e){const c={x:n.x+(r.x-t.x)/6,y:n.y+(r.y-t.y)/6},a={x:r.x-(e.x-n.x)/6,y:r.y-(e.y-n.y)/6};return{start:n,control1:c,control2:a,end:r}}function jt(t){if(t.length<2)return[];const n=[];for(let r=0;r<t.length-1;r++){const e=r===0?t[0]:t[r-1],c=t[r],a=t[r+1],i=r===t.length-2?t[r+1]:t[r+2];n.push(H(e,c,a,i))}return n}function K(t,n,r,e,c){const a=c*c,i=.5*(-t.x+r.x+2*(2*t.x-5*n.x+4*r.x-e.x)*c+3*(-t.x+3*n.x-3*r.x+e.x)*a),s=.5*(-t.y+r.y+2*(2*t.y-5*n.y+4*r.y-e.y)*c+3*(-t.y+3*n.y-3*r.y+e.y)*a);return{x:i,y:s}}function qt(t,n,r,e,c){const a=K(t,n,r,e,c);return Math.atan2(a.y,a.x)*180/Math.PI}function kt(t,n,r,e,c=100){let a=0,i=n;for(let s=1;s<=c;s++){const o=s/c,l=X(t,n,r,e,o),u=l.x-i.x,x=l.y-i.y;a+=Math.sqrt(u*u+x*x),i=l}return a}function Ot(t,n){if(t.length<2)return[...t];const r=[t[0]];let e=0,c=n;for(let a=0;a<t.length-1;a++){const i=a===0?t[0]:t[a-1],s=t[a],o=t[a+1],l=a===t.length-2?t[a+1]:t[a+2];let u=s;const x=100;for(let h=1;h<=x;h++){const f=h/x,g=X(i,s,o,l,f),d=g.x-u.x,y=g.y-u.y,A=Math.sqrt(d*d+y*y);e+=A,e>=c&&(r.push(g),c+=n),u=g}}return r}const R=20;function C(t,n=R){return{minX:t.x-n,maxX:t.x+n,minY:t.y-n,maxY:t.y+n}}function D(t,n){return t.maxX>=n.minX&&t.minX<=n.maxX&&t.maxY>=n.minY&&t.minY<=n.maxY}function E(t,n,r=R,e=R){const c=n.x-t.x,a=n.y-t.y,i=c*c+a*a,s=r+e;return i<s*s}function L(t,n){const r=n.x-t.x,e=n.y-t.y;return Math.sqrt(r*r+e*e)}function V(t,n=R){const r=[];for(let e=0;e<t.length;e++)for(let c=e+1;c<t.length;c++){const a=t[e],i=t[c],s=C(a.position,n),o=C(i.position,n);if(D(s,o)&&E(a.position,i.position,n,n)){const l=L(a.position,i.position),u=n*2-l;r.push({id1:a.id,id2:i.id,distance:l,penetration:u})}}return r}class G{constructor(n=R,r){this.radius=n,this.cellSize=r||n*2,this.grid=new Map}hashKey(n,r){const e=Math.floor(n/this.cellSize),c=Math.floor(r/this.cellSize);return`${e},${c}`}insert(n,r){const e=this.hashKey(r.x,r.y);this.grid.has(e)||this.grid.set(e,[]),this.grid.get(e).push({id:n,position:r})}getNearby(n){const r=[],e=Math.floor(n.x/this.cellSize),c=Math.floor(n.y/this.cellSize);for(let a=-1;a<=1;a++)for(let i=-1;i<=1;i++){const s=`${e+a},${c+i}`,o=this.grid.get(s);o&&r.push(...o)}return r}clear(){this.grid.clear()}detectCollisions(n){this.clear(),n.forEach(c=>this.insert(c.id,c.position));const r=[],e=new Set;return n.forEach(c=>{this.getNearby(c.position).forEach(i=>{if(c.id===i.id)return;const s=c.id<i.id?`${c.id}:${i.id}`:`${i.id}:${c.id}`;if(e.has(s))return;e.add(s);const o=C(c.position,this.radius),l=C(i.position,this.radius);if(D(o,l)&&E(c.position,i.position,this.radius,this.radius)){const u=L(c.position,i.position),x=this.radius*2-u;r.push({id1:c.id,id2:i.id,distance:u,penetration:x})}})}),r}getStats(){const n=this.grid.size;let r=0,e=0;return this.grid.forEach(c=>{r+=c.length,e=Math.max(e,c.length)}),{totalCells:this.grid.size*9,occupiedCells:n,totalDancers:r,avgDancersPerCell:n>0?r/n:0,maxDancersInCell:e}}}function Nt(t,n=R){return t.length<50?V(t,n):new G(n).detectCollisions(t)}const $t="1.0.0";exports.DANCER_RADIUS=R;exports.SpatialHashGrid=G;exports.VERSION=$t;exports.animateRotation=St;exports.arrangeInCircle=v;exports.arrangeInConcentricCircles=st;exports.arrangeInLine=O;exports.batchPointInPolygon=vt;exports.calculateArcControlPoints=T;exports.calculateArcFromThreePoints=lt;exports.calculateArcLength=Dt;exports.calculateCentroid=M;exports.calculateDistance=L;exports.calculateLineFromDrag=et;exports.calculateMirrorAxisFromDrag=dt;exports.calculateOptimalArcHeight=Tt;exports.calculateRotationFromDrag=wt;exports.calculateSpreadFactorForDistance=p;exports.calculateSwapArcPath=_;exports.catmullRomAngle=qt;exports.catmullRomArcLength=kt;exports.catmullRomPoint=X;exports.catmullRomSpline=Lt;exports.catmullRomSplineToBezier=jt;exports.catmullRomTangent=K;exports.catmullRomToBezier=H;exports.catmullRomUniformSample=Ot;exports.checkAABBOverlap=D;exports.checkCircleCollision=E;exports.computeBoundingBox=z;exports.createAABB=C;exports.createKaleidoscope=ft;exports.createRotationalCopies=Rt;exports.cubicBezier=F;exports.detectCollisions=V;exports.detectCollisionsAuto=Nt;exports.directionalSpread=Z;exports.distance=W;exports.ellipticalScale=k;exports.findSymmetricPairs=gt;exports.getArcPositionAtTime=Et;exports.getBoundingBox=tt;exports.getBoundingBoxDiagonal=Pt;exports.getMaxRotationRadius=Xt;exports.isCircleArrangementSafe=ot;exports.isLineArrangementSafe=rt;exports.isMirrorSafe=yt;exports.isPointInPolygon=Yt;exports.isPointInPolygonOptimized=$;exports.isRotationSafe=bt;exports.isSpreadSafe=nt;exports.mirrorAndDuplicate=ht;exports.mirrorBothAxes=N;exports.mirrorFormation=w;exports.pointInPolygon=Q;exports.polygonArea=zt;exports.polygonCentroid=Bt;exports.rotateFormation=b;exports.rotateFormationPreset=It;exports.scaleAroundCenter=J;exports.simplifyPolygon=Ft;exports.snapRotationAngle=Ct;
@@ -0,0 +1,16 @@
1
+ export type { Point, DancerPosition, SpreadOptions, LineArrangementOptions, CircleArrangementOptions, RotationOptions, MirrorOptions, BezierControlPoint, BoundingBox, } from './types';
2
+ export { calculateCentroid, scaleAroundCenter, pointInPolygon, distance, } from './centroid';
3
+ export { ellipticalScale, directionalSpread, calculateSpreadFactorForDistance, getBoundingBox, isSpreadSafe, } from './spreader';
4
+ export { arrangeInLine, calculateLineFromDrag, isLineArrangementSafe, } from './lineArrangement';
5
+ export { arrangeInCircle, arrangeInConcentricCircles, isCircleArrangementSafe, calculateArcFromThreePoints, } from './circleArrangement';
6
+ export { mirrorFormation, mirrorAndDuplicate, mirrorBothAxes, createKaleidoscope, isMirrorSafe, calculateMirrorAxisFromDrag, findSymmetricPairs, } from './mirrorFlip';
7
+ export type { RotationPreset, RotateOptions } from './rotateFormation';
8
+ export { rotateFormation, rotateFormationPreset, createRotationalCopies, animateRotation, calculateRotationFromDrag, snapRotationAngle, isRotationSafe, getBoundingBoxDiagonal, getMaxRotationRadius, } from './rotateFormation';
9
+ export type { PolygonBoundingBox } from './polygon';
10
+ export { computeBoundingBox, isPointInPolygon, isPointInPolygonOptimized, batchPointInPolygon, polygonArea, polygonCentroid, simplifyPolygon, } from './polygon';
11
+ export { cubicBezier, calculateArcControlPoints, calculateSwapArcPath, calculateOptimalArcHeight, calculateArcLength, getArcPositionAtTime, } from './bezier';
12
+ export type { BezierControlPoints } from './spline';
13
+ export { catmullRomPoint, catmullRomSpline, catmullRomToBezier, catmullRomSplineToBezier, catmullRomTangent, catmullRomAngle, catmullRomArcLength, catmullRomUniformSample, } from './spline';
14
+ export type { AABB, CollisionPair, DancerWithId } from './collision';
15
+ export { DANCER_RADIUS, createAABB, checkAABBOverlap, checkCircleCollision, calculateDistance, detectCollisions, SpatialHashGrid, detectCollisionsAuto, } from './collision';
16
+ export declare const VERSION = "1.0.0";