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

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 (128) 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-5E6VXJU2.js +30 -0
  5. package/bld/chunks/chunk-5E6VXJU2.js.map +7 -0
  6. package/bld/chunks/chunk-63ZUQWQ2.js +19 -0
  7. package/bld/chunks/chunk-63ZUQWQ2.js.map +7 -0
  8. package/bld/chunks/chunk-AWYI64EE.js +31 -0
  9. package/bld/chunks/chunk-AWYI64EE.js.map +7 -0
  10. package/bld/chunks/chunk-F53RTQPT.js +24787 -0
  11. package/bld/chunks/chunk-F53RTQPT.js.map +7 -0
  12. package/bld/chunks/chunk-TJIN6S4R.js +1 -0
  13. package/bld/chunks/chunk-TJIN6S4R.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 +17 -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 +144 -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/node/index.d.ts.map +1 -1
  47. package/bld/node/index.js +62 -14
  48. package/bld/node/index.js.map +7 -1
  49. package/bld/node/replicad.js +13 -3
  50. package/bld/node/replicad.js.map +7 -1
  51. package/bld/tooling/index.d.ts +17 -15
  52. package/bld/tooling/index.d.ts.map +1 -1
  53. package/bld/tooling/index.js +147 -19
  54. package/bld/tooling/index.js.map +7 -1
  55. package/package.json +18 -6
  56. package/src/library/cached.ts +132 -0
  57. package/src/library/font.ts +186 -0
  58. package/src/library/google-font.ts +97 -0
  59. package/src/library/index.ts +8 -3
  60. package/src/library/kernel-artifact-codec.ts +129 -20
  61. package/src/library/kernel-cache.ts +111 -33
  62. package/src/library/material.ts +3 -4
  63. package/src/library/open-cascade.ts +13 -0
  64. package/src/library/replicad.ts +5 -5
  65. package/src/library/retained-memory.ts +6 -1
  66. package/src/library/runtime.ts +2049 -1728
  67. package/src/library/sketch-solver.ts +3 -1
  68. package/src/library/sketch.ts +81 -13
  69. package/src/library/text.ts +477 -0
  70. package/src/library/tsconfig.json +2 -1
  71. package/src/node/index.ts +14 -3
  72. package/src/node/tsconfig.json +2 -1
  73. package/src/tooling/index.ts +48 -46
  74. package/src/tooling/tsconfig.json +2 -1
  75. package/bld/library/alignment-geometry.js +0 -322
  76. package/bld/library/alignment-geometry.js.map +0 -1
  77. package/bld/library/bound-solver.js +0 -252
  78. package/bld/library/bound-solver.js.map +0 -1
  79. package/bld/library/extrude.js +0 -28
  80. package/bld/library/extrude.js.map +0 -1
  81. package/bld/library/kernel-artifact-codec.js +0 -148
  82. package/bld/library/kernel-artifact-codec.js.map +0 -1
  83. package/bld/library/kernel-cache.js +0 -245
  84. package/bld/library/kernel-cache.js.map +0 -1
  85. package/bld/library/kernel-shapes.js +0 -108
  86. package/bld/library/kernel-shapes.js.map +0 -1
  87. package/bld/library/loft.js +0 -161
  88. package/bld/library/loft.js.map +0 -1
  89. package/bld/library/material.js +0 -176
  90. package/bld/library/material.js.map +0 -1
  91. package/bld/library/model-color.js +0 -36
  92. package/bld/library/model-color.js.map +0 -1
  93. package/bld/library/open-cascade-error.js +0 -22
  94. package/bld/library/open-cascade-error.js.map +0 -1
  95. package/bld/library/relation-solver.js +0 -258
  96. package/bld/library/relation-solver.js.map +0 -1
  97. package/bld/library/retained-memory.js +0 -27
  98. package/bld/library/retained-memory.js.map +0 -1
  99. package/bld/library/runtime.js +0 -2954
  100. package/bld/library/runtime.js.map +0 -1
  101. package/bld/library/shell.js +0 -221
  102. package/bld/library/shell.js.map +0 -1
  103. package/bld/library/sketch-curve-intersections.js +0 -117
  104. package/bld/library/sketch-curve-intersections.js.map +0 -1
  105. package/bld/library/sketch-curves.js +0 -89
  106. package/bld/library/sketch-curves.js.map +0 -1
  107. package/bld/library/sketch-drag-rules.js +0 -522
  108. package/bld/library/sketch-drag-rules.js.map +0 -1
  109. package/bld/library/sketch-face.js +0 -72
  110. package/bld/library/sketch-face.js.map +0 -1
  111. package/bld/library/sketch-incidence.js +0 -114
  112. package/bld/library/sketch-incidence.js.map +0 -1
  113. package/bld/library/sketch-precision.js +0 -55
  114. package/bld/library/sketch-precision.js.map +0 -1
  115. package/bld/library/sketch-regions.js +0 -193
  116. package/bld/library/sketch-regions.js.map +0 -1
  117. package/bld/library/sketch-solver.js +0 -578
  118. package/bld/library/sketch-solver.js.map +0 -1
  119. package/bld/library/sketch.js +0 -650
  120. package/bld/library/sketch.js.map +0 -1
  121. package/bld/library/spatial.js +0 -195
  122. package/bld/library/spatial.js.map +0 -1
  123. package/bld/library/topology-id.js +0 -67
  124. package/bld/library/topology-id.js.map +0 -1
  125. package/bld/library/topology-inspection.js +0 -267
  126. package/bld/library/topology-inspection.js.map +0 -1
  127. package/bld/library/topology.js +0 -455
  128. 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