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.
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Line Arrangement Mathematical Operations
3
+ *
4
+ * Distributes dancers evenly along a line (horizontal, vertical, or diagonal)
5
+ *
6
+ * Math: Linear Interpolation (LERP)
7
+ * P_i = P_start + (i / (n-1)) * (P_end - P_start)
8
+ *
9
+ * Where:
10
+ * P_i = Position of dancer i
11
+ * P_start = Start point of line
12
+ * P_end = End point of line
13
+ * n = Total number of dancers
14
+ * i = Index of current dancer (0 to n-1)
15
+ *
16
+ * CRITICAL: All coordinates in Stage space (0-1000 units)
17
+ * Performance: O(n) - Must complete in <5ms for 50 dancers
18
+ */
19
+ import type { DancerPosition, Point, LineOrientation, LineArrangementOptions } from './types';
20
+ /**
21
+ * Arrange dancers in a line formation
22
+ *
23
+ * Math: P_i = P_start + (i / (n-1)) * (P_end - P_start)
24
+ *
25
+ * Handles edge cases:
26
+ * - 1 dancer: Places at centroid of line
27
+ * - 2 dancers: Places at start and end
28
+ * - 3+ dancers: Evenly distributes along line
29
+ *
30
+ * @param positions - Current dancer positions (used for determining line placement)
31
+ * @param options - Line arrangement configuration
32
+ * @returns New positions arranged in a line
33
+ *
34
+ * @example
35
+ * // Horizontal line
36
+ * const newPositions = arrangeInLine(currentPositions, {
37
+ * orientation: 'horizontal',
38
+ * faceDirection: 'forward'
39
+ * });
40
+ *
41
+ * // Diagonal line with custom endpoints
42
+ * const diagonalPositions = arrangeInLine(currentPositions, {
43
+ * orientation: 'diagonal-45',
44
+ * startPoint: { x: 100, y: 100 },
45
+ * endPoint: { x: 900, y: 900 },
46
+ * faceDirection: 'center'
47
+ * });
48
+ *
49
+ * // Fixed spacing (overrides endpoints)
50
+ * const spacedPositions = arrangeInLine(currentPositions, {
51
+ * orientation: 'vertical',
52
+ * spacing: 50, // 50 Stage units between each dancer
53
+ * faceDirection: 90 // Face East
54
+ * });
55
+ */
56
+ export declare function arrangeInLine(positions: readonly DancerPosition[], options: LineArrangementOptions): DancerPosition[];
57
+ /**
58
+ * Calculate line endpoints from two dancer positions
59
+ * Useful for UI where user drags to define line
60
+ *
61
+ * @param point1 - First point (drag start)
62
+ * @param point2 - Second point (drag end)
63
+ * @param snapToOrientation - Optionally snap to nearest orientation
64
+ * @returns Orientation and adjusted endpoints
65
+ *
66
+ * @example
67
+ * // User drags from (100,100) to (500,150)
68
+ * const result = calculateLineFromDrag(
69
+ * { x: 100, y: 100 },
70
+ * { x: 500, y: 150 },
71
+ * true // Snap to nearest orientation
72
+ * );
73
+ * // result.orientation = 'horizontal' (nearly horizontal)
74
+ * // result.start = { x: 100, y: 125 }
75
+ * // result.end = { x: 500, y: 125 }
76
+ */
77
+ export declare function calculateLineFromDrag(point1: Point, point2: Point, snapToOrientation?: boolean): {
78
+ orientation: LineOrientation;
79
+ start: Point;
80
+ end: Point;
81
+ };
82
+ /**
83
+ * Validate that line arrangement won't exceed stage bounds
84
+ *
85
+ * @param positions - Positions to arrange
86
+ * @param options - Line arrangement options
87
+ * @param stageWidth - Stage width (default: 1000)
88
+ * @param stageHeight - Stage height (default: 1000)
89
+ * @param margin - Safety margin from edges (default: 20)
90
+ * @returns True if arrangement is safe
91
+ */
92
+ export declare function isLineArrangementSafe(positions: readonly DancerPosition[], options: LineArrangementOptions, stageWidth?: number, stageHeight?: number, margin?: number): boolean;
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Mirror/Flip Formation Mathematical Operations
3
+ *
4
+ * Reflects dancer positions across horizontal or vertical axis
5
+ *
6
+ * Math: Reflection Formulas
7
+ * Horizontal (across Y-axis): P'_x = 2*cx - P_x, P'_y = P_y
8
+ * Vertical (across X-axis): P'_x = P_x, P'_y = 2*cy - P_y
9
+ *
10
+ * Where:
11
+ * (cx, cy) = Reflection axis center (typically centroid)
12
+ * P = Original position
13
+ * P' = Reflected position
14
+ *
15
+ * Facing Direction:
16
+ * Horizontal flip: facing' = (360 - facing) % 360 [mirrors left/right]
17
+ * Vertical flip: facing' = (180 - facing + 360) % 360 [mirrors up/down]
18
+ *
19
+ * CRITICAL: All coordinates in Stage space (0-1000 units)
20
+ * Performance: O(n) - Must complete in <5ms for 50 dancers
21
+ */
22
+ import type { DancerPosition, Point, MirrorOptions } from './types';
23
+ /**
24
+ * Mirror/flip dancer positions across an axis
25
+ *
26
+ * Math:
27
+ * Horizontal: x' = 2*cx - x
28
+ * Vertical: y' = 2*cy - y
29
+ *
30
+ * Handles edge cases:
31
+ * - 0 dancers: Returns empty array
32
+ * - 1 dancer: Mirrors across centroid (results in same position if at center)
33
+ * - Custom axis position: Mirrors across specified line
34
+ *
35
+ * @param positions - Current dancer positions
36
+ * @param options - Mirror configuration
37
+ * @returns Mirrored positions
38
+ *
39
+ * @example
40
+ * // Mirror horizontally (flip left/right)
41
+ * const mirrored = mirrorFormation(currentPositions, {
42
+ * axis: 'horizontal'
43
+ * });
44
+ *
45
+ * // Mirror vertically (flip up/down)
46
+ * const flipped = mirrorFormation(currentPositions, {
47
+ * axis: 'vertical'
48
+ * });
49
+ *
50
+ * // Mirror across custom axis (center line at x=500)
51
+ * const custom = mirrorFormation(currentPositions, {
52
+ * axis: 'horizontal',
53
+ * axisPosition: 500
54
+ * });
55
+ *
56
+ * // Mirror positions but not facing
57
+ * const positionsOnly = mirrorFormation(currentPositions, {
58
+ * axis: 'horizontal',
59
+ * mirrorFacing: false
60
+ * });
61
+ */
62
+ export declare function mirrorFormation(positions: readonly DancerPosition[], options: MirrorOptions): DancerPosition[];
63
+ /**
64
+ * Create a mirrored duplicate of the formation
65
+ * Combines original and mirrored positions
66
+ *
67
+ * Useful for creating symmetric formations
68
+ *
69
+ * @param positions - Original positions
70
+ * @param options - Mirror configuration
71
+ * @returns Combined array of original + mirrored positions
72
+ *
73
+ * @example
74
+ * // Create symmetric formation (double the dancers)
75
+ * const symmetric = mirrorAndDuplicate(halfFormation, {
76
+ * axis: 'horizontal'
77
+ * });
78
+ * // Result: original dancers + mirrored copies
79
+ */
80
+ export declare function mirrorAndDuplicate(positions: readonly DancerPosition[], options: MirrorOptions): DancerPosition[];
81
+ /**
82
+ * Mirror formation across both axes (180° rotation effect)
83
+ * Equivalent to rotating 180° around center point
84
+ *
85
+ * Math:
86
+ * x' = 2*cx - x
87
+ * y' = 2*cy - y
88
+ * facing' = (facing + 180) % 360
89
+ *
90
+ * @param positions - Current positions
91
+ * @param center - Center point (auto-calculated if not provided)
92
+ * @param mirrorFacing - Whether to rotate facing 180° (default: true)
93
+ * @returns Double-mirrored positions
94
+ *
95
+ * @example
96
+ * // Flip formation upside down
97
+ * const upsideDown = mirrorBothAxes(currentPositions);
98
+ */
99
+ export declare function mirrorBothAxes(positions: readonly DancerPosition[], center?: Point, mirrorFacing?: boolean): DancerPosition[];
100
+ /**
101
+ * Create kaleidoscope effect by mirroring across both axes
102
+ * Creates 4 mirrored copies (quadrants)
103
+ *
104
+ * @param positions - Original positions (should be in one quadrant)
105
+ * @param center - Center point for kaleidoscope
106
+ * @returns Array with 4x dancers (original + 3 mirrored copies)
107
+ *
108
+ * @example
109
+ * // Create kaleidoscope pattern from quarter formation
110
+ * const kaleidoscope = createKaleidoscope(quarterFormation, { x: 500, y: 500 });
111
+ * // Result: 4 symmetric quadrants
112
+ */
113
+ export declare function createKaleidoscope(positions: readonly DancerPosition[], center?: Point): DancerPosition[];
114
+ /**
115
+ * Validate that mirrored formation won't exceed stage bounds
116
+ *
117
+ * @param positions - Positions to mirror
118
+ * @param options - Mirror options
119
+ * @param stageWidth - Stage width (default: 1000)
120
+ * @param stageHeight - Stage height (default: 1000)
121
+ * @param margin - Safety margin from edges (default: 20)
122
+ * @returns True if mirrored formation is safe
123
+ */
124
+ export declare function isMirrorSafe(positions: readonly DancerPosition[], options: MirrorOptions, stageWidth?: number, stageHeight?: number, margin?: number): boolean;
125
+ /**
126
+ * Calculate mirror axis from two points (for UI interaction)
127
+ * User drags a line to define mirror axis
128
+ *
129
+ * @param point1 - First point on mirror line
130
+ * @param point2 - Second point on mirror line
131
+ * @returns Axis type and position
132
+ *
133
+ * @example
134
+ * // User drags vertical line
135
+ * const mirror = calculateMirrorAxisFromDrag(
136
+ * { x: 500, y: 100 },
137
+ * { x: 500, y: 900 }
138
+ * );
139
+ * // mirror.axis = 'horizontal', mirror.axisPosition = 500
140
+ */
141
+ export declare function calculateMirrorAxisFromDrag(point1: Point, point2: Point): {
142
+ axis: 'horizontal' | 'vertical';
143
+ axisPosition: number;
144
+ };
145
+ /**
146
+ * Find pairs of dancers that are symmetric across an axis
147
+ * Useful for highlighting symmetric relationships
148
+ *
149
+ * @param positions - All dancer positions
150
+ * @param options - Mirror configuration
151
+ * @param tolerance - Position tolerance for matching (Stage units, default: 10)
152
+ * @returns Array of [index1, index2] pairs that are symmetric
153
+ *
154
+ * @example
155
+ * // Find symmetric pairs
156
+ * const pairs = findSymmetricPairs(allPositions, { axis: 'horizontal' });
157
+ * // pairs = [[0, 5], [1, 6], ...] (dancers 0&5 are symmetric, etc.)
158
+ */
159
+ export declare function findSymmetricPairs(positions: readonly DancerPosition[], options: MirrorOptions, tolerance?: number): Array<[number, number]>;
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Polygon geometry utilities
3
+ * For lasso selection and point-in-polygon detection
4
+ * Optimized based on Proof 4 in math_scratchpad.md
5
+ */
6
+ import type { Point } from './types';
7
+ /**
8
+ * Polygon bounding box
9
+ */
10
+ export interface PolygonBoundingBox {
11
+ minX: number;
12
+ maxX: number;
13
+ minY: number;
14
+ maxY: number;
15
+ }
16
+ /**
17
+ * Compute bounding box for a polygon
18
+ * O(n) operation - should be cached when possible
19
+ *
20
+ * @param polygon - Array of points [x1, y1, x2, y2, ...]
21
+ * @returns Bounding box
22
+ */
23
+ export declare function computeBoundingBox(polygon: readonly number[]): PolygonBoundingBox;
24
+ /**
25
+ * Check if a point is inside a polygon using ray casting algorithm
26
+ *
27
+ * @param point - Point to test
28
+ * @param polygon - Array of points forming polygon [x1, y1, x2, y2, ...]
29
+ * @returns true if point is inside polygon
30
+ */
31
+ export declare function isPointInPolygon(point: Point, polygon: readonly number[]): boolean;
32
+ /**
33
+ * Optimized point-in-polygon test with bounding box pre-check
34
+ * Based on Proof 4: Optimized Point-in-Polygon in math_scratchpad.md
35
+ *
36
+ * Use this for large polygons (1000+ points) and/or many point queries
37
+ *
38
+ * @param point - Point to test
39
+ * @param polygon - Array of points [x1, y1, x2, y2, ...]
40
+ * @param bbox - Pre-computed bounding box (optional, will compute if not provided)
41
+ * @returns true if point is inside polygon
42
+ */
43
+ export declare function isPointInPolygonOptimized(point: Point, polygon: readonly number[], bbox?: PolygonBoundingBox): boolean;
44
+ /**
45
+ * Batch point-in-polygon test for multiple points
46
+ * Computes bounding box once and reuses it
47
+ *
48
+ * @param points - Array of points to test
49
+ * @param polygon - Array of points [x1, y1, x2, y2, ...]
50
+ * @returns Array of booleans indicating if each point is inside
51
+ */
52
+ export declare function batchPointInPolygon(points: readonly Point[], polygon: readonly number[]): boolean[];
53
+ /**
54
+ * Calculate the area of a polygon
55
+ * Uses the shoelace formula
56
+ *
57
+ * @param polygon - Array of points [x1, y1, x2, y2, ...]
58
+ * @returns Signed area (positive for counter-clockwise, negative for clockwise)
59
+ */
60
+ export declare function polygonArea(polygon: readonly number[]): number;
61
+ /**
62
+ * Calculate the centroid of a polygon
63
+ *
64
+ * @param polygon - Array of points [x1, y1, x2, y2, ...]
65
+ * @returns Centroid point
66
+ */
67
+ export declare function polygonCentroid(polygon: readonly number[]): Point;
68
+ /**
69
+ * Simplify a polygon by removing points closer than a threshold
70
+ * Useful for reducing the complexity of lasso selections
71
+ *
72
+ * @param polygon - Array of points [x1, y1, x2, y2, ...]
73
+ * @param threshold - Minimum distance between consecutive points
74
+ * @returns Simplified polygon
75
+ */
76
+ export declare function simplifyPolygon(polygon: readonly number[], threshold: number): number[];
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Rotate Formation Mathematical Operations
3
+ *
4
+ * Rotates dancer positions around a center point using rotation matrix
5
+ *
6
+ * Math: 2D Rotation Matrix (adapted for Stage coordinates with Y-down)
7
+ * x' = cx + (x - cx) * cos(θ) - (y - cy) * sin(θ)
8
+ * y' = cy + (x - cx) * sin(θ) + (y - cy) * cos(θ)
9
+ *
10
+ * CRITICAL: Stage coordinates have Y-axis pointing DOWN (not up like math)
11
+ * This means we need to negate the Y component in rotation calculations
12
+ * to achieve counterclockwise rotation visually.
13
+ *
14
+ * Where:
15
+ * (cx, cy) = Center of rotation (typically centroid)
16
+ * θ = Rotation angle in radians (positive = counterclockwise)
17
+ * (x, y) = Original position
18
+ * (x', y') = Rotated position
19
+ *
20
+ * Facing Direction:
21
+ * facing' = (facing + rotationDegrees) % 360
22
+ *
23
+ * Common Rotations:
24
+ * 90° (π/2): Quarter turn counterclockwise
25
+ * 180° (π): Half turn
26
+ * 270° (3π/2): Quarter turn clockwise (or -90°)
27
+ * -90° (-π/2): Quarter turn clockwise
28
+ *
29
+ * CRITICAL: All coordinates in Stage space (0-1000 units)
30
+ * Performance: O(n) - Must complete in <5ms for 50 dancers
31
+ */
32
+ import type { Point, DancerPosition } from './types';
33
+ /**
34
+ * Preset rotation angles (in degrees)
35
+ */
36
+ export type RotationPreset = 90 | 180 | 270 | -90 | -180 | -270;
37
+ /**
38
+ * Options for rotation transformation
39
+ */
40
+ export interface RotateOptions {
41
+ /** Rotation angle in degrees (positive = counterclockwise) */
42
+ angle: number;
43
+ /** Center of rotation (auto-calculated if not provided) */
44
+ center?: Point;
45
+ /** Whether to rotate facing direction (default: true) */
46
+ rotateFacing?: boolean;
47
+ }
48
+ /**
49
+ * Rotate formation around a center point
50
+ *
51
+ * Math: Uses 2D rotation matrix transformation (adapted for Stage Y-down coords)
52
+ *
53
+ * Handles edge cases:
54
+ * - 0 dancers: Returns empty array
55
+ * - 1 dancer: Rotates around center (may stay in place if at center)
56
+ * - 0° rotation: Returns original positions
57
+ * - 360° rotation: Returns to original positions
58
+ *
59
+ * Positive angles rotate counterclockwise (standard mathematical convention)
60
+ * Negative angles rotate clockwise
61
+ *
62
+ * @example
63
+ * // Rotate 90° counterclockwise
64
+ * const rotated = rotateFormation(currentPositions, {
65
+ * angle: 90
66
+ * });
67
+ *
68
+ * @example
69
+ * // Rotate 90° clockwise (negative angle)
70
+ * const clockwise = rotateFormation(currentPositions, {
71
+ * angle: -90
72
+ * });
73
+ *
74
+ * @example
75
+ * // Rotate 180° (flip)
76
+ * const flipped = rotateFormation(currentPositions, {
77
+ * angle: 180
78
+ * });
79
+ *
80
+ * @example
81
+ * // Rotate around custom center
82
+ * const custom = rotateFormation(currentPositions, {
83
+ * angle: 45,
84
+ * center: { x: 500, y: 500 }
85
+ * });
86
+ *
87
+ * @example
88
+ * // Rotate positions but not facing
89
+ * const positionsOnly = rotateFormation(currentPositions, {
90
+ * angle: 90,
91
+ * rotateFacing: false
92
+ * });
93
+ */
94
+ export declare function rotateFormation(positions: readonly DancerPosition[], options: RotateOptions): DancerPosition[];
95
+ /**
96
+ * Rotate formation by preset angle (90°, 180°, 270°)
97
+ * Convenience wrapper with common rotation values
98
+ *
99
+ * @example
100
+ * // Quarter turn counterclockwise
101
+ * const rotated = rotateFormationPreset(positions, 90);
102
+ *
103
+ * @example
104
+ * // Half turn
105
+ * const half = rotateFormationPreset(positions, 180);
106
+ *
107
+ * @example
108
+ * // Quarter turn clockwise
109
+ * const clockwise = rotateFormationPreset(positions, -90);
110
+ */
111
+ export declare function rotateFormationPreset(positions: readonly DancerPosition[], preset: RotationPreset, center?: Point): DancerPosition[];
112
+ /**
113
+ * Create rotational copies of a formation
114
+ * Useful for creating symmetric radial patterns
115
+ *
116
+ * @example
117
+ * // Create 4-way rotational symmetry
118
+ * const pattern = createRotationalCopies(wedgeFormation, 4, { x: 500, y: 500 });
119
+ * // Result: 4 copies rotated 0°, 90°, 180°, 270°
120
+ *
121
+ * @example
122
+ * // Create 6-way star pattern
123
+ * const star = createRotationalCopies(rayFormation, 6);
124
+ * // Result: 6 copies rotated 0°, 60°, 120°, 180°, 240°, 300°
125
+ */
126
+ export declare function createRotationalCopies(positions: readonly DancerPosition[], copies: number, center?: Point): DancerPosition[];
127
+ /**
128
+ * Animate smooth rotation between two angles
129
+ * Generates intermediate positions for rotation animation
130
+ *
131
+ * @example
132
+ * // Animate 90° rotation in 10 steps
133
+ * const frames = animateRotation(positions, 0, 90, 10);
134
+ * // frames[0] = positions at 0°
135
+ * // frames[5] = positions at 45°
136
+ * // frames[9] = positions at 90°
137
+ */
138
+ export declare function animateRotation(positions: readonly DancerPosition[], startAngle: number, endAngle: number, steps: number, center?: Point): DancerPosition[][];
139
+ /**
140
+ * Calculate rotation angle from two points (for UI interaction)
141
+ * User drags from center to define rotation
142
+ *
143
+ * CRITICAL: Adapted for Stage coordinates with Y-down
144
+ * We negate the Y components to get correct angle direction
145
+ *
146
+ * @example
147
+ * // User drags from right to top (90° counterclockwise)
148
+ * const angle = calculateRotationFromDrag(
149
+ * { x: 500, y: 500 },
150
+ * { x: 600, y: 500 }, // Start: right of center
151
+ * { x: 500, y: 400 } // End: top of center
152
+ * );
153
+ * // angle ≈ 90
154
+ */
155
+ export declare function calculateRotationFromDrag(center: Point, startPoint: Point, endPoint: Point): number;
156
+ /**
157
+ * Snap rotation angle to nearest preset (45° increments)
158
+ *
159
+ * @example
160
+ * snapRotationAngle(47) // → 45
161
+ * snapRotationAngle(92) // → 90
162
+ * snapRotationAngle(178) // → 180
163
+ */
164
+ export declare function snapRotationAngle(angle: number, snapIncrement?: number): number;
165
+ /**
166
+ * Validate that rotated formation won't exceed stage bounds
167
+ */
168
+ export declare function isRotationSafe(positions: readonly DancerPosition[], options: RotateOptions, stageWidth?: number, stageHeight?: number, margin?: number): boolean;
169
+ /**
170
+ * Calculate bounding box diagonal length
171
+ * Useful for determining safe rotation radius
172
+ */
173
+ export declare function getBoundingBoxDiagonal(positions: readonly DancerPosition[]): number;
174
+ /**
175
+ * Calculate maximum safe rotation radius for formation to stay on stage
176
+ */
177
+ export declare function getMaxRotationRadius(center: Point, stageWidth?: number, stageHeight?: number, margin?: number): number;
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Catmull-Rom spline interpolation for smooth path generation
3
+ * Implements proof from math_scratchpad.md
4
+ */
5
+ import type { Point } from './types';
6
+ /**
7
+ * Bezier control points for a cubic Bezier curve
8
+ */
9
+ export interface BezierControlPoints {
10
+ start: Point;
11
+ control1: Point;
12
+ control2: Point;
13
+ end: Point;
14
+ }
15
+ /**
16
+ * Interpolate a point on a Catmull-Rom spline segment
17
+ * Based on Proof 3: Catmull-Rom Spline Interpolation in math_scratchpad.md
18
+ *
19
+ * @param p0 - Point before start (for tangent calculation)
20
+ * @param p1 - Segment start point
21
+ * @param p2 - Segment end point
22
+ * @param p3 - Point after end (for tangent calculation)
23
+ * @param t - Parameter in [0, 1]
24
+ * @returns Interpolated point
25
+ */
26
+ export declare function catmullRomPoint(p0: Point, p1: Point, p2: Point, p3: Point, t: number): Point;
27
+ /**
28
+ * Generate a smooth Catmull-Rom spline through control points
29
+ *
30
+ * @param controlPoints - Array of points to interpolate through
31
+ * @param segmentsPerCurve - Number of points to generate per curve segment (default: 20)
32
+ * @returns Array of interpolated points forming the smooth path
33
+ */
34
+ export declare function catmullRomSpline(controlPoints: readonly Point[], segmentsPerCurve?: number): Point[];
35
+ /**
36
+ * Convert a Catmull-Rom segment to cubic Bezier control points
37
+ * Based on conversion formula from math_scratchpad.md
38
+ *
39
+ * @param p0 - Point before start
40
+ * @param p1 - Segment start
41
+ * @param p2 - Segment end
42
+ * @param p3 - Point after end
43
+ * @returns Cubic Bezier control points
44
+ */
45
+ export declare function catmullRomToBezier(p0: Point, p1: Point, p2: Point, p3: Point): BezierControlPoints;
46
+ /**
47
+ * Convert entire Catmull-Rom spline to Bezier curves
48
+ * Useful for rendering with Canvas/SVG bezierCurveTo
49
+ *
50
+ * @param controlPoints - Array of control points
51
+ * @returns Array of Bezier curve segments
52
+ */
53
+ export declare function catmullRomSplineToBezier(controlPoints: readonly Point[]): BezierControlPoints[];
54
+ /**
55
+ * Calculate the tangent (derivative) at a point on the Catmull-Rom spline
56
+ * Useful for orienting dancers along a path
57
+ *
58
+ * @param p0 - Point before start
59
+ * @param p1 - Segment start
60
+ * @param p2 - Segment end
61
+ * @param p3 - Point after end
62
+ * @param t - Parameter in [0, 1]
63
+ * @returns Tangent vector (not normalized)
64
+ */
65
+ export declare function catmullRomTangent(p0: Point, p1: Point, p2: Point, p3: Point, t: number): Point;
66
+ /**
67
+ * Calculate the angle (in degrees) of the tangent at a point
68
+ * Useful for setting dancer facing direction along a path
69
+ *
70
+ * @param p0 - Point before start
71
+ * @param p1 - Segment start
72
+ * @param p2 - Segment end
73
+ * @param p3 - Point after end
74
+ * @param t - Parameter in [0, 1]
75
+ * @returns Angle in degrees (0 = right, 90 = down)
76
+ */
77
+ export declare function catmullRomAngle(p0: Point, p1: Point, p2: Point, p3: Point, t: number): number;
78
+ /**
79
+ * Calculate arc length of a Catmull-Rom segment
80
+ * Uses numerical integration (Simpson's rule)
81
+ *
82
+ * @param p0 - Point before start
83
+ * @param p1 - Segment start
84
+ * @param p2 - Segment end
85
+ * @param p3 - Point after end
86
+ * @param samples - Number of samples for integration (default: 100)
87
+ * @returns Arc length
88
+ */
89
+ export declare function catmullRomArcLength(p0: Point, p1: Point, p2: Point, p3: Point, samples?: number): number;
90
+ /**
91
+ * Sample a Catmull-Rom spline at uniform distances (arc-length parameterization)
92
+ * Useful for placing dancers at equal spacing along a curved path
93
+ *
94
+ * @param controlPoints - Array of control points
95
+ * @param spacing - Desired spacing between samples
96
+ * @returns Array of evenly spaced points along the curve
97
+ */
98
+ export declare function catmullRomUniformSample(controlPoints: readonly Point[], spacing: number): Point[];
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Enhanced spreader tool with directional and elliptical scaling
3
+ * Implements mathematical proofs from math_scratchpad.md
4
+ */
5
+ import type { DancerPosition, Point, SpreadOptions } from './types';
6
+ /**
7
+ * Apply elliptical scaling to positions around a center point
8
+ * Based on Proof 1: Elliptical Spreading Transform in math_scratchpad.md
9
+ *
10
+ * @param positions - Array of positions to transform
11
+ * @param scaleX - Horizontal scale factor
12
+ * @param scaleY - Vertical scale factor
13
+ * @param center - Center point (optional, will calculate if not provided)
14
+ * @param rotationDegrees - Rotation angle in degrees (default: 0)
15
+ * @returns Transformed positions
16
+ */
17
+ export declare function ellipticalScale(positions: readonly DancerPosition[], scaleX: number, scaleY: number, center?: Point, rotationDegrees?: number): DancerPosition[];
18
+ /**
19
+ * Apply directional spreading to positions
20
+ * Convenience wrapper around ellipticalScale for common spread patterns
21
+ *
22
+ * @param positions - Array of positions to spread
23
+ * @param options - Spread configuration options
24
+ * @returns Spread positions
25
+ */
26
+ export declare function directionalSpread(positions: readonly DancerPosition[], options: SpreadOptions): DancerPosition[];
27
+ /**
28
+ * Calculate the spread factor needed to achieve a target distance
29
+ * Useful for UI controls that want to spread by absolute distance rather than scale factor
30
+ *
31
+ * @param positions - Current positions
32
+ * @param targetDistance - Desired average distance from center
33
+ * @param center - Center point (optional)
34
+ * @returns Scale factor to achieve target distance
35
+ */
36
+ export declare function calculateSpreadFactorForDistance(positions: readonly DancerPosition[], targetDistance: number, center?: Point): number;
37
+ /**
38
+ * Get bounding box dimensions of positions
39
+ * Useful for determining appropriate spread limits
40
+ *
41
+ * @param positions - Array of positions
42
+ * @returns Bounding box with dimensions
43
+ */
44
+ export declare function getBoundingBox(positions: readonly DancerPosition[]): {
45
+ minX: number;
46
+ maxX: number;
47
+ minY: number;
48
+ maxY: number;
49
+ width: number;
50
+ height: number;
51
+ };
52
+ /**
53
+ * Check if spreading would cause positions to exceed stage bounds
54
+ *
55
+ * @param positions - Positions to check
56
+ * @param scaleFactor - Proposed scale factor
57
+ * @param stageWidth - Stage width in units
58
+ * @param stageHeight - Stage height in units
59
+ * @param center - Center point (optional)
60
+ * @returns True if spread is safe, false if it would exceed bounds
61
+ */
62
+ export declare function isSpreadSafe(positions: readonly DancerPosition[], scaleFactor: number, stageWidth: number, stageHeight: number, center?: Point): boolean;