@orbat-mapper/control-measures 0.10.0 → 0.12.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/dist/{index-CYa8ELZp.d.mts → index-IeI0ELGA.d.mts} +79 -11
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +24 -2
- package/dist/preview/index.d.mts +1 -1
- package/dist/preview/index.mjs +1 -1
- package/dist/{renderControlMeasure-BcJiY40G.mjs → renderControlMeasure-CclYDSlM.mjs} +666 -76
- package/package.json +1 -1
|
@@ -130,15 +130,27 @@ const destinationPoint = (origin, distance, bearing) => {
|
|
|
130
130
|
//#endregion
|
|
131
131
|
//#region src/internal/vector-utils.ts
|
|
132
132
|
/**
|
|
133
|
-
* Offsets a polyline using Miter Joins or Round Joins.
|
|
133
|
+
* Offsets a polyline to both sides at once, using Miter Joins or Round Joins.
|
|
134
|
+
* `offsets` carries one half-width per vertex, so the outline may taper along
|
|
135
|
+
* its centerline; both sides read the same array rather than materialising a
|
|
136
|
+
* negated copy per geometry pass.
|
|
134
137
|
*/
|
|
135
|
-
function
|
|
138
|
+
function offsetPolylineSides(points, offsets, rounded = false, segments = 5) {
|
|
139
|
+
return {
|
|
140
|
+
left: offsetPolylineWithOffsets(points, offsets, rounded, segments, 1),
|
|
141
|
+
right: offsetPolylineWithOffsets(points, offsets, rounded, segments, -1)
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
/** One side of {@link offsetPolylineSides}; `sign` picks which. */
|
|
145
|
+
function offsetPolylineWithOffsets(points, offsets, rounded, segments, sign) {
|
|
136
146
|
if (points.length < 2) return points;
|
|
147
|
+
if (offsets.length !== points.length) return points;
|
|
137
148
|
const result = [];
|
|
138
149
|
const N = points.length;
|
|
139
150
|
for (let i = 0; i < N; i++) {
|
|
140
151
|
const p = points[i];
|
|
141
152
|
if (!p) continue;
|
|
153
|
+
const offset = offsets[i] * sign;
|
|
142
154
|
if (i === 0) {
|
|
143
155
|
const next = points[i + 1];
|
|
144
156
|
if (next) {
|
|
@@ -195,6 +207,17 @@ function offsetPolyline(points, offset, rounded = false, segments = 5) {
|
|
|
195
207
|
}
|
|
196
208
|
return result;
|
|
197
209
|
}
|
|
210
|
+
/** Linearly interpolates values by distance along a polyline, not vertex count. */
|
|
211
|
+
function interpolatePolylineValues(points, startValue, endValue) {
|
|
212
|
+
const distances = [0];
|
|
213
|
+
for (let i = 1; i < points.length; i++) distances.push(distances[i - 1] + vecMag(vecSub(points[i], points[i - 1])));
|
|
214
|
+
const totalLength = distances.at(-1) ?? 0;
|
|
215
|
+
if (totalLength < 1e-6) return points.map(() => endValue);
|
|
216
|
+
return distances.map((distance) => {
|
|
217
|
+
const progress = distance / totalLength;
|
|
218
|
+
return startValue + (endValue - startValue) * progress;
|
|
219
|
+
});
|
|
220
|
+
}
|
|
198
221
|
function vecAdd(a, b) {
|
|
199
222
|
return [a[0] + b[0], a[1] + b[1]];
|
|
200
223
|
}
|
|
@@ -248,9 +271,81 @@ function lineIntersection(p1, p2, p3, p4) {
|
|
|
248
271
|
}
|
|
249
272
|
//#endregion
|
|
250
273
|
//#region src/attack-utils.ts
|
|
251
|
-
const DEFAULT_SHAFT_WIDTH_RATIO = .6;
|
|
274
|
+
const DEFAULT_SHAFT_WIDTH_RATIO$1 = .6;
|
|
275
|
+
const DEFAULT_REAR_WIDTH_RATIO = DEFAULT_SHAFT_WIDTH_RATIO$1;
|
|
252
276
|
const SHAFT_MIN_RATIO = .1;
|
|
253
277
|
const SHAFT_MAX_RATIO = .9;
|
|
278
|
+
const REAR_WIDTH_OPTION_HANDLE_ID = "rear-width";
|
|
279
|
+
const SHAFT_WIDTH_OPTION_HANDLE_ID = "shaft-width";
|
|
280
|
+
/**
|
|
281
|
+
* Creates the definition-owned rear-/shaft-width handle pair shared by every
|
|
282
|
+
* variable-width arrow body. Callers supply only how their own geometry maps
|
|
283
|
+
* onto {@link WidthHandleGeometry}; placement (the two ends of the left edge)
|
|
284
|
+
* and the drag math (perpendicular distance from the dragged point to the
|
|
285
|
+
* matching centerline segment, over the reference half-width) live here once.
|
|
286
|
+
*/
|
|
287
|
+
function createWidthOptionHandles(config) {
|
|
288
|
+
return {
|
|
289
|
+
get(controlPoints, options) {
|
|
290
|
+
const geometry = config.geometry(controlPoints, options);
|
|
291
|
+
const rearLeft = geometry?.leftEdge[0];
|
|
292
|
+
const neckLeft = geometry?.leftEdge.at(-1);
|
|
293
|
+
if (!rearLeft || !neckLeft) return [];
|
|
294
|
+
return [{
|
|
295
|
+
id: REAR_WIDTH_OPTION_HANDLE_ID,
|
|
296
|
+
position: unproject(rearLeft[0], rearLeft[1])
|
|
297
|
+
}, {
|
|
298
|
+
id: SHAFT_WIDTH_OPTION_HANDLE_ID,
|
|
299
|
+
position: unproject(neckLeft[0], neckLeft[1])
|
|
300
|
+
}];
|
|
301
|
+
},
|
|
302
|
+
drag({ controlPoints, options, handleId, position }) {
|
|
303
|
+
const geometry = config.geometry(controlPoints, options);
|
|
304
|
+
if (!geometry || geometry.referenceHalfWidth < 1e-6) return void 0;
|
|
305
|
+
const rear = handleId === REAR_WIDTH_OPTION_HANDLE_ID;
|
|
306
|
+
if (!rear && !(handleId === "shaft-width")) return void 0;
|
|
307
|
+
const from = rear ? geometry.spine[0] : geometry.spine.at(-2);
|
|
308
|
+
const to = rear ? geometry.spine[1] : geometry.spine.at(-1);
|
|
309
|
+
if (!from || !to) return void 0;
|
|
310
|
+
const ratio = clamp(pointToInfiniteLineDistance(project(position[0], position[1]), from, to) / geometry.referenceHalfWidth, config.minRatio, rear ? config.maxRearRatio : config.maxShaftRatio);
|
|
311
|
+
return rear ? { rearWidthRatio: ratio } : { shaftWidthRatio: ratio };
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* The width handles for attack bodies built by {@link processAttackGeometry},
|
|
317
|
+
* whose ratios are fractions of the arrowhead half-width. A coordinate resolver
|
|
318
|
+
* lets derivative measures (Counterattack by Fire) map their authored Axis1
|
|
319
|
+
* points to the body coordinates first.
|
|
320
|
+
*/
|
|
321
|
+
function createVariableWidthAttackOptionHandles(resolveCoordinates = (points) => points) {
|
|
322
|
+
return createWidthOptionHandles({
|
|
323
|
+
geometry(controlPoints, options) {
|
|
324
|
+
const coordinates = resolveCoordinates(controlPoints);
|
|
325
|
+
if (!coordinates) return null;
|
|
326
|
+
const { geometry } = processAttackGeometry(coordinates, options);
|
|
327
|
+
const outerLeft = geometry?.headRing[0];
|
|
328
|
+
if (!geometry || !outerLeft) return null;
|
|
329
|
+
return {
|
|
330
|
+
spine: geometry.shaftCenterline,
|
|
331
|
+
leftEdge: geometry.shaftLeft,
|
|
332
|
+
referenceHalfWidth: vecMag(vecSub(outerLeft, geometry.ptBase))
|
|
333
|
+
};
|
|
334
|
+
},
|
|
335
|
+
minRatio: SHAFT_MIN_RATIO,
|
|
336
|
+
maxRearRatio: 3,
|
|
337
|
+
maxShaftRatio: SHAFT_MAX_RATIO
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
const variableWidthAttackOptionHandles = createVariableWidthAttackOptionHandles();
|
|
341
|
+
/**
|
|
342
|
+
* The single home of the "rear inherits shaft" rule: an omitted `rearWidthRatio`
|
|
343
|
+
* leaves the body parallel by matching the effective shaft ratio. Every
|
|
344
|
+
* variable-width body resolves the option through here so the rule cannot drift.
|
|
345
|
+
*/
|
|
346
|
+
function resolveRearWidthRatio(options, shaftRatio) {
|
|
347
|
+
return options.rearWidthRatio ?? shaftRatio;
|
|
348
|
+
}
|
|
254
349
|
/**
|
|
255
350
|
* Processes input coordinates and options to generate the core symbol geometry.
|
|
256
351
|
* This handles validation, default values, projection, and geometry calculation.
|
|
@@ -259,6 +354,7 @@ function processAttackGeometry(coordinates, options = {}) {
|
|
|
259
354
|
const shaftRatio = clamp(options.shaftWidthRatio ?? .6, SHAFT_MIN_RATIO, SHAFT_MAX_RATIO);
|
|
260
355
|
const smooth = options.smooth ?? false;
|
|
261
356
|
const smoothResolution = options.smoothResolution ?? 5;
|
|
357
|
+
const rearRatio = clamp(resolveRearWidthRatio(options, shaftRatio), SHAFT_MIN_RATIO, 3);
|
|
262
358
|
const points = coordinates.map((c) => project(c[0], c[1]));
|
|
263
359
|
const numPoints = points.length;
|
|
264
360
|
const ptTip = points[0];
|
|
@@ -268,9 +364,9 @@ function processAttackGeometry(coordinates, options = {}) {
|
|
|
268
364
|
const p = points[i];
|
|
269
365
|
if (p) spinePoints.push(p);
|
|
270
366
|
}
|
|
271
|
-
return { geometry: calculateSymbolGeometry(ptTip, ptWidth, spinePoints, shaftRatio, smooth, smoothResolution) };
|
|
367
|
+
return { geometry: calculateSymbolGeometry(ptTip, ptWidth, spinePoints, shaftRatio, rearRatio, smooth, smoothResolution) };
|
|
272
368
|
}
|
|
273
|
-
function calculateSymbolGeometry(ptTip, ptWidth, spine, shaftRatio, smooth, smoothResolution) {
|
|
369
|
+
function calculateSymbolGeometry(ptTip, ptWidth, spine, shaftRatio, rearWidthRatio, smooth, smoothResolution) {
|
|
274
370
|
const initialNeck = spine[spine.length - 1];
|
|
275
371
|
if (!initialNeck) return null;
|
|
276
372
|
const initialTipDir = vecNorm(vecSub(ptTip, initialNeck));
|
|
@@ -323,10 +419,11 @@ function calculateSymbolGeometry(ptTip, ptWidth, spine, shaftRatio, smooth, smoo
|
|
|
323
419
|
outerLeft
|
|
324
420
|
];
|
|
325
421
|
const fullSpine = [...remainingSpine, shaftEndCenter];
|
|
422
|
+
const { left: shaftLeft, right: shaftRight } = offsetPolylineSides(fullSpine, interpolatePolylineValues(fullSpine, headHalfWidth * rearWidthRatio, shaftHalfWidth), smooth, smoothResolution);
|
|
326
423
|
return {
|
|
327
424
|
shaftCenterline: fullSpine,
|
|
328
|
-
shaftLeft
|
|
329
|
-
shaftRight
|
|
425
|
+
shaftLeft,
|
|
426
|
+
shaftRight,
|
|
330
427
|
headRing,
|
|
331
428
|
ptTip,
|
|
332
429
|
ptNeck,
|
|
@@ -563,7 +660,7 @@ function samePosition(a, b) {
|
|
|
563
660
|
}
|
|
564
661
|
//#endregion
|
|
565
662
|
//#region src/draw-rules/area1.ts
|
|
566
|
-
function derive$
|
|
663
|
+
function derive$14(points) {
|
|
567
664
|
return clonePositions(points);
|
|
568
665
|
}
|
|
569
666
|
/**
|
|
@@ -583,9 +680,9 @@ const area1DrawRule = {
|
|
|
583
680
|
id: "area1",
|
|
584
681
|
minimumUserPoints: 3,
|
|
585
682
|
closedRing: true,
|
|
586
|
-
derive: derive$
|
|
683
|
+
derive: derive$14,
|
|
587
684
|
transform(event) {
|
|
588
|
-
return derive$
|
|
685
|
+
return derive$14(event.next);
|
|
589
686
|
}
|
|
590
687
|
};
|
|
591
688
|
//#endregion
|
|
@@ -625,14 +722,14 @@ const containDrawRule = createMidpointPerpendicularDrawRule({
|
|
|
625
722
|
* that fixes both the radius and the bearing of the symbol's opening. Both
|
|
626
723
|
* points are user-clicked, so the canonical array is just the (clamped) input.
|
|
627
724
|
*/
|
|
628
|
-
function derive$
|
|
725
|
+
function derive$13(points) {
|
|
629
726
|
return clonePositions(points.slice(0, 2));
|
|
630
727
|
}
|
|
631
728
|
const centerRadiusDrawRule = {
|
|
632
729
|
id: "area15:center-radius",
|
|
633
730
|
minimumUserPoints: 2,
|
|
634
731
|
canonicalPointCount: 2,
|
|
635
|
-
derive: derive$
|
|
732
|
+
derive: derive$13,
|
|
636
733
|
transform(event) {
|
|
637
734
|
const { previous, next, activePointIndex } = event;
|
|
638
735
|
if (activePointIndex === 0 && previous.length >= 2 && next.length >= 2) {
|
|
@@ -641,7 +738,7 @@ const centerRadiusDrawRule = {
|
|
|
641
738
|
const origin = previous[1];
|
|
642
739
|
return [clonePosition(next[0]), [origin[0] + dx, origin[1] + dy]];
|
|
643
740
|
}
|
|
644
|
-
return derive$
|
|
741
|
+
return derive$13(next);
|
|
645
742
|
}
|
|
646
743
|
};
|
|
647
744
|
/** Backward-compatible doctrinal name for Area15's shared center-radius rule. */
|
|
@@ -705,16 +802,16 @@ const point12DrawRule = createMidpointPerpendicularDrawRule({ id: "point12:obsta
|
|
|
705
802
|
* Point1 — a single anchor point (e.g. Text's center). One user click commits
|
|
706
803
|
* the measure; dragging the point simply translates it.
|
|
707
804
|
*/
|
|
708
|
-
function derive$
|
|
805
|
+
function derive$12(points) {
|
|
709
806
|
return clonePositions(points.slice(0, 1));
|
|
710
807
|
}
|
|
711
808
|
const pointDrawRule = {
|
|
712
809
|
id: "point1:anchor",
|
|
713
810
|
minimumUserPoints: 1,
|
|
714
811
|
canonicalPointCount: 1,
|
|
715
|
-
derive: derive$
|
|
812
|
+
derive: derive$12,
|
|
716
813
|
transform(event) {
|
|
717
|
-
return derive$
|
|
814
|
+
return derive$12(event.next);
|
|
718
815
|
}
|
|
719
816
|
};
|
|
720
817
|
//#endregion
|
|
@@ -741,7 +838,7 @@ const SECTOR_ANGLE_SNAP_RADIANS = Math.PI / 36;
|
|
|
741
838
|
* independently control a radius and a true-north azimuth. Dragging the anchor
|
|
742
839
|
* translates both edge points so the sector retains its size and orientation.
|
|
743
840
|
*/
|
|
744
|
-
function derive$
|
|
841
|
+
function derive$11(points) {
|
|
745
842
|
return clonePositions(points.slice(0, 3));
|
|
746
843
|
}
|
|
747
844
|
function guidePoints$2(points) {
|
|
@@ -769,7 +866,7 @@ const sectorDrawRule = {
|
|
|
769
866
|
showGuide: true,
|
|
770
867
|
guidePoints: guidePoints$2,
|
|
771
868
|
constrainAngles,
|
|
772
|
-
derive: derive$
|
|
869
|
+
derive: derive$11,
|
|
773
870
|
transform(event) {
|
|
774
871
|
const { previous, next, activePointIndex } = event;
|
|
775
872
|
if (activePointIndex === 0 && previous.length >= 3 && next.length >= 3) {
|
|
@@ -781,7 +878,7 @@ const sectorDrawRule = {
|
|
|
781
878
|
[previous[2][0] + dx, previous[2][1] + dy]
|
|
782
879
|
];
|
|
783
880
|
}
|
|
784
|
-
return derive$
|
|
881
|
+
return derive$11(next);
|
|
785
882
|
}
|
|
786
883
|
};
|
|
787
884
|
//#endregion
|
|
@@ -836,20 +933,20 @@ const ambushDrawRule = createMidpointPerpendicularDrawRule({
|
|
|
836
933
|
});
|
|
837
934
|
//#endregion
|
|
838
935
|
//#region src/draw-rules/line1.ts
|
|
839
|
-
function derive$
|
|
936
|
+
function derive$10(points) {
|
|
840
937
|
return clonePositions(points);
|
|
841
938
|
}
|
|
842
939
|
const line1DrawRule = {
|
|
843
940
|
id: "line1",
|
|
844
941
|
minimumUserPoints: 2,
|
|
845
|
-
derive: derive$
|
|
942
|
+
derive: derive$10,
|
|
846
943
|
transform(event) {
|
|
847
|
-
return derive$
|
|
944
|
+
return derive$10(event.next);
|
|
848
945
|
}
|
|
849
946
|
};
|
|
850
947
|
//#endregion
|
|
851
948
|
//#region src/draw-rules/line3.ts
|
|
852
|
-
function derive$
|
|
949
|
+
function derive$9(points) {
|
|
853
950
|
return clonePositions(points);
|
|
854
951
|
}
|
|
855
952
|
/**
|
|
@@ -864,23 +961,23 @@ const line3DrawRule = {
|
|
|
864
961
|
id: "line3",
|
|
865
962
|
minimumUserPoints: 3,
|
|
866
963
|
canonicalPointCount: 3,
|
|
867
|
-
derive: derive$
|
|
964
|
+
derive: derive$9,
|
|
868
965
|
transform(event) {
|
|
869
|
-
return derive$
|
|
966
|
+
return derive$9(event.next);
|
|
870
967
|
}
|
|
871
968
|
};
|
|
872
969
|
//#endregion
|
|
873
970
|
//#region src/draw-rules/line9.ts
|
|
874
|
-
function derive$
|
|
971
|
+
function derive$8(points) {
|
|
875
972
|
return clonePositions(points);
|
|
876
973
|
}
|
|
877
974
|
const line9DrawRule = {
|
|
878
975
|
id: "line9",
|
|
879
976
|
minimumUserPoints: 2,
|
|
880
977
|
canonicalPointCount: 2,
|
|
881
|
-
derive: derive$
|
|
978
|
+
derive: derive$8,
|
|
882
979
|
transform(event) {
|
|
883
|
-
return derive$
|
|
980
|
+
return derive$8(event.next);
|
|
884
981
|
}
|
|
885
982
|
};
|
|
886
983
|
//#endregion
|
|
@@ -1009,7 +1106,7 @@ const line24DrawRule = createMidpointPerpendicularDrawRule({
|
|
|
1009
1106
|
});
|
|
1010
1107
|
//#endregion
|
|
1011
1108
|
//#region src/draw-rules/line26.ts
|
|
1012
|
-
function derive$
|
|
1109
|
+
function derive$7(points) {
|
|
1013
1110
|
if (points.length === 2) {
|
|
1014
1111
|
const [p1, p2] = points;
|
|
1015
1112
|
const dx = p2[0] - p1[0];
|
|
@@ -1048,9 +1145,9 @@ const line26DrawRule = {
|
|
|
1048
1145
|
minimumUserPoints: 4,
|
|
1049
1146
|
minimumPreviewPoints: 2,
|
|
1050
1147
|
canonicalPointCount: 4,
|
|
1051
|
-
derive: derive$
|
|
1148
|
+
derive: derive$7,
|
|
1052
1149
|
transform(event) {
|
|
1053
|
-
return derive$
|
|
1150
|
+
return derive$7(event.next);
|
|
1054
1151
|
}
|
|
1055
1152
|
};
|
|
1056
1153
|
//#endregion
|
|
@@ -1063,7 +1160,7 @@ function deriveArcPoint(p1, p2, p4) {
|
|
|
1063
1160
|
if (!arc) return [(p2[0] + p4[0]) / 2, (p2[1] + p4[1]) / 2];
|
|
1064
1161
|
return unproject(arc.midpoint[0], arc.midpoint[1]);
|
|
1065
1162
|
}
|
|
1066
|
-
function derive$
|
|
1163
|
+
function derive$6(points) {
|
|
1067
1164
|
if (points.length < 2) return clonePositions(points);
|
|
1068
1165
|
const [p1, p2] = points;
|
|
1069
1166
|
if (points.length === 2) {
|
|
@@ -1102,7 +1199,7 @@ const line27DrawRule = {
|
|
|
1102
1199
|
minimumUserPoints: 3,
|
|
1103
1200
|
minimumPreviewPoints: 2,
|
|
1104
1201
|
canonicalPointCount: 4,
|
|
1105
|
-
derive: derive$
|
|
1202
|
+
derive: derive$6,
|
|
1106
1203
|
transform(event) {
|
|
1107
1204
|
const { previous, next, activePointIndex } = event;
|
|
1108
1205
|
if (activePointIndex === 0 && previous.length >= 4 && next.length >= 4) {
|
|
@@ -1134,6 +1231,124 @@ const line27DrawRule = {
|
|
|
1134
1231
|
}
|
|
1135
1232
|
};
|
|
1136
1233
|
//#endregion
|
|
1234
|
+
//#region src/draw-rules/line31.ts
|
|
1235
|
+
function derive$5(points) {
|
|
1236
|
+
if (points.length === 2) {
|
|
1237
|
+
const [c1, c3] = points;
|
|
1238
|
+
const start = project(c1[0], c1[1]);
|
|
1239
|
+
const span = vecSub(project(c3[0], c3[1]), start);
|
|
1240
|
+
const p2 = vecAdd(start, vecScale(span, .5));
|
|
1241
|
+
const p4 = vecAdd(vecAdd(start, vecScale(span, .75)), vecScale([-span[1], span[0]], .25));
|
|
1242
|
+
return clonePositions([
|
|
1243
|
+
c1,
|
|
1244
|
+
unproject(p2[0], p2[1]),
|
|
1245
|
+
c3,
|
|
1246
|
+
unproject(p4[0], p4[1])
|
|
1247
|
+
]);
|
|
1248
|
+
}
|
|
1249
|
+
const derived = clonePositions(points.slice(0, 4));
|
|
1250
|
+
if (derived.length < 3) return derived;
|
|
1251
|
+
const [c1, c2, c3] = derived;
|
|
1252
|
+
const p1 = project(c1[0], c1[1]);
|
|
1253
|
+
const p2 = project(c2[0], c2[1]);
|
|
1254
|
+
const straight = vecSub(p2, p1);
|
|
1255
|
+
if (vecMag(straight) === 0) return derived;
|
|
1256
|
+
const diameter = vecMag(vecSub(project(c3[0], c3[1]), p2));
|
|
1257
|
+
const anchor = vecAdd(p2, vecScale(vecNorm(straight), diameter));
|
|
1258
|
+
derived[2] = unproject(anchor[0], anchor[1]);
|
|
1259
|
+
return derived;
|
|
1260
|
+
}
|
|
1261
|
+
/**
|
|
1262
|
+
* The snapped PT. 4 for a semicircle spanning PT. 2 → PT. 3, in projected
|
|
1263
|
+
* meters. PT. 4 is the top of the selected semicircle, not a free point on its
|
|
1264
|
+
* circumference, so crossing the diameter flips which side holds the arc.
|
|
1265
|
+
* Returns `null` for a degenerate (zero-length) diameter.
|
|
1266
|
+
*/
|
|
1267
|
+
function orientationPoint(p2, p3, p4) {
|
|
1268
|
+
const diameter = vecSub(p3, p2);
|
|
1269
|
+
if (diameter[0] === 0 && diameter[1] === 0) return null;
|
|
1270
|
+
const apex = vecAdd(midpoint(p2, p3), vecScale([-diameter[1], diameter[0]], quarterArcSide(p2, p3, p4) / 2));
|
|
1271
|
+
return unproject(apex[0], apex[1]);
|
|
1272
|
+
}
|
|
1273
|
+
function snapOrientationToArc(points) {
|
|
1274
|
+
if (points.length < 4) return points;
|
|
1275
|
+
const snapped = orientationPoint(project(points[1][0], points[1][1]), project(points[2][0], points[2][1]), project(points[3][0], points[3][1]));
|
|
1276
|
+
if (snapped) points[3] = snapped;
|
|
1277
|
+
return points;
|
|
1278
|
+
}
|
|
1279
|
+
/** Line31 anchors for Envelopment; PT. 3 is constrained parallel to PT. 1 → PT. 2. */
|
|
1280
|
+
const line31DrawRule = {
|
|
1281
|
+
id: "line31:envelopment",
|
|
1282
|
+
minimumUserPoints: 2,
|
|
1283
|
+
minimumPreviewPoints: 2,
|
|
1284
|
+
canonicalPointCount: 4,
|
|
1285
|
+
derive: derive$5,
|
|
1286
|
+
transform(event) {
|
|
1287
|
+
const { activePointIndex, next, previous } = event;
|
|
1288
|
+
if (next.length < 4) return derive$5(next);
|
|
1289
|
+
if (activePointIndex === 1) {
|
|
1290
|
+
const points = clonePositions(next.slice(0, 4));
|
|
1291
|
+
const p1 = project(points[0][0], points[0][1]);
|
|
1292
|
+
const p2 = project(points[1][0], points[1][1]);
|
|
1293
|
+
const p3 = project(points[2][0], points[2][1]);
|
|
1294
|
+
const p4 = project(points[3][0], points[3][1]);
|
|
1295
|
+
const span = vecSub(p3, p1);
|
|
1296
|
+
const lengthSquared = vecDot(span, span);
|
|
1297
|
+
if (lengthSquared === 0) return points;
|
|
1298
|
+
const slid = vecAdd(p1, vecScale(span, Math.min(1, Math.max(0, vecDot(vecSub(p2, p1), span) / lengthSquared))));
|
|
1299
|
+
points[1] = unproject(slid[0], slid[1]);
|
|
1300
|
+
const snapped = orientationPoint(slid, p3, p4);
|
|
1301
|
+
if (snapped) points[3] = snapped;
|
|
1302
|
+
return points;
|
|
1303
|
+
}
|
|
1304
|
+
if (activePointIndex === 3) return snapOrientationToArc(clonePositions(next.slice(0, 4)));
|
|
1305
|
+
if ((activePointIndex === 0 || activePointIndex === 2) && previous.length >= 4) {
|
|
1306
|
+
const oldP1 = project(previous[0][0], previous[0][1]);
|
|
1307
|
+
const oldP2 = project(previous[1][0], previous[1][1]);
|
|
1308
|
+
const oldSpan = vecMag(vecSub(project(previous[2][0], previous[2][1]), oldP1));
|
|
1309
|
+
const straightRatio = oldSpan === 0 ? .5 : vecMag(vecSub(oldP2, oldP1)) / oldSpan;
|
|
1310
|
+
const points = clonePositions(next.slice(0, 4));
|
|
1311
|
+
const p1 = project(points[0][0], points[0][1]);
|
|
1312
|
+
const p3 = project(points[2][0], points[2][1]);
|
|
1313
|
+
const p4 = project(points[3][0], points[3][1]);
|
|
1314
|
+
const slid = vecAdd(p1, vecScale(vecSub(p3, p1), straightRatio));
|
|
1315
|
+
points[1] = unproject(slid[0], slid[1]);
|
|
1316
|
+
const snapped = orientationPoint(slid, p3, p4);
|
|
1317
|
+
if (snapped) points[3] = snapped;
|
|
1318
|
+
return points;
|
|
1319
|
+
}
|
|
1320
|
+
return snapOrientationToArc(derive$5(next));
|
|
1321
|
+
}
|
|
1322
|
+
};
|
|
1323
|
+
//#endregion
|
|
1324
|
+
//#region src/draw-rules/line32.ts
|
|
1325
|
+
function derive$4(points) {
|
|
1326
|
+
if (points.length === 2) {
|
|
1327
|
+
const [c1, c3] = points;
|
|
1328
|
+
const p2 = midpoint(project(c1[0], c1[1]), project(c3[0], c3[1]));
|
|
1329
|
+
return clonePositions([
|
|
1330
|
+
c1,
|
|
1331
|
+
unproject(p2[0], p2[1]),
|
|
1332
|
+
c3
|
|
1333
|
+
]);
|
|
1334
|
+
}
|
|
1335
|
+
return clonePositions(points.slice(0, 3));
|
|
1336
|
+
}
|
|
1337
|
+
/** Line32 anchors for the two-click Infiltration mission task. */
|
|
1338
|
+
const line32DrawRule = {
|
|
1339
|
+
id: "line32:infiltration",
|
|
1340
|
+
minimumUserPoints: 2,
|
|
1341
|
+
minimumPreviewPoints: 2,
|
|
1342
|
+
canonicalPointCount: 3,
|
|
1343
|
+
hiddenControlPointIndices: [1],
|
|
1344
|
+
derive: derive$4,
|
|
1345
|
+
transform(event) {
|
|
1346
|
+
const { activePointIndex, next } = event;
|
|
1347
|
+
if (next.length >= 3 && (activePointIndex === 0 || activePointIndex === 2)) return derive$4([next[0], next[2]]);
|
|
1348
|
+
return derive$4(next);
|
|
1349
|
+
}
|
|
1350
|
+
};
|
|
1351
|
+
//#endregion
|
|
1137
1352
|
//#region src/draw-rules/area8.ts
|
|
1138
1353
|
function derive$3(points) {
|
|
1139
1354
|
if (points.length < 2) return clonePositions(points);
|
|
@@ -1608,6 +1823,16 @@ const ATTACK_SHAFT_PARAMS = [
|
|
|
1608
1823
|
max: 1,
|
|
1609
1824
|
step: .05
|
|
1610
1825
|
},
|
|
1826
|
+
{
|
|
1827
|
+
key: "rearWidthRatio",
|
|
1828
|
+
presentationTier: "advanced",
|
|
1829
|
+
label: "Rear width",
|
|
1830
|
+
description: "Width at the rear of the shaft as a ratio of the arrowhead width.",
|
|
1831
|
+
type: "number",
|
|
1832
|
+
min: .1,
|
|
1833
|
+
max: 2,
|
|
1834
|
+
step: .05
|
|
1835
|
+
},
|
|
1611
1836
|
{
|
|
1612
1837
|
key: "smooth",
|
|
1613
1838
|
label: "Smooth",
|
|
@@ -1836,7 +2061,8 @@ const AREA_TEXT_AMPLIFIERS_DESIGNATION_HOSTILE = AREA_TEXT_AMPLIFIERS.filter((d)
|
|
|
1836
2061
|
//#endregion
|
|
1837
2062
|
//#region src/generators/cm15-maneuver-areas/airborneAttack.ts
|
|
1838
2063
|
const DEFAULT_AIRBORNE_ATTACK_OPTIONS = {
|
|
1839
|
-
shaftWidthRatio: DEFAULT_SHAFT_WIDTH_RATIO,
|
|
2064
|
+
shaftWidthRatio: DEFAULT_SHAFT_WIDTH_RATIO$1,
|
|
2065
|
+
rearWidthRatio: DEFAULT_REAR_WIDTH_RATIO,
|
|
1840
2066
|
smooth: false,
|
|
1841
2067
|
smoothResolution: 5
|
|
1842
2068
|
};
|
|
@@ -1901,7 +2127,8 @@ const AIRBORNE_ATTACK = defineControlMeasure({
|
|
|
1901
2127
|
metadata: AIRBORNE_ATTACK_METADATA,
|
|
1902
2128
|
generator: createAirborneAttack,
|
|
1903
2129
|
defaultOptions: DEFAULT_AIRBORNE_ATTACK_OPTIONS,
|
|
1904
|
-
rule: axis1DrawRule
|
|
2130
|
+
rule: axis1DrawRule,
|
|
2131
|
+
optionHandles: variableWidthAttackOptionHandles
|
|
1905
2132
|
});
|
|
1906
2133
|
/**
|
|
1907
2134
|
* Reacts to an **input-contract** violation according to `mode`: `throw`
|
|
@@ -5738,7 +5965,8 @@ const ATTACK_BY_FIRE = defineControlMeasure({
|
|
|
5738
5965
|
//#endregion
|
|
5739
5966
|
//#region src/generators/cm15-maneuver-areas/attackHelicopter.ts
|
|
5740
5967
|
const DEFAULT_ATTACK_HELICOPTER_OPTIONS = {
|
|
5741
|
-
shaftWidthRatio: DEFAULT_SHAFT_WIDTH_RATIO,
|
|
5968
|
+
shaftWidthRatio: DEFAULT_SHAFT_WIDTH_RATIO$1,
|
|
5969
|
+
rearWidthRatio: DEFAULT_REAR_WIDTH_RATIO,
|
|
5742
5970
|
smooth: false,
|
|
5743
5971
|
smoothResolution: 5,
|
|
5744
5972
|
symbolHeightRatio: .45,
|
|
@@ -5957,7 +6185,8 @@ const ATTACK_HELICOPTER = defineControlMeasure({
|
|
|
5957
6185
|
metadata: ATTACK_HELICOPTER_METADATA,
|
|
5958
6186
|
generator: createAttackHelicopter,
|
|
5959
6187
|
defaultOptions: DEFAULT_ATTACK_HELICOPTER_OPTIONS,
|
|
5960
|
-
rule: axis1DrawRule
|
|
6188
|
+
rule: axis1DrawRule,
|
|
6189
|
+
optionHandles: variableWidthAttackOptionHandles
|
|
5961
6190
|
});
|
|
5962
6191
|
//#endregion
|
|
5963
6192
|
//#region src/generators/cm15-maneuver-areas/battlePosition.ts
|
|
@@ -6592,8 +6821,12 @@ const BRIDGE_OR_GAP = defineControlMeasure({
|
|
|
6592
6821
|
const DEFAULT_SMOOTH_RESOLUTION$5 = 12;
|
|
6593
6822
|
const MIN_SMOOTH_RESOLUTION$1 = 2;
|
|
6594
6823
|
const MAX_SMOOTH_RESOLUTION$1 = 64;
|
|
6824
|
+
const DEFAULT_SHAFT_WIDTH_RATIO = .06;
|
|
6825
|
+
const MIN_SHAFT_WIDTH_RATIO = .01;
|
|
6826
|
+
const MAX_SHAFT_WIDTH_RATIO = .3;
|
|
6595
6827
|
const DEFAULT_BLOCK_ARROW_OPTIONS = {
|
|
6596
|
-
shaftWidthRatio:
|
|
6828
|
+
shaftWidthRatio: DEFAULT_SHAFT_WIDTH_RATIO,
|
|
6829
|
+
rearWidthRatio: DEFAULT_SHAFT_WIDTH_RATIO,
|
|
6597
6830
|
arrowheadStyle: "triangle",
|
|
6598
6831
|
arrowheadWidthRatio: .18,
|
|
6599
6832
|
arrowheadLengthRatio: .22,
|
|
@@ -6625,8 +6858,18 @@ const BLOCK_ARROW_METADATA = {
|
|
|
6625
6858
|
label: "Shaft width",
|
|
6626
6859
|
description: "Shaft band width as a fraction of the total path length",
|
|
6627
6860
|
type: "number",
|
|
6628
|
-
min:
|
|
6629
|
-
max:
|
|
6861
|
+
min: MIN_SHAFT_WIDTH_RATIO,
|
|
6862
|
+
max: MAX_SHAFT_WIDTH_RATIO,
|
|
6863
|
+
step: .01
|
|
6864
|
+
},
|
|
6865
|
+
{
|
|
6866
|
+
key: "rearWidthRatio",
|
|
6867
|
+
presentationTier: "advanced",
|
|
6868
|
+
label: "Rear width",
|
|
6869
|
+
description: "Width at the rear of the shaft as a fraction of the total path length",
|
|
6870
|
+
type: "number",
|
|
6871
|
+
min: MIN_SHAFT_WIDTH_RATIO,
|
|
6872
|
+
max: MAX_SHAFT_WIDTH_RATIO,
|
|
6630
6873
|
step: .01
|
|
6631
6874
|
},
|
|
6632
6875
|
{
|
|
@@ -6717,25 +6960,42 @@ const BLOCK_ARROW_METADATA = {
|
|
|
6717
6960
|
* right side of the shaft.
|
|
6718
6961
|
*/
|
|
6719
6962
|
function createBlockArrow(coordinates, options = {}) {
|
|
6720
|
-
const
|
|
6721
|
-
|
|
6722
|
-
|
|
6723
|
-
};
|
|
6724
|
-
const axis1 = axis1Geometry(coordinates);
|
|
6725
|
-
const axis = arrowAxis(axis1.path);
|
|
6726
|
-
if (!axis) return {
|
|
6963
|
+
const resolved = resolveBlockArrowOptions(options);
|
|
6964
|
+
const geometry = calculateBlockArrowGeometry(coordinates, resolved);
|
|
6965
|
+
if (!geometry) return {
|
|
6727
6966
|
type: "FeatureCollection",
|
|
6728
6967
|
features: []
|
|
6729
6968
|
};
|
|
6969
|
+
const { ring } = geometry;
|
|
6970
|
+
return {
|
|
6971
|
+
type: "FeatureCollection",
|
|
6972
|
+
features: [{
|
|
6973
|
+
type: "Feature",
|
|
6974
|
+
properties: {
|
|
6975
|
+
part: "body",
|
|
6976
|
+
fill: resolved.filled
|
|
6977
|
+
},
|
|
6978
|
+
geometry: {
|
|
6979
|
+
type: "Polygon",
|
|
6980
|
+
coordinates: [ring.map((p) => unproject(p[0], p[1]))]
|
|
6981
|
+
}
|
|
6982
|
+
}]
|
|
6983
|
+
};
|
|
6984
|
+
}
|
|
6985
|
+
function calculateBlockArrowGeometry(coordinates, options) {
|
|
6986
|
+
const axis1 = axis1Geometry(coordinates);
|
|
6987
|
+
const axis = arrowAxis(axis1.path);
|
|
6988
|
+
if (!axis) return null;
|
|
6730
6989
|
const { pts, pathLength, tip, dir, perp, segLength } = axis;
|
|
6731
|
-
const requestedHeadLen = axis1.headLength ?? pathLength * arrowheadLengthRatio;
|
|
6990
|
+
const requestedHeadLen = axis1.headLength ?? pathLength * options.arrowheadLengthRatio;
|
|
6732
6991
|
const headLen = Math.min(requestedHeadLen, segLength * .95);
|
|
6733
|
-
const halfShaft = pathLength * shaftWidthRatio / 2;
|
|
6734
|
-
const
|
|
6992
|
+
const halfShaft = pathLength * options.shaftWidthRatio / 2;
|
|
6993
|
+
const halfRear = pathLength * options.rearWidthRatio / 2;
|
|
6994
|
+
const halfHead = axis1.headHalfWidth ?? pathLength * options.arrowheadWidthRatio / 2;
|
|
6735
6995
|
const onAxis = (d, side = 0) => [tip[0] - dir[0] * d + perp[0] * side, tip[1] - dir[1] * d + perp[1] * side];
|
|
6736
6996
|
let shaftEndDist;
|
|
6737
6997
|
let headPts;
|
|
6738
|
-
switch (arrowheadStyle) {
|
|
6998
|
+
switch (options.arrowheadStyle) {
|
|
6739
6999
|
case "barbed":
|
|
6740
7000
|
shaftEndDist = headLen * .55;
|
|
6741
7001
|
headPts = [
|
|
@@ -6793,9 +7053,9 @@ function createBlockArrow(coordinates, options = {}) {
|
|
|
6793
7053
|
break;
|
|
6794
7054
|
}
|
|
6795
7055
|
let spine = [...pts.slice(0, -1), onAxis(shaftEndDist)];
|
|
6796
|
-
if (smooth) spine = catmullRom(spine, normalizeSmoothResolution$1(smoothResolution));
|
|
6797
|
-
const
|
|
6798
|
-
const rightSide =
|
|
7056
|
+
if (options.smooth) spine = catmullRom(spine, normalizeSmoothResolution$1(options.smoothResolution));
|
|
7057
|
+
const widths = interpolatePolylineValues(spine, halfRear, halfShaft);
|
|
7058
|
+
const { left: leftSide, right: rightSide } = offsetPolylineSides(spine, widths);
|
|
6799
7059
|
const ring = [
|
|
6800
7060
|
...leftSide,
|
|
6801
7061
|
...headPts,
|
|
@@ -6803,18 +7063,20 @@ function createBlockArrow(coordinates, options = {}) {
|
|
|
6803
7063
|
];
|
|
6804
7064
|
ring.push(ring[0]);
|
|
6805
7065
|
return {
|
|
6806
|
-
|
|
6807
|
-
|
|
6808
|
-
|
|
6809
|
-
|
|
6810
|
-
|
|
6811
|
-
|
|
6812
|
-
|
|
6813
|
-
|
|
6814
|
-
|
|
6815
|
-
|
|
6816
|
-
|
|
6817
|
-
|
|
7066
|
+
ring,
|
|
7067
|
+
spine,
|
|
7068
|
+
leftSide,
|
|
7069
|
+
pathLength
|
|
7070
|
+
};
|
|
7071
|
+
}
|
|
7072
|
+
function resolveBlockArrowOptions(options) {
|
|
7073
|
+
const resolved = {
|
|
7074
|
+
...DEFAULT_BLOCK_ARROW_OPTIONS,
|
|
7075
|
+
...options
|
|
7076
|
+
};
|
|
7077
|
+
return {
|
|
7078
|
+
...resolved,
|
|
7079
|
+
rearWidthRatio: resolveRearWidthRatio(options, resolved.shaftWidthRatio)
|
|
6818
7080
|
};
|
|
6819
7081
|
}
|
|
6820
7082
|
const BLOCK_ARROW = defineControlMeasure({
|
|
@@ -6822,6 +7084,20 @@ const BLOCK_ARROW = defineControlMeasure({
|
|
|
6822
7084
|
generator: createBlockArrow,
|
|
6823
7085
|
defaultOptions: DEFAULT_BLOCK_ARROW_OPTIONS,
|
|
6824
7086
|
rule: axis1DrawRule,
|
|
7087
|
+
optionHandles: createWidthOptionHandles({
|
|
7088
|
+
geometry(controlPoints, options) {
|
|
7089
|
+
const geometry = calculateBlockArrowGeometry(controlPoints, resolveBlockArrowOptions(options));
|
|
7090
|
+
if (!geometry) return null;
|
|
7091
|
+
return {
|
|
7092
|
+
spine: geometry.spine,
|
|
7093
|
+
leftEdge: geometry.leftSide,
|
|
7094
|
+
referenceHalfWidth: geometry.pathLength / 2
|
|
7095
|
+
};
|
|
7096
|
+
},
|
|
7097
|
+
minRatio: MIN_SHAFT_WIDTH_RATIO,
|
|
7098
|
+
maxRearRatio: MAX_SHAFT_WIDTH_RATIO,
|
|
7099
|
+
maxShaftRatio: MAX_SHAFT_WIDTH_RATIO
|
|
7100
|
+
}),
|
|
6825
7101
|
previewSample: {
|
|
6826
7102
|
controlPoints: [
|
|
6827
7103
|
[1.4, 0],
|
|
@@ -9274,7 +9550,8 @@ const CLEAR = defineControlMeasure({
|
|
|
9274
9550
|
//#endregion
|
|
9275
9551
|
//#region src/generators/cm15-maneuver-areas/supportingAttack.ts
|
|
9276
9552
|
const DEFAULT_SUPPORTING_ATTACK_OPTIONS = {
|
|
9277
|
-
shaftWidthRatio: DEFAULT_SHAFT_WIDTH_RATIO,
|
|
9553
|
+
shaftWidthRatio: DEFAULT_SHAFT_WIDTH_RATIO$1,
|
|
9554
|
+
rearWidthRatio: DEFAULT_REAR_WIDTH_RATIO,
|
|
9278
9555
|
smooth: false,
|
|
9279
9556
|
smoothResolution: 5
|
|
9280
9557
|
};
|
|
@@ -9346,12 +9623,14 @@ const SUPPORTING_ATTACK = defineControlMeasure({
|
|
|
9346
9623
|
metadata: SUPPORTING_ATTACK_METADATA,
|
|
9347
9624
|
generator: createSupportingAttack,
|
|
9348
9625
|
defaultOptions: DEFAULT_SUPPORTING_ATTACK_OPTIONS,
|
|
9349
|
-
rule: axis1DrawRule
|
|
9626
|
+
rule: axis1DrawRule,
|
|
9627
|
+
optionHandles: variableWidthAttackOptionHandles
|
|
9350
9628
|
});
|
|
9351
9629
|
//#endregion
|
|
9352
9630
|
//#region src/generators/cm34-mission-tasks/counterattack.ts
|
|
9353
9631
|
const DEFAULT_COUNTERATTACK_OPTIONS = {
|
|
9354
|
-
shaftWidthRatio: DEFAULT_SHAFT_WIDTH_RATIO,
|
|
9632
|
+
shaftWidthRatio: DEFAULT_SHAFT_WIDTH_RATIO$1,
|
|
9633
|
+
rearWidthRatio: DEFAULT_REAR_WIDTH_RATIO,
|
|
9355
9634
|
smooth: false,
|
|
9356
9635
|
smoothResolution: 5,
|
|
9357
9636
|
labelPosition: 1
|
|
@@ -9436,6 +9715,7 @@ const COUNTERATTACK = defineControlMeasure({
|
|
|
9436
9715
|
generator: createCounterattack,
|
|
9437
9716
|
defaultOptions: DEFAULT_COUNTERATTACK_OPTIONS,
|
|
9438
9717
|
rule: axis1DrawRule,
|
|
9718
|
+
optionHandles: variableWidthAttackOptionHandles,
|
|
9439
9719
|
previewSample: {
|
|
9440
9720
|
controlPoints: [
|
|
9441
9721
|
[1, 0],
|
|
@@ -9596,6 +9876,7 @@ const COUNTERATTACK_BY_FIRE = defineControlMeasure({
|
|
|
9596
9876
|
generator: createCounterattackByFire,
|
|
9597
9877
|
defaultOptions: DEFAULT_COUNTERATTACK_BY_FIRE_OPTIONS,
|
|
9598
9878
|
rule: counterattackByFireDrawRule,
|
|
9879
|
+
optionHandles: createVariableWidthAttackOptionHandles(counterattackByFireBodyCoordinates),
|
|
9599
9880
|
previewSample: {
|
|
9600
9881
|
controlPoints: [
|
|
9601
9882
|
[1.35, 0],
|
|
@@ -10381,6 +10662,299 @@ const DISRUPT_MISSION_TASK = defineControlMeasure({
|
|
|
10381
10662
|
previewSample: { controlPoints: [[0, .4], [0, -.4]] }
|
|
10382
10663
|
});
|
|
10383
10664
|
//#endregion
|
|
10665
|
+
//#region src/generators/cm34-mission-tasks/envelopment.ts
|
|
10666
|
+
const DEFAULT_ENVELOPMENT_OPTIONS = {
|
|
10667
|
+
arrowheadLengthRatio: .1,
|
|
10668
|
+
arrowheadWidthRatio: .12,
|
|
10669
|
+
labelPosition: .6,
|
|
10670
|
+
labelGapRatio: .18,
|
|
10671
|
+
resolution: 64
|
|
10672
|
+
};
|
|
10673
|
+
const ENVELOPMENT_METADATA = {
|
|
10674
|
+
id: "envelopment",
|
|
10675
|
+
name: "Envelopment",
|
|
10676
|
+
description: "A form of maneuver in which an attacking force avoids an enemy’s principal defense by attacking along an assailable flank.",
|
|
10677
|
+
entity: "Mission Tasks",
|
|
10678
|
+
entityType: "Envelopment",
|
|
10679
|
+
value: "343500",
|
|
10680
|
+
minCoordinates: 4,
|
|
10681
|
+
maxCoordinates: 4,
|
|
10682
|
+
geometry: "line",
|
|
10683
|
+
geometryTypes: ["MultiLineString", "Point"],
|
|
10684
|
+
paints: {
|
|
10685
|
+
stroke: true,
|
|
10686
|
+
fill: "none",
|
|
10687
|
+
text: true
|
|
10688
|
+
},
|
|
10689
|
+
drawRule: "Line31",
|
|
10690
|
+
capturesLabelSize: true,
|
|
10691
|
+
params: [
|
|
10692
|
+
{
|
|
10693
|
+
key: "arrowheadLengthRatio",
|
|
10694
|
+
presentationTier: "advanced",
|
|
10695
|
+
label: "Arrowhead length",
|
|
10696
|
+
description: "Arrowhead length as a ratio of the arc diameter",
|
|
10697
|
+
type: "number",
|
|
10698
|
+
min: .01,
|
|
10699
|
+
max: .5,
|
|
10700
|
+
step: .01
|
|
10701
|
+
},
|
|
10702
|
+
{
|
|
10703
|
+
key: "arrowheadWidthRatio",
|
|
10704
|
+
presentationTier: "advanced",
|
|
10705
|
+
label: "Arrowhead width",
|
|
10706
|
+
description: "Arrowhead width as a ratio of the arc diameter",
|
|
10707
|
+
type: "number",
|
|
10708
|
+
min: .01,
|
|
10709
|
+
max: .5,
|
|
10710
|
+
step: .01
|
|
10711
|
+
},
|
|
10712
|
+
{
|
|
10713
|
+
key: "labelPosition",
|
|
10714
|
+
presentationTier: "advanced",
|
|
10715
|
+
label: "Label position",
|
|
10716
|
+
description: "Position of the E label along the straight portion",
|
|
10717
|
+
type: "number",
|
|
10718
|
+
min: 0,
|
|
10719
|
+
max: 1,
|
|
10720
|
+
step: .01
|
|
10721
|
+
},
|
|
10722
|
+
{
|
|
10723
|
+
key: "labelGapRatio",
|
|
10724
|
+
presentationTier: "advanced",
|
|
10725
|
+
label: "Label gap",
|
|
10726
|
+
description: "Size of the straight-line cutout holding the E label",
|
|
10727
|
+
type: "number",
|
|
10728
|
+
min: 0,
|
|
10729
|
+
max: 1,
|
|
10730
|
+
step: .01
|
|
10731
|
+
},
|
|
10732
|
+
...ARC_RESOLUTION_PARAMS
|
|
10733
|
+
]
|
|
10734
|
+
};
|
|
10735
|
+
/** Generates the Envelopment mission task symbol (343500). */
|
|
10736
|
+
function createEnvelopment(coordinates, options = {}) {
|
|
10737
|
+
const [c1, c2, c3, c4] = coordinates;
|
|
10738
|
+
const p1 = project(c1[0], c1[1]);
|
|
10739
|
+
const p2 = project(c2[0], c2[1]);
|
|
10740
|
+
const diameterAnchor = project(c3[0], c3[1]);
|
|
10741
|
+
const p4 = project(c4[0], c4[1]);
|
|
10742
|
+
const straight = vecSub(p2, p1);
|
|
10743
|
+
const straightLength = vecMag(straight);
|
|
10744
|
+
const diameter = vecMag(vecSub(diameterAnchor, p2));
|
|
10745
|
+
if (straightLength < 1e-6 || diameter < 1e-6) return {
|
|
10746
|
+
type: "FeatureCollection",
|
|
10747
|
+
features: []
|
|
10748
|
+
};
|
|
10749
|
+
const straightDirection = vecNorm(straight);
|
|
10750
|
+
const p3 = vecAdd(p2, vecScale(straightDirection, diameter));
|
|
10751
|
+
const labelPoint = vecAdd(p1, vecScale(straight, clamp01(options.labelPosition ?? DEFAULT_ENVELOPMENT_OPTIONS.labelPosition)));
|
|
10752
|
+
const gapHalf = straightLength * clamp01(options.labelGapRatio ?? DEFAULT_ENVELOPMENT_OPTIONS.labelGapRatio) / 2;
|
|
10753
|
+
const gapStart = vecAdd(labelPoint, vecScale(straightDirection, -gapHalf));
|
|
10754
|
+
const gapEnd = vecAdd(labelPoint, vecScale(straightDirection, gapHalf));
|
|
10755
|
+
const center = midpoint(p2, p3);
|
|
10756
|
+
const startRadius = vecSub(p2, center);
|
|
10757
|
+
let bulgeRadius = [-startRadius[1], startRadius[0]];
|
|
10758
|
+
if (vecDot(vecSub(p4, center), bulgeRadius) < 0) bulgeRadius = vecScale(bulgeRadius, -1);
|
|
10759
|
+
const arc = sampleProjectedArc(center, startRadius, bulgeRadius, 0, Math.PI, normalizeArcResolution(options.resolution) / 2);
|
|
10760
|
+
const arrowhead = createArrowHead$1(p3, vecNorm(vecScale(bulgeRadius, -1)), diameter * Math.max(0, options.arrowheadLengthRatio ?? DEFAULT_ENVELOPMENT_OPTIONS.arrowheadLengthRatio), diameter * Math.max(0, options.arrowheadWidthRatio ?? DEFAULT_ENVELOPMENT_OPTIONS.arrowheadWidthRatio));
|
|
10761
|
+
return {
|
|
10762
|
+
type: "FeatureCollection",
|
|
10763
|
+
features: [{
|
|
10764
|
+
type: "Feature",
|
|
10765
|
+
properties: { part: "envelopment" },
|
|
10766
|
+
geometry: {
|
|
10767
|
+
type: "MultiLineString",
|
|
10768
|
+
coordinates: [
|
|
10769
|
+
[c1, unproject(gapStart[0], gapStart[1])],
|
|
10770
|
+
[unproject(gapEnd[0], gapEnd[1]), c2],
|
|
10771
|
+
arc,
|
|
10772
|
+
arrowhead
|
|
10773
|
+
]
|
|
10774
|
+
}
|
|
10775
|
+
}, createLabelFeature(labelPoint, "E", labelRotationAlong(straightDirection), options)]
|
|
10776
|
+
};
|
|
10777
|
+
}
|
|
10778
|
+
const ENVELOPMENT = defineControlMeasure({
|
|
10779
|
+
metadata: ENVELOPMENT_METADATA,
|
|
10780
|
+
generator: createEnvelopment,
|
|
10781
|
+
defaultOptions: DEFAULT_ENVELOPMENT_OPTIONS,
|
|
10782
|
+
rule: line31DrawRule,
|
|
10783
|
+
previewSample: {
|
|
10784
|
+
controlPoints: [
|
|
10785
|
+
[-.9, -.25],
|
|
10786
|
+
[-.15, -.25],
|
|
10787
|
+
[.85, -.25],
|
|
10788
|
+
[.35, .45]
|
|
10789
|
+
],
|
|
10790
|
+
options: { labelSize: 100 }
|
|
10791
|
+
}
|
|
10792
|
+
});
|
|
10793
|
+
//#endregion
|
|
10794
|
+
//#region src/generators/cm34-mission-tasks/infiltration.ts
|
|
10795
|
+
const DEFAULT_INFILTRATION_OPTIONS = {
|
|
10796
|
+
arrowheadLengthRatio: .1,
|
|
10797
|
+
arrowheadWidthRatio: .1,
|
|
10798
|
+
labelPosition: .6,
|
|
10799
|
+
labelGapRatio: .24,
|
|
10800
|
+
resolution: 64
|
|
10801
|
+
};
|
|
10802
|
+
const INFILTRATION_METADATA = {
|
|
10803
|
+
id: "infiltration",
|
|
10804
|
+
name: "Infiltration",
|
|
10805
|
+
description: "Moves forces through or into an area occupied by enemy forces without detection.",
|
|
10806
|
+
entity: "Mission Tasks",
|
|
10807
|
+
entityType: "Infiltration",
|
|
10808
|
+
value: "343800",
|
|
10809
|
+
minCoordinates: 3,
|
|
10810
|
+
maxCoordinates: 3,
|
|
10811
|
+
geometry: "line",
|
|
10812
|
+
geometryTypes: ["MultiLineString", "Point"],
|
|
10813
|
+
paints: {
|
|
10814
|
+
stroke: true,
|
|
10815
|
+
fill: "none",
|
|
10816
|
+
text: true
|
|
10817
|
+
},
|
|
10818
|
+
drawRule: "Line32",
|
|
10819
|
+
capturesLabelSize: true,
|
|
10820
|
+
params: [
|
|
10821
|
+
{
|
|
10822
|
+
key: "arrowheadLengthRatio",
|
|
10823
|
+
presentationTier: "advanced",
|
|
10824
|
+
label: "Arrowhead length",
|
|
10825
|
+
description: "Arrowhead length as a ratio of the Point 1–Point 3 span",
|
|
10826
|
+
type: "number",
|
|
10827
|
+
min: .01,
|
|
10828
|
+
max: .5,
|
|
10829
|
+
step: .01
|
|
10830
|
+
},
|
|
10831
|
+
{
|
|
10832
|
+
key: "arrowheadWidthRatio",
|
|
10833
|
+
presentationTier: "advanced",
|
|
10834
|
+
label: "Arrowhead width",
|
|
10835
|
+
description: "Arrowhead width as a ratio of the Point 1–Point 3 span",
|
|
10836
|
+
type: "number",
|
|
10837
|
+
min: .01,
|
|
10838
|
+
max: .5,
|
|
10839
|
+
step: .01
|
|
10840
|
+
},
|
|
10841
|
+
{
|
|
10842
|
+
key: "labelPosition",
|
|
10843
|
+
presentationTier: "advanced",
|
|
10844
|
+
label: "Label position",
|
|
10845
|
+
description: "Position of the IN label along the initial straight portion",
|
|
10846
|
+
type: "number",
|
|
10847
|
+
min: 0,
|
|
10848
|
+
max: 1,
|
|
10849
|
+
step: .01
|
|
10850
|
+
},
|
|
10851
|
+
{
|
|
10852
|
+
key: "labelGapRatio",
|
|
10853
|
+
presentationTier: "advanced",
|
|
10854
|
+
label: "Label gap",
|
|
10855
|
+
description: "Size of the initial-line cutout holding the IN label",
|
|
10856
|
+
type: "number",
|
|
10857
|
+
min: 0,
|
|
10858
|
+
max: 1,
|
|
10859
|
+
step: .01
|
|
10860
|
+
},
|
|
10861
|
+
...ARC_RESOLUTION_PARAMS
|
|
10862
|
+
]
|
|
10863
|
+
};
|
|
10864
|
+
const CENTERED_BEND_SINE = 2 * .1;
|
|
10865
|
+
const CENTERED_BEND_COSINE = Math.sqrt(1 - CENTERED_BEND_SINE * CENTERED_BEND_SINE);
|
|
10866
|
+
/**
|
|
10867
|
+
* Resolves the common tangent direction of the two Line32 quarter-circles.
|
|
10868
|
+
* For the doctrinal construction, PT. 1 and PT. 3 lie on parallel tangents on
|
|
10869
|
+
* opposite sides of PT. 2. Their midpoint offset from PT. 2 therefore lies on
|
|
10870
|
+
* that tangent axis. For a derived, centered PT. 2 the axis is mathematically
|
|
10871
|
+
* underdetermined, so a proportional doctrinal bend resolves it.
|
|
10872
|
+
*/
|
|
10873
|
+
function tangentDirection(p1, p2, p3) {
|
|
10874
|
+
const axis = vecSub(vecScale(p2, 2), vecAdd(p1, p3));
|
|
10875
|
+
const span = vecSub(p3, p1);
|
|
10876
|
+
const spanLength = vecMag(span);
|
|
10877
|
+
let direction;
|
|
10878
|
+
if (vecMag(axis) < spanLength * .02) {
|
|
10879
|
+
const chord = vecNorm(span);
|
|
10880
|
+
direction = [chord[0] * CENTERED_BEND_COSINE - chord[1] * CENTERED_BEND_SINE, chord[0] * CENTERED_BEND_SINE + chord[1] * CENTERED_BEND_COSINE];
|
|
10881
|
+
} else direction = vecNorm(axis);
|
|
10882
|
+
if (vecDot(direction, span) < 0) direction = vecScale(direction, -1);
|
|
10883
|
+
return direction;
|
|
10884
|
+
}
|
|
10885
|
+
/** Generates the Infiltration mission task symbol (343800). */
|
|
10886
|
+
function createInfiltration(coordinates, options = {}) {
|
|
10887
|
+
const [c1, c2, c3] = coordinates;
|
|
10888
|
+
const p1 = project(c1[0], c1[1]);
|
|
10889
|
+
const p2 = project(c2[0], c2[1]);
|
|
10890
|
+
const p3 = project(c3[0], c3[1]);
|
|
10891
|
+
const spanLength = vecMag(vecSub(p3, p1));
|
|
10892
|
+
if (spanLength < 1e-6) return {
|
|
10893
|
+
type: "FeatureCollection",
|
|
10894
|
+
features: []
|
|
10895
|
+
};
|
|
10896
|
+
const along = tangentDirection(p1, p2, p3);
|
|
10897
|
+
const normal = [-along[1], along[0]];
|
|
10898
|
+
const signedRadius = vecDot(vecSub(p1, p2), normal);
|
|
10899
|
+
const radius = Math.abs(signedRadius);
|
|
10900
|
+
if (radius < 1e-6) return {
|
|
10901
|
+
type: "FeatureCollection",
|
|
10902
|
+
features: []
|
|
10903
|
+
};
|
|
10904
|
+
const sideRadius = vecScale(normal, (signedRadius < 0 ? -1 : 1) * radius);
|
|
10905
|
+
const alongRadius = vecScale(along, radius);
|
|
10906
|
+
const firstCenter = vecSub(p2, alongRadius);
|
|
10907
|
+
const arcStart = vecAdd(firstCenter, sideRadius);
|
|
10908
|
+
const secondCenter = vecAdd(p2, alongRadius);
|
|
10909
|
+
const arcEnd = vecSub(secondCenter, sideRadius);
|
|
10910
|
+
const straight = vecSub(arcStart, p1);
|
|
10911
|
+
const straightLength = vecMag(straight);
|
|
10912
|
+
if (straightLength < 1e-6) return {
|
|
10913
|
+
type: "FeatureCollection",
|
|
10914
|
+
features: []
|
|
10915
|
+
};
|
|
10916
|
+
const arcResolution = normalizeArcResolution(options.resolution) / 2;
|
|
10917
|
+
const firstArc = sampleProjectedArc(firstCenter, sideRadius, alongRadius, 0, Math.PI / 2, arcResolution);
|
|
10918
|
+
const secondArc = sampleProjectedArc(secondCenter, vecScale(alongRadius, -1), vecScale(sideRadius, -1), 0, Math.PI / 2, arcResolution);
|
|
10919
|
+
const labelPoint = vecAdd(p1, vecScale(straight, clamp01(options.labelPosition ?? DEFAULT_INFILTRATION_OPTIONS.labelPosition)));
|
|
10920
|
+
const gapHalf = straightLength * clamp01(options.labelGapRatio ?? DEFAULT_INFILTRATION_OPTIONS.labelGapRatio) / 2;
|
|
10921
|
+
const gapStart = vecAdd(labelPoint, vecScale(along, -gapHalf));
|
|
10922
|
+
const gapEnd = vecAdd(labelPoint, vecScale(along, gapHalf));
|
|
10923
|
+
const arrowhead = createArrowHead$1(p3, along, spanLength * Math.max(0, options.arrowheadLengthRatio ?? DEFAULT_INFILTRATION_OPTIONS.arrowheadLengthRatio), spanLength * Math.max(0, options.arrowheadWidthRatio ?? DEFAULT_INFILTRATION_OPTIONS.arrowheadWidthRatio));
|
|
10924
|
+
return {
|
|
10925
|
+
type: "FeatureCollection",
|
|
10926
|
+
features: [{
|
|
10927
|
+
type: "Feature",
|
|
10928
|
+
properties: { part: "infiltration" },
|
|
10929
|
+
geometry: {
|
|
10930
|
+
type: "MultiLineString",
|
|
10931
|
+
coordinates: [
|
|
10932
|
+
[c1, unproject(gapStart[0], gapStart[1])],
|
|
10933
|
+
[unproject(gapEnd[0], gapEnd[1]), unproject(arcStart[0], arcStart[1])],
|
|
10934
|
+
firstArc,
|
|
10935
|
+
secondArc,
|
|
10936
|
+
[unproject(arcEnd[0], arcEnd[1]), c3],
|
|
10937
|
+
arrowhead
|
|
10938
|
+
]
|
|
10939
|
+
}
|
|
10940
|
+
}, createLabelFeature(labelPoint, "IN", labelRotationAlong(along), options)]
|
|
10941
|
+
};
|
|
10942
|
+
}
|
|
10943
|
+
const INFILTRATION = defineControlMeasure({
|
|
10944
|
+
metadata: INFILTRATION_METADATA,
|
|
10945
|
+
generator: createInfiltration,
|
|
10946
|
+
defaultOptions: DEFAULT_INFILTRATION_OPTIONS,
|
|
10947
|
+
rule: line32DrawRule,
|
|
10948
|
+
previewSample: {
|
|
10949
|
+
controlPoints: [
|
|
10950
|
+
[-.9, .25],
|
|
10951
|
+
[-.05, 0],
|
|
10952
|
+
[.9, -.25]
|
|
10953
|
+
],
|
|
10954
|
+
options: { labelSize: 100 }
|
|
10955
|
+
}
|
|
10956
|
+
});
|
|
10957
|
+
//#endregion
|
|
10384
10958
|
//#region src/generators/cm34-mission-tasks/guard.ts
|
|
10385
10959
|
const DEFAULT_GUARD_OPTIONS = DEFAULT_SECURITY_TASK_OPTIONS;
|
|
10386
10960
|
const GUARD_METADATA = {
|
|
@@ -12742,7 +13316,8 @@ const FORTIFIED_AREA = defineControlMeasure({
|
|
|
12742
13316
|
//#endregion
|
|
12743
13317
|
//#region src/generators/cm15-maneuver-areas/maneuver-arrow-task-shared.ts
|
|
12744
13318
|
const DEFAULT_MANEUVER_ARROW_TASK_OPTIONS = {
|
|
12745
|
-
shaftWidthRatio: DEFAULT_SHAFT_WIDTH_RATIO,
|
|
13319
|
+
shaftWidthRatio: DEFAULT_SHAFT_WIDTH_RATIO$1,
|
|
13320
|
+
rearWidthRatio: DEFAULT_REAR_WIDTH_RATIO,
|
|
12746
13321
|
smooth: false,
|
|
12747
13322
|
smoothResolution: 5,
|
|
12748
13323
|
crossbarLengthRatio: 1.1,
|
|
@@ -12751,10 +13326,14 @@ const DEFAULT_MANEUVER_ARROW_TASK_OPTIONS = {
|
|
|
12751
13326
|
labelPadding: 0
|
|
12752
13327
|
};
|
|
12753
13328
|
function createManeuverArrowTask(coordinates, options, textAmplifiers, config) {
|
|
12754
|
-
const
|
|
13329
|
+
const merged = {
|
|
12755
13330
|
...DEFAULT_MANEUVER_ARROW_TASK_OPTIONS,
|
|
12756
13331
|
...options
|
|
12757
13332
|
};
|
|
13333
|
+
const resolved = {
|
|
13334
|
+
...merged,
|
|
13335
|
+
rearWidthRatio: resolveRearWidthRatio(options, merged.shaftWidthRatio)
|
|
13336
|
+
};
|
|
12758
13337
|
const { geometry } = processAttackGeometry(coordinates, resolved);
|
|
12759
13338
|
if (!geometry) return {
|
|
12760
13339
|
type: "FeatureCollection",
|
|
@@ -12775,17 +13354,22 @@ function createManeuverArrowTask(coordinates, options, textAmplifiers, config) {
|
|
|
12775
13354
|
};
|
|
12776
13355
|
const tipVector = vecSub(geometry.ptTip, geometry.ptBase);
|
|
12777
13356
|
const tipDirection = vecNorm(tipVector);
|
|
12778
|
-
const
|
|
13357
|
+
const headWidth = vecMag(vecSub(geometry.headRing[0], geometry.headRing[2]));
|
|
12779
13358
|
let crossbarCenter;
|
|
12780
13359
|
let crossbarAlong;
|
|
13360
|
+
let crossbarBaseWidth = headWidth;
|
|
12781
13361
|
if (config.crossbarAt === "tip") {
|
|
12782
13362
|
crossbarCenter = geometry.ptTip;
|
|
12783
13363
|
crossbarAlong = tipDirection;
|
|
12784
13364
|
} else {
|
|
12785
|
-
const
|
|
13365
|
+
const shaftPosition = Number.isFinite(config.crossbarAt.shaftPosition) ? clamp01(config.crossbarAt.shaftPosition) : 0;
|
|
13366
|
+
const crossbarFrame = pointAlongPolyline(segments, totalLength, shaftPosition);
|
|
12786
13367
|
crossbarCenter = crossbarFrame.point;
|
|
12787
13368
|
crossbarAlong = crossbarFrame.along;
|
|
13369
|
+
const rearWidth = vecMag(vecSub(geometry.shaftLeft[0], geometry.shaftRight[0]));
|
|
13370
|
+
crossbarBaseWidth = rearWidth + (vecMag(vecSub(geometry.shaftLeft.at(-1), geometry.shaftRight.at(-1))) - rearWidth) * shaftPosition;
|
|
12788
13371
|
}
|
|
13372
|
+
const crossbarLength = crossbarBaseWidth * Math.max(1, resolved.crossbarLengthRatio);
|
|
12789
13373
|
const crossbarPerp = [-crossbarAlong[1], crossbarAlong[0]];
|
|
12790
13374
|
const crossbar = [vecAdd(crossbarCenter, vecScale(crossbarPerp, crossbarLength / 2)), vecSub(crossbarCenter, vecScale(crossbarPerp, crossbarLength / 2))];
|
|
12791
13375
|
const features = [{
|
|
@@ -12925,6 +13509,7 @@ const FRONTAL_ATTACK = defineControlMeasure({
|
|
|
12925
13509
|
generator: createFrontalAttack,
|
|
12926
13510
|
defaultOptions: DEFAULT_FRONTAL_ATTACK_OPTIONS,
|
|
12927
13511
|
rule: axis1DrawRule,
|
|
13512
|
+
optionHandles: variableWidthAttackOptionHandles,
|
|
12928
13513
|
previewSample: {
|
|
12929
13514
|
controlPoints: [
|
|
12930
13515
|
[1, 0],
|
|
@@ -13962,7 +14547,8 @@ const NO_FIRE_AREA_IRREGULAR = defineControlMeasure({
|
|
|
13962
14547
|
//#endregion
|
|
13963
14548
|
//#region src/generators/cm15-maneuver-areas/mainAttack.ts
|
|
13964
14549
|
const DEFAULT_MAIN_ATTACK_OPTIONS = {
|
|
13965
|
-
shaftWidthRatio: DEFAULT_SHAFT_WIDTH_RATIO,
|
|
14550
|
+
shaftWidthRatio: DEFAULT_SHAFT_WIDTH_RATIO$1,
|
|
14551
|
+
rearWidthRatio: DEFAULT_REAR_WIDTH_RATIO,
|
|
13966
14552
|
smooth: false,
|
|
13967
14553
|
smoothResolution: 5
|
|
13968
14554
|
};
|
|
@@ -13990,7 +14576,7 @@ const MAIN_ATTACK_METADATA = {
|
|
|
13990
14576
|
*
|
|
13991
14577
|
* The symbol consists of a single MultiLineString feature containing:
|
|
13992
14578
|
* 1. **Arrowhead**: A line forming a "chevron" or "roof" shape.
|
|
13993
|
-
* 2. **Shaft**: Two
|
|
14579
|
+
* 2. **Shaft**: Two boundary lines behind the arrow, optionally flared toward the rear.
|
|
13994
14580
|
*
|
|
13995
14581
|
* The shaft is calculated to terminate exactly where it touches the inner walls
|
|
13996
14582
|
* of the arrowhead, creating a seamless connection.
|
|
@@ -14029,7 +14615,8 @@ const MAIN_ATTACK = defineControlMeasure({
|
|
|
14029
14615
|
metadata: MAIN_ATTACK_METADATA,
|
|
14030
14616
|
generator: createMainAttack,
|
|
14031
14617
|
defaultOptions: DEFAULT_MAIN_ATTACK_OPTIONS,
|
|
14032
|
-
rule: axis1DrawRule
|
|
14618
|
+
rule: axis1DrawRule,
|
|
14619
|
+
optionHandles: variableWidthAttackOptionHandles
|
|
14033
14620
|
});
|
|
14034
14621
|
//#endregion
|
|
14035
14622
|
//#region src/generators/cm27-protection-areas/mine-types.ts
|
|
@@ -16226,6 +16813,8 @@ const DEFINITIONS = {
|
|
|
16226
16813
|
delay: DELAY,
|
|
16227
16814
|
disengage: DISENGAGE,
|
|
16228
16815
|
"disrupt-mission-task": DISRUPT_MISSION_TASK,
|
|
16816
|
+
envelopment: ENVELOPMENT,
|
|
16817
|
+
infiltration: INFILTRATION,
|
|
16229
16818
|
guard: GUARD,
|
|
16230
16819
|
isolate: ISOLATE,
|
|
16231
16820
|
penetrate: PENETRATE,
|
|
@@ -16246,6 +16835,7 @@ const DEFINITIONS = {
|
|
|
16246
16835
|
generator: createTurningMovement,
|
|
16247
16836
|
defaultOptions: DEFAULT_TURNING_MOVEMENT_OPTIONS,
|
|
16248
16837
|
rule: axis1DrawRule,
|
|
16838
|
+
optionHandles: variableWidthAttackOptionHandles,
|
|
16249
16839
|
previewSample: {
|
|
16250
16840
|
controlPoints: [
|
|
16251
16841
|
[1, 0],
|