@code3d/core 0.0.1-alpha.0 → 0.0.1-alpha.3

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.
Files changed (131) hide show
  1. package/README.md +223 -786
  2. package/THIRD_PARTY.md +40 -0
  3. package/bld/THIRD_PARTY_NOTICES.txt +182 -0
  4. package/bld/chunks/chunk-BF55AIEQ.js +31 -0
  5. package/bld/chunks/chunk-BF55AIEQ.js.map +7 -0
  6. package/bld/chunks/chunk-P5VGKAEM.js +30 -0
  7. package/bld/chunks/chunk-P5VGKAEM.js.map +7 -0
  8. package/bld/chunks/chunk-RPY2WVCB.js +24834 -0
  9. package/bld/chunks/chunk-RPY2WVCB.js.map +7 -0
  10. package/bld/chunks/chunk-TJIN6S4R.js +1 -0
  11. package/bld/chunks/chunk-TJIN6S4R.js.map +7 -0
  12. package/bld/chunks/chunk-U5G2VU2I.js +19 -0
  13. package/bld/chunks/chunk-U5G2VU2I.js.map +7 -0
  14. package/bld/library/cached.d.ts +33 -0
  15. package/bld/library/cached.d.ts.map +1 -0
  16. package/bld/library/font.d.ts +39 -0
  17. package/bld/library/font.d.ts.map +1 -0
  18. package/bld/library/google-font.d.ts +14 -0
  19. package/bld/library/google-font.d.ts.map +1 -0
  20. package/bld/library/index.d.ts +8 -2
  21. package/bld/library/index.d.ts.map +1 -1
  22. package/bld/library/index.js +60 -3
  23. package/bld/library/index.js.map +7 -1
  24. package/bld/library/kernel-artifact-codec.d.ts.map +1 -1
  25. package/bld/library/kernel-cache.d.ts +24 -6
  26. package/bld/library/kernel-cache.d.ts.map +1 -1
  27. package/bld/library/material.d.ts +1 -1
  28. package/bld/library/material.d.ts.map +1 -1
  29. package/bld/library/open-cascade.d.ts +4 -0
  30. package/bld/library/open-cascade.d.ts.map +1 -0
  31. package/bld/library/replicad.d.ts +3 -2
  32. package/bld/library/replicad.d.ts.map +1 -1
  33. package/bld/library/replicad.js +10 -24
  34. package/bld/library/replicad.js.map +7 -1
  35. package/bld/library/retained-memory.d.ts.map +1 -1
  36. package/bld/library/runtime.d.ts +146 -80
  37. package/bld/library/runtime.d.ts.map +1 -1
  38. package/bld/library/sketch-solver.d.ts +1 -1
  39. package/bld/library/sketch-solver.d.ts.map +1 -1
  40. package/bld/library/sketch.d.ts +9 -1
  41. package/bld/library/sketch.d.ts.map +1 -1
  42. package/bld/library/text.d.ts +53 -0
  43. package/bld/library/text.d.ts.map +1 -0
  44. package/bld/library/three.js +3 -3
  45. package/bld/library/three.js.map +7 -1
  46. package/bld/library/topology.d.ts.map +1 -1
  47. package/bld/node/index.d.ts.map +1 -1
  48. package/bld/node/index.js +62 -14
  49. package/bld/node/index.js.map +7 -1
  50. package/bld/node/replicad.js +13 -3
  51. package/bld/node/replicad.js.map +7 -1
  52. package/bld/tooling/index.d.ts +17 -15
  53. package/bld/tooling/index.d.ts.map +1 -1
  54. package/bld/tooling/index.js +147 -19
  55. package/bld/tooling/index.js.map +7 -1
  56. package/package.json +18 -6
  57. package/src/library/cached.ts +132 -0
  58. package/src/library/font.ts +186 -0
  59. package/src/library/google-font.ts +97 -0
  60. package/src/library/index.ts +8 -3
  61. package/src/library/kernel-artifact-codec.ts +129 -20
  62. package/src/library/kernel-cache.ts +128 -34
  63. package/src/library/loft.ts +4 -0
  64. package/src/library/material.ts +3 -4
  65. package/src/library/open-cascade.ts +13 -0
  66. package/src/library/replicad.ts +5 -5
  67. package/src/library/retained-memory.ts +6 -1
  68. package/src/library/runtime.ts +2064 -1730
  69. package/src/library/sketch-solver.ts +3 -1
  70. package/src/library/sketch.ts +81 -13
  71. package/src/library/text.ts +477 -0
  72. package/src/library/topology.ts +24 -0
  73. package/src/library/tsconfig.json +2 -1
  74. package/src/node/index.ts +14 -3
  75. package/src/node/tsconfig.json +2 -1
  76. package/src/tooling/index.ts +48 -46
  77. package/src/tooling/tsconfig.json +2 -1
  78. package/bld/library/alignment-geometry.js +0 -322
  79. package/bld/library/alignment-geometry.js.map +0 -1
  80. package/bld/library/bound-solver.js +0 -252
  81. package/bld/library/bound-solver.js.map +0 -1
  82. package/bld/library/extrude.js +0 -28
  83. package/bld/library/extrude.js.map +0 -1
  84. package/bld/library/kernel-artifact-codec.js +0 -148
  85. package/bld/library/kernel-artifact-codec.js.map +0 -1
  86. package/bld/library/kernel-cache.js +0 -245
  87. package/bld/library/kernel-cache.js.map +0 -1
  88. package/bld/library/kernel-shapes.js +0 -108
  89. package/bld/library/kernel-shapes.js.map +0 -1
  90. package/bld/library/loft.js +0 -161
  91. package/bld/library/loft.js.map +0 -1
  92. package/bld/library/material.js +0 -176
  93. package/bld/library/material.js.map +0 -1
  94. package/bld/library/model-color.js +0 -36
  95. package/bld/library/model-color.js.map +0 -1
  96. package/bld/library/open-cascade-error.js +0 -22
  97. package/bld/library/open-cascade-error.js.map +0 -1
  98. package/bld/library/relation-solver.js +0 -258
  99. package/bld/library/relation-solver.js.map +0 -1
  100. package/bld/library/retained-memory.js +0 -27
  101. package/bld/library/retained-memory.js.map +0 -1
  102. package/bld/library/runtime.js +0 -2954
  103. package/bld/library/runtime.js.map +0 -1
  104. package/bld/library/shell.js +0 -221
  105. package/bld/library/shell.js.map +0 -1
  106. package/bld/library/sketch-curve-intersections.js +0 -117
  107. package/bld/library/sketch-curve-intersections.js.map +0 -1
  108. package/bld/library/sketch-curves.js +0 -89
  109. package/bld/library/sketch-curves.js.map +0 -1
  110. package/bld/library/sketch-drag-rules.js +0 -522
  111. package/bld/library/sketch-drag-rules.js.map +0 -1
  112. package/bld/library/sketch-face.js +0 -72
  113. package/bld/library/sketch-face.js.map +0 -1
  114. package/bld/library/sketch-incidence.js +0 -114
  115. package/bld/library/sketch-incidence.js.map +0 -1
  116. package/bld/library/sketch-precision.js +0 -55
  117. package/bld/library/sketch-precision.js.map +0 -1
  118. package/bld/library/sketch-regions.js +0 -193
  119. package/bld/library/sketch-regions.js.map +0 -1
  120. package/bld/library/sketch-solver.js +0 -578
  121. package/bld/library/sketch-solver.js.map +0 -1
  122. package/bld/library/sketch.js +0 -650
  123. package/bld/library/sketch.js.map +0 -1
  124. package/bld/library/spatial.js +0 -195
  125. package/bld/library/spatial.js.map +0 -1
  126. package/bld/library/topology-id.js +0 -67
  127. package/bld/library/topology-id.js.map +0 -1
  128. package/bld/library/topology-inspection.js +0 -267
  129. package/bld/library/topology-inspection.js.map +0 -1
  130. package/bld/library/topology.js +0 -455
  131. package/bld/library/topology.js.map +0 -1
