@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.
- package/README.md +223 -786
- package/THIRD_PARTY.md +40 -0
- package/bld/THIRD_PARTY_NOTICES.txt +182 -0
- package/bld/chunks/chunk-BF55AIEQ.js +31 -0
- package/bld/chunks/chunk-BF55AIEQ.js.map +7 -0
- package/bld/chunks/chunk-P5VGKAEM.js +30 -0
- package/bld/chunks/chunk-P5VGKAEM.js.map +7 -0
- package/bld/chunks/chunk-RPY2WVCB.js +24834 -0
- package/bld/chunks/chunk-RPY2WVCB.js.map +7 -0
- package/bld/chunks/chunk-TJIN6S4R.js +1 -0
- package/bld/chunks/chunk-TJIN6S4R.js.map +7 -0
- package/bld/chunks/chunk-U5G2VU2I.js +19 -0
- package/bld/chunks/chunk-U5G2VU2I.js.map +7 -0
- package/bld/library/cached.d.ts +33 -0
- package/bld/library/cached.d.ts.map +1 -0
- package/bld/library/font.d.ts +39 -0
- package/bld/library/font.d.ts.map +1 -0
- package/bld/library/google-font.d.ts +14 -0
- package/bld/library/google-font.d.ts.map +1 -0
- package/bld/library/index.d.ts +8 -2
- package/bld/library/index.d.ts.map +1 -1
- package/bld/library/index.js +60 -3
- package/bld/library/index.js.map +7 -1
- package/bld/library/kernel-artifact-codec.d.ts.map +1 -1
- package/bld/library/kernel-cache.d.ts +24 -6
- package/bld/library/kernel-cache.d.ts.map +1 -1
- package/bld/library/material.d.ts +1 -1
- package/bld/library/material.d.ts.map +1 -1
- package/bld/library/open-cascade.d.ts +4 -0
- package/bld/library/open-cascade.d.ts.map +1 -0
- package/bld/library/replicad.d.ts +3 -2
- package/bld/library/replicad.d.ts.map +1 -1
- package/bld/library/replicad.js +10 -24
- package/bld/library/replicad.js.map +7 -1
- package/bld/library/retained-memory.d.ts.map +1 -1
- package/bld/library/runtime.d.ts +146 -80
- package/bld/library/runtime.d.ts.map +1 -1
- package/bld/library/sketch-solver.d.ts +1 -1
- package/bld/library/sketch-solver.d.ts.map +1 -1
- package/bld/library/sketch.d.ts +9 -1
- package/bld/library/sketch.d.ts.map +1 -1
- package/bld/library/text.d.ts +53 -0
- package/bld/library/text.d.ts.map +1 -0
- package/bld/library/three.js +3 -3
- package/bld/library/three.js.map +7 -1
- package/bld/library/topology.d.ts.map +1 -1
- package/bld/node/index.d.ts.map +1 -1
- package/bld/node/index.js +62 -14
- package/bld/node/index.js.map +7 -1
- package/bld/node/replicad.js +13 -3
- package/bld/node/replicad.js.map +7 -1
- package/bld/tooling/index.d.ts +17 -15
- package/bld/tooling/index.d.ts.map +1 -1
- package/bld/tooling/index.js +147 -19
- package/bld/tooling/index.js.map +7 -1
- package/package.json +18 -6
- package/src/library/cached.ts +132 -0
- package/src/library/font.ts +186 -0
- package/src/library/google-font.ts +97 -0
- package/src/library/index.ts +8 -3
- package/src/library/kernel-artifact-codec.ts +129 -20
- package/src/library/kernel-cache.ts +128 -34
- package/src/library/loft.ts +4 -0
- package/src/library/material.ts +3 -4
- package/src/library/open-cascade.ts +13 -0
- package/src/library/replicad.ts +5 -5
- package/src/library/retained-memory.ts +6 -1
- package/src/library/runtime.ts +2064 -1730
- package/src/library/sketch-solver.ts +3 -1
- package/src/library/sketch.ts +81 -13
- package/src/library/text.ts +477 -0
- package/src/library/topology.ts +24 -0
- package/src/library/tsconfig.json +2 -1
- package/src/node/index.ts +14 -3
- package/src/node/tsconfig.json +2 -1
- package/src/tooling/index.ts +48 -46
- package/src/tooling/tsconfig.json +2 -1
- package/bld/library/alignment-geometry.js +0 -322
- package/bld/library/alignment-geometry.js.map +0 -1
- package/bld/library/bound-solver.js +0 -252
- package/bld/library/bound-solver.js.map +0 -1
- package/bld/library/extrude.js +0 -28
- package/bld/library/extrude.js.map +0 -1
- package/bld/library/kernel-artifact-codec.js +0 -148
- package/bld/library/kernel-artifact-codec.js.map +0 -1
- package/bld/library/kernel-cache.js +0 -245
- package/bld/library/kernel-cache.js.map +0 -1
- package/bld/library/kernel-shapes.js +0 -108
- package/bld/library/kernel-shapes.js.map +0 -1
- package/bld/library/loft.js +0 -161
- package/bld/library/loft.js.map +0 -1
- package/bld/library/material.js +0 -176
- package/bld/library/material.js.map +0 -1
- package/bld/library/model-color.js +0 -36
- package/bld/library/model-color.js.map +0 -1
- package/bld/library/open-cascade-error.js +0 -22
- package/bld/library/open-cascade-error.js.map +0 -1
- package/bld/library/relation-solver.js +0 -258
- package/bld/library/relation-solver.js.map +0 -1
- package/bld/library/retained-memory.js +0 -27
- package/bld/library/retained-memory.js.map +0 -1
- package/bld/library/runtime.js +0 -2954
- package/bld/library/runtime.js.map +0 -1
- package/bld/library/shell.js +0 -221
- package/bld/library/shell.js.map +0 -1
- package/bld/library/sketch-curve-intersections.js +0 -117
- package/bld/library/sketch-curve-intersections.js.map +0 -1
- package/bld/library/sketch-curves.js +0 -89
- package/bld/library/sketch-curves.js.map +0 -1
- package/bld/library/sketch-drag-rules.js +0 -522
- package/bld/library/sketch-drag-rules.js.map +0 -1
- package/bld/library/sketch-face.js +0 -72
- package/bld/library/sketch-face.js.map +0 -1
- package/bld/library/sketch-incidence.js +0 -114
- package/bld/library/sketch-incidence.js.map +0 -1
- package/bld/library/sketch-precision.js +0 -55
- package/bld/library/sketch-precision.js.map +0 -1
- package/bld/library/sketch-regions.js +0 -193
- package/bld/library/sketch-regions.js.map +0 -1
- package/bld/library/sketch-solver.js +0 -578
- package/bld/library/sketch-solver.js.map +0 -1
- package/bld/library/sketch.js +0 -650
- package/bld/library/sketch.js.map +0 -1
- package/bld/library/spatial.js +0 -195
- package/bld/library/spatial.js.map +0 -1
- package/bld/library/topology-id.js +0 -67
- package/bld/library/topology-id.js.map +0 -1
- package/bld/library/topology-inspection.js +0 -267
- package/bld/library/topology-inspection.js.map +0 -1
- package/bld/library/topology.js +0 -455
- 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
|