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 +94 -0
- package/dist/bezier.d.ts +158 -0
- package/dist/centroid.d.ts +35 -0
- package/dist/circleArrangement.d.ts +139 -0
- package/dist/collision.d.ts +149 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +985 -0
- package/dist/lineArrangement.d.ts +92 -0
- package/dist/mirrorFlip.d.ts +159 -0
- package/dist/polygon.d.ts +76 -0
- package/dist/rotateFormation.d.ts +177 -0
- package/dist/spline.d.ts +98 -0
- package/dist/spreader.d.ts +62 -0
- package/dist/types.d.ts +98 -0
- package/package.json +36 -0
|
@@ -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;
|
package/dist/spline.d.ts
ADDED
|
@@ -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;
|