@@ -1,89 +0,0 @@
1
- import { sketchRelativePrecision } from './sketch-precision.js';
2
- const tau = Math.PI * 2;
3
- export const sketchPositiveAngle = (angle) => ((angle % tau) + tau) % tau;
4
- export function sketchArcGeometry(center, start, end, direction) {
5
- const angle = Math.atan2(start[1] - center[1], start[0] - center[0]);
6
- const endAngle = Math.atan2(end[1] - center[1], end[0] - center[0]);
7
- const sign = direction === 'ccw' ? 1 : -1;
8
- return {
9
- kind: 'arc',
10
- center,
11
- radius: Math.hypot(start[0] - center[0], start[1] - center[1]),
12
- start: angle,
13
- sweep: sign * sketchPositiveAngle(sign * (endAngle - angle)),
14
- };
15
- }
16
- export function sketchCurveGeometry(entity, point) {
17
- switch (entity.kind) {
18
- case 'point':
19
- return undefined;
20
- case 'line':
21
- return {
22
- kind: 'line',
23
- points: [point(entity.points[0]), point(entity.points[1])],
24
- };
25
- case 'circle':
26
- return {
27
- kind: 'circle',
28
- center: point(entity.center),
29
- radius: entity.radius,
30
- };
31
- case 'arc':
32
- return {
33
- ...sketchArcGeometry(point(entity.center), point(entity.points[0]), point(entity.points[1]), entity.direction),
34
- radius: entity.radius,
35
- };
36
- }
37
- }
38
- export function sketchCurvePosition(curve, t) {
39
- if (curve.kind === 'line') {
40
- const [a, b] = curve.points;
41
- return [a[0] + t * (b[0] - a[0]), a[1] + t * (b[1] - a[1])];
42
- }
43
- const angle = curve.kind === 'circle' ? t * tau : curve.start + t * curve.sweep;
44
- return [
45
- curve.center[0] + curve.radius * Math.cos(angle),
46
- curve.center[1] + curve.radius * Math.sin(angle),
47
- ];
48
- }
49
- /** Nearest point on the finite curve, including arc endpoints (not its full circle). */
50
- export function sketchCurveClosestParameter(curve, position) {
51
- if (curve.kind === 'line') {
52
- const [a, b] = curve.points, dx = b[0] - a[0], dy = b[1] - a[1];
53
- return Math.max(0, Math.min(1, ((position[0] - a[0]) * dx + (position[1] - a[1]) * dy) /
54
- (dx * dx + dy * dy) || 0));
55
- }
56
- const angle = Math.atan2(position[1] - curve.center[1], position[0] - curve.center[0]);
57
- if (curve.kind === 'circle')
58
- return sketchPositiveAngle(angle) / tau;
59
- const sign = Math.sign(curve.sweep), sweep = Math.abs(curve.sweep);
60
- const distance = sketchPositiveAngle(sign * (angle - curve.start));
61
- if (distance <= sweep)
62
- return sweep ? distance / sweep : 0;
63
- return distance - sweep < tau - distance ? 1 : 0;
64
- }
65
- export function sketchCurveBounds(curve) {
66
- if (curve.kind === 'line')
67
- return curve.points;
68
- const candidates = [0, Math.PI / 2, Math.PI, (3 * Math.PI) / 2].filter(angle => curve.kind === 'circle' ||
69
- sketchPositiveAngle(Math.sign(curve.sweep) * (angle - curve.start)) <=
70
- Math.abs(curve.sweep));
71
- return [
72
- ...(curve.kind === 'arc'
73
- ? [sketchCurvePosition(curve, 0), sketchCurvePosition(curve, 1)]
74
- : []),
75
- ...candidates.map((angle) => [
76
- curve.center[0] + curve.radius * Math.cos(angle),
77
- curve.center[1] + curve.radius * Math.sin(angle),
78
- ]),
79
- ];
80
- }
81
- export function sketchCurveTolerance(curve) {
82
- const size = curve.kind === 'line'
83
- ? Math.hypot(curve.points[1][0] - curve.points[0][0], curve.points[1][1] - curve.points[0][1])
84
- : curve.radius;
85
- return Math.max(size * sketchRelativePrecision, Number.EPSILON *
86
- 16 *
87
- Math.max(...sketchCurveBounds(curve).flatMap(p => p.map(Math.abs))));
88
- }
89
- //# sourceMappingURL=sketch-curves.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"sketch-curves.js","sourceRoot":"","sources":["../../src/library/sketch-curves.ts"],"names":[],"mappings":"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"}
@@ -1,522 +0,0 @@
1
- import { solveSketchProblem, } from './sketch-solver.js';
2
- import { sketchIncidences, sketchIncidenceGeometry, sketchIncidencePoints, sketchIncidenceCurve, sketchIncidenceBoundary, } from './sketch-incidence.js';
3
- import { sketchCurveClosestParameter, sketchCurvePosition, } from './sketch-curves.js';
4
- /** Dispatch knows neither point roles nor connected components. */
5
- export function createSketchDragSession(context, rules = dragRules) {
6
- for (const rule of rules) {
7
- const session = rule(context);
8
- if (session)
9
- return session;
10
- }
11
- throw new Error('No sketch drag rule matched the gesture.');
12
- }
13
- export function solveSketchDrag(current, target, reference, geometry) {
14
- const plan = createSketchDragSession({ reference, target, geometry })(current, target);
15
- // Resolve remaining freedom only after every gesture-specific preference.
16
- // Earlier stages' achieved positions stay fixed; references never follow
17
- // the iterative seed. Solver points are canonical, so aliases add no weight.
18
- const stages = [
19
- ...plan.stages,
20
- {
21
- objectives: reached => reference.points.flatMap((_, point) => (target.kind === 'point' && point === target.point) ||
22
- reached.points[point].locked.every(Boolean)
23
- ? []
24
- : [anchor(reference, point)]),
25
- },
26
- ];
27
- const bounds = new Map();
28
- for (;;) {
29
- const problem = {
30
- ...plan.problem,
31
- constraints: [
32
- ...plan.problem.constraints,
33
- ...[...bounds].map(([contact, endpoint]) => ({
34
- kind: 'coincident',
35
- points: [contact.point, endpoint],
36
- })),
37
- ],
38
- };
39
- const result = solveSketchDragPlan({ ...plan, problem, stages });
40
- const solved = sketchIncidenceGeometry(problem, result);
41
- const outside = plan.incidences?.flatMap(contact => {
42
- const endpoint = bounds.has(contact)
43
- ? undefined
44
- : sketchIncidenceBoundary(solved, contact);
45
- return endpoint === undefined ? [] : [{ contact, endpoint }];
46
- })[0];
47
- if (!outside)
48
- return result;
49
- bounds.set(outside.contact, outside.endpoint);
50
- }
51
- }
52
- /** Each stage keeps the preceding stage's chosen parameter values, not all
53
- * equivalent optima. These locks belong to this solve, never the next frame. */
54
- export function solveSketchDragPlan(plan) {
55
- let problem = plan.problem;
56
- let result;
57
- for (const stage of plan.stages) {
58
- const objectives = stage.objectives(problem);
59
- if (result && !objectives.length)
60
- continue;
61
- // Stage output is an iterative seed, not a new authored exact value.
62
- // Otherwise cleanup can undo a later hard solve to restore an earlier tail.
63
- const solved = solveSketchProblem(stage.seed?.(problem, objectives) ?? problem, objectives, plan.problem);
64
- result = solved;
65
- const points = new Set(objectives.filter(o => o.kind === 'point').map(o => o.point));
66
- const radii = objectives.filter(o => o.kind === 'radius');
67
- problem = {
68
- ...problem,
69
- points: problem.points.map((point, index) => ({
70
- position: solved.positions[index],
71
- locked: points.has(index) ? [true, true] : point.locked,
72
- })),
73
- circles: problem.circles.map((circle, index) => ({
74
- ...circle,
75
- radius: solved.radii[index],
76
- locked: circle.locked ||
77
- radii.some(o => o.curve === 'circle' && o.index === index),
78
- })),
79
- arcs: problem.arcs.map((arc, index) => ({
80
- ...arc,
81
- radius: solved.arcRadii[index],
82
- locked: arc.locked || radii.some(o => o.curve === 'arc' && o.index === index),
83
- })),
84
- };
85
- }
86
- return result;
87
- }
88
- const radiusRule = ({ reference, target }) => {
89
- if (target.kind !== 'radius')
90
- return;
91
- const curve = (target.curve === 'arc' ? reference.arcs : reference.circles)[target.index];
92
- return (current, updated) => {
93
- const next = updated;
94
- const suggestions = new Map();
95
- const dimension = reference.constraints.find(c => c.kind === 'radius' && c.curve === next.curve && c.index === next.index);
96
- const radius = curve.locked
97
- ? curve.radius
98
- : dimension?.kind === 'radius'
99
- ? dimension.value
100
- : next.value;
101
- const center = reference.points[curve.center].position;
102
- if (next.curve === 'arc') {
103
- const arc = reference.arcs[next.index];
104
- for (const point of arc.points)
105
- suggest(suggestions, point, radialPosition(center, reference.points[point].position, radius));
106
- }
107
- for (const c of reference.constraints)
108
- if (c.kind === 'pointOnCircle' &&
109
- c.curve === next.curve &&
110
- c.index === next.index)
111
- suggest(suggestions, c.points[0], radialPosition(center, reference.points[c.points[0]].position, radius));
112
- return {
113
- problem: seed(current, suggestions, next.curve === 'arc' ? new Map([[next.index, radius]]) : new Map()),
114
- stages: [
115
- { objectives: () => [{ ...next, weight: 1 }] },
116
- {
117
- objectives: () => [
118
- anchor(reference, curve.center),
119
- ...[...suggestions].map(([point, positions]) => ({
120
- ...anchor(reference, point),
121
- position: positions[0],
122
- })),
123
- ],
124
- },
125
- ],
126
- };
127
- };
128
- };
129
- const centerRule = context => {
130
- if (context.target.kind !== 'point')
131
- return;
132
- const scope = connected(neighbors(context.reference), context.target.point);
133
- const policy = translationPolicy(context.reference, context.target.point, scope);
134
- if (policy)
135
- return pointSession(context, [policy]);
136
- return;
137
- };
138
- const junctionRule = context => {
139
- const { reference, target } = context;
140
- if (target.kind !== 'point' ||
141
- reference.points[target.point].locked.some(Boolean))
142
- return;
143
- // This rule, not the dispatcher, defines an unconstrained articulation point.
144
- if (reference.constraints.some(c => !(c.kind === 'pointOnCircle' && c.points[1] === target.point) &&
145
- constraintPoints(reference, c).includes(target.point)))
146
- return;
147
- const scope = connected(neighbors(reference), target.point);
148
- const remaining = new Set([...scope].filter(p => p !== target.point));
149
- const graph = neighbors(reference, target.point);
150
- const parts = [];
151
- for (const point of remaining) {
152
- const part = connected(graph, point);
153
- for (const member of part)
154
- remaining.delete(member);
155
- part.add(target.point);
156
- parts.push(part);
157
- }
158
- if (parts.length < 2)
159
- return;
160
- return pointSession(context, parts.map(part => translationPolicy(reference, target.point, part) ??
161
- endpointPolicy(reference, target.point, part)));
162
- };
163
- const endpointRule = context => {
164
- if (context.target.kind !== 'point')
165
- return;
166
- const scope = connected(neighbors(context.reference), context.target.point);
167
- return pointSession(context, [
168
- endpointPolicy(context.reference, context.target.point, scope),
169
- ]);
170
- };
171
- const motionRules = [
172
- radiusRule,
173
- centerRule,
174
- junctionRule,
175
- endpointRule,
176
- ];
177
- /** This rule supplies geometric connections; the dispatcher still knows no roles. */
178
- const incidenceRule = ({ reference, target, geometry }) => {
179
- const original = geometry ?? sketchIncidenceGeometry(reference);
180
- const contacts = sketchIncidences(original);
181
- if (!contacts.length)
182
- return;
183
- const constraints = contacts.map((contact) => contact.kind === 'line'
184
- ? {
185
- kind: 'pointOnLine',
186
- points: [contact.point, ...original.lines[contact.index]],
187
- }
188
- : {
189
- kind: 'pointOnCircle',
190
- points: [
191
- contact.point,
192
- (contact.kind === 'circle' ? original.circles : original.arcs)[contact.index].center,
193
- ],
194
- curve: contact.kind,
195
- index: contact.index,
196
- });
197
- const session = createSketchDragSession({
198
- reference: {
199
- ...reference,
200
- constraints: [...reference.constraints, ...constraints],
201
- },
202
- target,
203
- }, motionRules);
204
- return (current, updated) => {
205
- // A polar seed follows the requested angle, including an exact half-turn,
206
- // where a distance equation's local derivative alone cannot pick a branch.
207
- const suggestions = new Map();
208
- const currentGeometry = sketchIncidenceGeometry(current);
209
- for (const contact of contacts) {
210
- if (contact.kind === 'line' ||
211
- updated.kind !== 'point' ||
212
- contact.point !== updated.point)
213
- continue;
214
- const curve = sketchIncidenceCurve(currentGeometry, contact);
215
- if (curve.kind !== 'line' &&
216
- updated.position.every((v, i) => v === curve.center[i]))
217
- continue;
218
- const t = sketchCurveClosestParameter(curve, updated.position);
219
- // Finite endpoints already have authoritative coordinates. Re-evaluating
220
- // sin/cos would introduce a new seed such as cos(pi/2) instead of exact 0.
221
- const position = contact.kind === 'arc' && (t === 0 || t === 1)
222
- ? currentGeometry.points[currentGeometry.arcs[contact.index].points[t]]
223
- : sketchCurvePosition(curve, t);
224
- suggest(suggestions, contact.point, position);
225
- }
226
- const plan = session({
227
- ...seed(current, suggestions, new Map()),
228
- constraints: [...current.constraints, ...constraints],
229
- }, updated);
230
- // An interior point can slide without moving either end. Both endpoints
231
- // are soft references, so they can yield when the point moves off the line.
232
- const preferences = updated.kind === 'point'
233
- ? new Set(contacts
234
- .filter(c => c.point === updated.point)
235
- .flatMap(c => sketchIncidencePoints(original, c).slice(1)))
236
- : new Set();
237
- // Incidence leaves a tangential freedom. Keep followers near their
238
- // gesture-start positions instead of accepting an arbitrary slide.
239
- for (const { point } of contacts)
240
- preferences.add(point);
241
- const stages = [...plan.stages];
242
- const last = stages.at(-1);
243
- stages[stages.length - 1] = {
244
- ...last,
245
- objectives: current => {
246
- const objectives = last.objectives(current);
247
- const present = new Set(objectives.filter(o => o.kind === 'point').map(o => o.point));
248
- if (updated.kind === 'point')
249
- present.add(updated.point);
250
- return [
251
- ...objectives,
252
- ...[...preferences]
253
- .filter(p => !present.has(p) && !current.points[p].locked.every(Boolean))
254
- .map(p => anchor(reference, p)),
255
- ];
256
- },
257
- };
258
- return {
259
- ...plan,
260
- incidences: contacts,
261
- stages,
262
- };
263
- };
264
- };
265
- const dragRules = [incidenceRule, ...motionRules];
266
- function pointSession(context, policies) {
267
- const { reference } = context;
268
- const translations = policies.filter(p => p.kind === 'translation');
269
- const endpoints = policies.filter(p => p.kind === 'endpoint');
270
- // An endpoint can also be another curve's center. Its incident arcs retain
271
- // their center preference regardless of the point's other motion roles.
272
- const arcs = reference.arcs.flatMap((arc, index) => context.target.kind === 'point' && arc.points.includes(context.target.point)
273
- ? [index]
274
- : []);
275
- const centers = new Set(arcs.map(index => reference.arcs[index].center));
276
- const anchors = [...new Set(endpoints.flatMap(p => p.anchors))].filter(point => !centers.has(point));
277
- const translated = [...new Set(translations.flatMap(p => p.points))];
278
- const preservedRadii = translations.flatMap(p => p.radii);
279
- return (current, updated) => {
280
- const target = updated;
281
- const suggestions = new Map();
282
- const radii = new Map();
283
- const from = reference.points[target.point].position;
284
- for (const index of arcs) {
285
- const arc = reference.arcs[index];
286
- const center = reference.points[arc.center].position;
287
- const dimension = reference.constraints.find(c => c.kind === 'radius' && c.curve === 'arc' && c.index === index);
288
- const radius = arc.locked
289
- ? arc.radius
290
- : dimension?.kind === 'radius'
291
- ? dimension.value
292
- : Math.hypot(target.position[0] - center[0], target.position[1] - center[1]);
293
- if (radius === 0)
294
- continue;
295
- radii.set(index, radius);
296
- const sweep = reference.constraints.some(c => c.kind === 'sweep' && c.index === index);
297
- // A mouse target at the center has no radial direction. For a fixed
298
- // radius keep the current endpoint direction as the seed; the mouse
299
- // objective still participates in the constrained solve.
300
- const direction = target.position.every((v, axis) => v === center[axis])
301
- ? current.points[target.point].position
302
- : target.position;
303
- const turn = Math.atan2(direction[1] - center[1], direction[0] - center[0]) -
304
- Math.atan2(from[1] - center[1], from[0] - center[0]);
305
- for (const point of arc.points) {
306
- let position = point === target.point ? direction : reference.points[point].position;
307
- if (sweep && point !== target.point) {
308
- const [x, y] = [position[0] - center[0], position[1] - center[1]];
309
- position = [
310
- center[0] + x * Math.cos(turn) - y * Math.sin(turn),
311
- center[1] + x * Math.sin(turn) + y * Math.cos(turn),
312
- ];
313
- }
314
- suggest(suggestions, point, radialPosition(center, position, radius));
315
- }
316
- }
317
- // Seed a center's followers from the same proposed position as the center.
318
- // Mixing a projected arc endpoint with a raw mouse translation distorts
319
- // the attached arc before its translation stage can preserve its shape.
320
- const endpoint = suggestions.get(target.point);
321
- const destination = endpoint ? averagePosition(endpoint) : target.position;
322
- for (const point of translated) {
323
- if (point === target.point && endpoint)
324
- continue;
325
- suggest(suggestions, point, reference.points[point].position.map((v, axis) => v + destination[axis] - from[axis]));
326
- }
327
- const stages = [
328
- { objectives: () => [{ ...target, weight: 1 }] },
329
- {
330
- objectives: reached => [
331
- ...anchors.map(point => anchor(reference, point)),
332
- ...translated
333
- .filter(p => p !== target.point)
334
- .map(p => ({
335
- ...anchor(reference, p),
336
- position: reference.points[p].position.map((v, axis) => v - from[axis] + reached.points[target.point].position[axis]),
337
- })),
338
- ],
339
- // Earlier radius/mouse stages may leave arbitrary follower angles.
340
- // Start local translation at its actual, now-known destination rather
341
- // than asking a near-singular soft solve to remove that inherited pose.
342
- seed: (reached, objectives) => seed(reached, new Map(objectives.flatMap(o => o.kind === 'point' && translated.includes(o.point)
343
- ? [[o.point, [o.position]]]
344
- : [])), new Map()),
345
- },
346
- ...(translations.length
347
- ? [{ objectives: () => translations.flatMap(p => p.exterior) }]
348
- : []),
349
- ];
350
- // A center's radii take precedence over following the mouse. A shared
351
- // endpoint still retains its incident centers before these radius goals.
352
- if (preservedRadii.length)
353
- stages.unshift({ objectives: () => preservedRadii });
354
- if (centers.size)
355
- stages.unshift({
356
- objectives: () => [...centers].map(point => anchor(reference, point)),
357
- });
358
- return { problem: seed(current, suggestions, radii), stages };
359
- };
360
- }
361
- function translationPolicy(problem, point, scope) {
362
- const curves = problem.arcs.filter(a => a.center === point && a.points.every(p => scope.has(p)));
363
- const owned = new Set([point, ...curves.flatMap(a => [...a.points])]);
364
- for (const c of problem.constraints)
365
- if (c.kind === 'pointOnCircle' && c.points[1] === point)
366
- owned.add(c.points[0]);
367
- const rectangle = rectanglePoints(problem, point, scope);
368
- const isCenter = curves.length > 0 ||
369
- problem.circles.some(c => c.center === point) ||
370
- !!rectangle;
371
- if (!isCenter)
372
- return;
373
- if (rectangle)
374
- for (const p of rectangle)
375
- owned.add(p);
376
- const radii = ['circle', 'arc'].flatMap(curve => (curve === 'circle' ? problem.circles : problem.arcs).flatMap((c, index) => scope.has(c.center)
377
- ? [{ kind: 'radius', curve, index, value: c.radius, weight: 1 }]
378
- : []));
379
- const ownRadius = (o) => (o.curve === 'circle' ? problem.circles : problem.arcs)[o.index].center ===
380
- point;
381
- return {
382
- kind: 'translation',
383
- points: [...owned],
384
- radii: radii.filter(ownRadius),
385
- exterior: [
386
- ...[...scope].filter(p => !owned.has(p)).map(p => anchor(problem, p)),
387
- ...radii.filter(o => !ownRadius(o)),
388
- ],
389
- };
390
- }
391
- function endpointPolicy(problem, point, scope) {
392
- const arcs = problem.arcs.flatMap((a, index) => a.points.includes(point) &&
393
- scope.has(a.center) &&
394
- a.points.every(p => scope.has(p))
395
- ? [index]
396
- : []);
397
- const controls = new Set(arcs.map(i => problem.arcs[i].center));
398
- for (const c of problem.constraints)
399
- if (c.kind === 'pointOnCircle' && c.points[0] === point)
400
- controls.add(c.points[1]);
401
- for (const c of problem.constraints)
402
- if (c.kind === 'midpoint' &&
403
- c.points[0] !== point &&
404
- rectanglePoints(problem, c.points[0], scope))
405
- controls.add(c.points[0]);
406
- controls.delete(point);
407
- if (controls.size)
408
- return { kind: 'endpoint', anchors: [...controls] };
409
- const graph = neighbors(problem);
410
- const distance = new Map([[point, 0]]);
411
- for (const [p, d] of distance)
412
- for (const next of graph[p])
413
- if (scope.has(next) && !distance.has(next))
414
- distance.set(next, d + 1);
415
- let farthest = -1, max = 0;
416
- for (const p of scope)
417
- if ((distance.get(p) ?? 0) > max) {
418
- farthest = p;
419
- max = distance.get(p);
420
- }
421
- return { kind: 'endpoint', anchors: farthest < 0 ? [] : [farthest] };
422
- }
423
- /** Recognize the actual four-edge/midpoint structure, not tool creation history. */
424
- function rectanglePoints(problem, center, scope) {
425
- const midpoint = problem.constraints.find(c => c.kind === 'midpoint' && c.points[0] === center);
426
- if (midpoint?.kind !== 'midpoint')
427
- return;
428
- const lines = problem.lines.filter(line => line.every(p => scope.has(p)) && !line.includes(center));
429
- const corners = new Set(lines.flatMap(line => [...line]));
430
- if (lines.length !== 4 ||
431
- corners.size !== 4 ||
432
- !midpoint.points.slice(1).every(p => corners.has(p)))
433
- return;
434
- if ([...corners].some(p => lines.filter(line => line.includes(p)).length !== 2))
435
- return;
436
- if (!lines.every(line => problem.constraints.some(c => (c.kind === 'horizontal' || c.kind === 'vertical') &&
437
- c.points.every(p => line.includes(p)))))
438
- return;
439
- return [...corners];
440
- }
441
- function constraintPoints(problem, c) {
442
- if ('points' in c)
443
- return c.points;
444
- if ('point' in c)
445
- return [c.point];
446
- if (c.kind === 'sweep' || c.curve === 'arc') {
447
- const arc = problem.arcs[c.index];
448
- return [arc.center, ...arc.points];
449
- }
450
- return [problem.circles[c.index].center];
451
- }
452
- function neighbors(problem, excluded = -1) {
453
- const graph = problem.points.map(() => new Set());
454
- const connect = (vertices) => {
455
- const included = vertices.filter(p => p !== excluded);
456
- for (const a of included)
457
- for (const b of included)
458
- if (a !== b)
459
- graph[a].add(b);
460
- };
461
- problem.lines.forEach(connect);
462
- problem.arcs.forEach(a => connect([a.center, ...a.points]));
463
- problem.constraints.forEach(c => connect(constraintPoints(problem, c)));
464
- return graph;
465
- }
466
- function connected(graph, start) {
467
- const result = new Set([start]);
468
- for (const point of result)
469
- for (const next of graph[point])
470
- result.add(next);
471
- return result;
472
- }
473
- function anchor(problem, point) {
474
- return {
475
- kind: 'point',
476
- point,
477
- position: problem.points[point].position,
478
- weight: 1,
479
- };
480
- }
481
- function suggest(suggestions, point, position) {
482
- const values = suggestions.get(point) ?? [];
483
- values.push(position);
484
- suggestions.set(point, values);
485
- }
486
- function radialPosition(center, position, radius) {
487
- // Rescale the actual direction: atan2 -> sin/cos would turn an exact
488
- // horizontal/vertical coordinate into a tail that is then kept as a seed.
489
- const dx = position[0] - center[0], dy = position[1] - center[1];
490
- const length = Math.hypot(dx, dy);
491
- return [
492
- center[0] + (dx / length) * radius,
493
- center[1] + (dy / length) * radius,
494
- ];
495
- }
496
- function seed(problem, suggestions, radii) {
497
- return {
498
- ...problem,
499
- points: problem.points.map((p, index) => {
500
- const values = suggestions.get(index);
501
- if (!values)
502
- return p;
503
- const position = averagePosition(values);
504
- return {
505
- ...p,
506
- position: p.position.map((v, axis) => p.locked[axis] ||
507
- problem.constraints.some(c => 'point' in c &&
508
- c.point === index &&
509
- (c.kind === 'fixed' || c.kind === (axis === 0 ? 'x' : 'y')))
510
- ? v
511
- : position[axis]),
512
- };
513
- }),
514
- arcs: problem.arcs.map((arc, index) => arc.locked || !radii.has(index)
515
- ? arc
516
- : { ...arc, radius: radii.get(index) }),
517
- };
518
- }
519
- function averagePosition(values) {
520
- return [0, 1].map(axis => values.reduce((sum, value) => sum + value[axis], 0) / values.length);
521
- }
522
- //# sourceMappingURL=sketch-drag-rules.js.map