@xom11/whiteboard 0.30.0 → 0.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/dist/ai.d.mts +374 -59
  2. package/dist/ai.d.ts +374 -59
  3. package/dist/ai.js +7542 -545
  4. package/dist/ai.js.map +1 -1
  5. package/dist/ai.mjs +7243 -569
  6. package/dist/ai.mjs.map +1 -1
  7. package/dist/catalog.json +5 -5
  8. package/dist/{chunk-ZTQBUKLJ.mjs → chunk-4EXRNZRX.mjs} +781 -95
  9. package/dist/chunk-4EXRNZRX.mjs.map +1 -0
  10. package/dist/{chunk-PPKHCRRE.mjs → chunk-C76SOFXF.mjs} +3 -3
  11. package/dist/{chunk-PPKHCRRE.mjs.map → chunk-C76SOFXF.mjs.map} +1 -1
  12. package/dist/{chunk-IHUFOV7L.mjs → chunk-CH6SFONH.mjs} +15 -3
  13. package/dist/chunk-CH6SFONH.mjs.map +1 -0
  14. package/dist/{chunk-XVVLT6B3.mjs → chunk-DBG6DZ2V.mjs} +4 -4
  15. package/dist/{chunk-XVVLT6B3.mjs.map → chunk-DBG6DZ2V.mjs.map} +1 -1
  16. package/dist/{chunk-BU5KLO3P.mjs → chunk-DCFZTKFE.mjs} +14 -6
  17. package/dist/chunk-DCFZTKFE.mjs.map +1 -0
  18. package/dist/{chunk-SF3U7ZF4.mjs → chunk-DJM5EWTT.mjs} +231 -16
  19. package/dist/chunk-DJM5EWTT.mjs.map +1 -0
  20. package/dist/{chunk-V3YJ6JFL.mjs → chunk-F42MZ745.mjs} +3 -3
  21. package/dist/{chunk-V3YJ6JFL.mjs.map → chunk-F42MZ745.mjs.map} +1 -1
  22. package/dist/{chunk-5JM35CXV.mjs → chunk-FI2MGX5H.mjs} +4 -4
  23. package/dist/{chunk-5JM35CXV.mjs.map → chunk-FI2MGX5H.mjs.map} +1 -1
  24. package/dist/{chunk-SZDAS7LK.mjs → chunk-IE2GGHNF.mjs} +131 -81
  25. package/dist/chunk-IE2GGHNF.mjs.map +1 -0
  26. package/dist/{chunk-BNBOIDO5.mjs → chunk-K7VJU7LQ.mjs} +3 -3
  27. package/dist/{chunk-BNBOIDO5.mjs.map → chunk-K7VJU7LQ.mjs.map} +1 -1
  28. package/dist/{chunk-H22OZYTW.mjs → chunk-KLGPAMJS.mjs} +55 -43
  29. package/dist/chunk-KLGPAMJS.mjs.map +1 -0
  30. package/dist/{chunk-CXHNVYMD.mjs → chunk-KWDBVLST.mjs} +5 -5
  31. package/dist/{chunk-CXHNVYMD.mjs.map → chunk-KWDBVLST.mjs.map} +1 -1
  32. package/dist/{chunk-73Q7ADVL.mjs → chunk-MNHOFOM5.mjs} +2 -2
  33. package/dist/{chunk-73Q7ADVL.mjs.map → chunk-MNHOFOM5.mjs.map} +1 -1
  34. package/dist/{chunk-QRUAEXLR.mjs → chunk-MOAXHVKI.mjs} +5 -5
  35. package/dist/{chunk-QRUAEXLR.mjs.map → chunk-MOAXHVKI.mjs.map} +1 -1
  36. package/dist/{chunk-QGNU34T7.mjs → chunk-QLQ4MJNO.mjs} +10 -4
  37. package/dist/chunk-QLQ4MJNO.mjs.map +1 -0
  38. package/dist/chunk-SUXIMLYN.mjs +238 -0
  39. package/dist/chunk-SUXIMLYN.mjs.map +1 -0
  40. package/dist/{chunk-OQIQNKPQ.mjs → chunk-XSTGTEGP.mjs} +5 -5
  41. package/dist/{chunk-OQIQNKPQ.mjs.map → chunk-XSTGTEGP.mjs.map} +1 -1
  42. package/dist/geometry-2d.d.mts +1 -1
  43. package/dist/geometry-2d.d.ts +1 -1
  44. package/dist/geometry-2d.js +1329 -288
  45. package/dist/geometry-2d.js.map +1 -1
  46. package/dist/geometry-2d.mjs +6 -6
  47. package/dist/geometry-3d.d.mts +1 -1
  48. package/dist/geometry-3d.d.ts +1 -1
  49. package/dist/geometry-3d.js +4136 -462
  50. package/dist/geometry-3d.js.map +1 -1
  51. package/dist/geometry-3d.mjs +5 -5
  52. package/dist/graph-2d.d.mts +1 -1
  53. package/dist/graph-2d.d.ts +1 -1
  54. package/dist/graph-2d.js +1010 -208
  55. package/dist/graph-2d.js.map +1 -1
  56. package/dist/graph-2d.mjs +8 -8
  57. package/dist/{handleExtractProblem-BrDY9ifM.d.ts → handleExtractProblem-mtJyhEw3.d.mts} +2 -0
  58. package/dist/{handleExtractProblem-BrDY9ifM.d.mts → handleExtractProblem-mtJyhEw3.d.ts} +2 -0
  59. package/dist/{host-ZQCDAT6O.mjs → host-H2IGOKJU.mjs} +3 -3
  60. package/dist/{host-ZQCDAT6O.mjs.map → host-H2IGOKJU.mjs.map} +1 -1
  61. package/dist/{host-3UFGFMJ2.mjs → host-JNLJ5BVR.mjs} +92 -45
  62. package/dist/host-JNLJ5BVR.mjs.map +1 -0
  63. package/dist/{host-HOSJHQ5H.mjs → host-VRWLQSMH.mjs} +14 -13
  64. package/dist/host-VRWLQSMH.mjs.map +1 -0
  65. package/dist/host-W4U6YCSX.mjs +4973 -0
  66. package/dist/host-W4U6YCSX.mjs.map +1 -0
  67. package/dist/index.d.mts +3 -3
  68. package/dist/index.d.ts +3 -3
  69. package/dist/index.js +4577 -600
  70. package/dist/index.js.map +1 -1
  71. package/dist/index.mjs +35 -29
  72. package/dist/index.mjs.map +1 -1
  73. package/dist/latex.d.mts +1 -1
  74. package/dist/latex.d.ts +1 -1
  75. package/dist/latex.js +8 -2
  76. package/dist/latex.js.map +1 -1
  77. package/dist/latex.mjs +1 -1
  78. package/dist/render-NMS7OAV6.mjs +10 -0
  79. package/dist/{render-ZX2O2IK7.mjs.map → render-NMS7OAV6.mjs.map} +1 -1
  80. package/dist/serialize-7UZWQQ4I.mjs +9 -0
  81. package/dist/{serialize-N4G6RFBB.mjs.map → serialize-7UZWQQ4I.mjs.map} +1 -1
  82. package/dist/{types-C3FjpoUi.d.mts → types-tePd94vW.d.mts} +8 -0
  83. package/dist/{types-C3FjpoUi.d.ts → types-tePd94vW.d.ts} +8 -0
  84. package/package.json +4 -1
  85. package/dist/chunk-BU5KLO3P.mjs.map +0 -1
  86. package/dist/chunk-H22OZYTW.mjs.map +0 -1
  87. package/dist/chunk-IHUFOV7L.mjs.map +0 -1
  88. package/dist/chunk-QGNU34T7.mjs.map +0 -1
  89. package/dist/chunk-QK6OVDLC.mjs +0 -103
  90. package/dist/chunk-QK6OVDLC.mjs.map +0 -1
  91. package/dist/chunk-SF3U7ZF4.mjs.map +0 -1
  92. package/dist/chunk-SZDAS7LK.mjs.map +0 -1
  93. package/dist/chunk-ZTQBUKLJ.mjs.map +0 -1
  94. package/dist/host-2ISGVO7O.mjs +0 -2377
  95. package/dist/host-2ISGVO7O.mjs.map +0 -1
  96. package/dist/host-3UFGFMJ2.mjs.map +0 -1
  97. package/dist/host-HOSJHQ5H.mjs.map +0 -1
  98. package/dist/render-ZX2O2IK7.mjs +0 -10
  99. package/dist/serialize-N4G6RFBB.mjs +0 -9
@@ -1,6 +1,6 @@
1
1
  "use client";
2
- import { compile, validate } from './chunk-IHUFOV7L.mjs';
3
- import { createEmptyState } from './chunk-73Q7ADVL.mjs';
2
+ import { labelOpts, compile, validate } from './chunk-CH6SFONH.mjs';
3
+ import { createEmptyState } from './chunk-MNHOFOM5.mjs';
4
4
  import { registerKind } from './chunk-B4NJJZFR.mjs';
5
5
  import * as React from 'react';
6
6
 
@@ -81,8 +81,377 @@ function constraintRefs(c) {
81
81
  return [c.polygonId];
82
82
  case "onSphere":
83
83
  return [c.sphereId];
84
- default:
84
+ case "midpoint":
85
+ return [c.p1, c.p2];
86
+ case "centroid":
87
+ return [...c.vertices];
88
+ case "intersectionLines":
89
+ return [c.a1, c.b1, c.a2, c.b2];
90
+ case "intersectionLinePlane":
91
+ return [c.a, c.b, c.plane];
92
+ case "perpFootLine":
93
+ return [c.from, c.a, c.b];
94
+ case "perpFootPlane":
95
+ return [c.from, c.plane];
96
+ case "circumsphereCenter":
97
+ return [...c.vertices];
98
+ case "pyramidInsphereCenter":
99
+ return [c.apex, ...c.vertices];
100
+ case "faceCircumcenter":
101
+ return [...c.vertices];
102
+ case "pointAboveFace":
103
+ return [c.base, c.apex, ...c.vertices];
104
+ // Kind KHÔNG có ref scene-object (toạ độ literal): liệt kê TƯỜNG MINH để
105
+ // exhaustive never-guard bên dưới buộc khai báo khi thêm constraint kind mới
106
+ // (mô phỏng constraintRefs2D — quên case = cascade-delete/deps sai âm thầm).
107
+ case "free":
108
+ case "onGround":
109
+ case "onAxis":
110
+ return [];
111
+ default: {
85
112
  return [];
113
+ }
114
+ }
115
+ }
116
+
117
+ // src/core/scene/kinds/constraint3d-math.ts
118
+ function sub(a, b) {
119
+ return [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
120
+ }
121
+ function add(a, b) {
122
+ return [a[0] + b[0], a[1] + b[1], a[2] + b[2]];
123
+ }
124
+ function scale(a, k) {
125
+ return [a[0] * k, a[1] * k, a[2] * k];
126
+ }
127
+ function dot(a, b) {
128
+ return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
129
+ }
130
+ function cross(a, b) {
131
+ return [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
132
+ }
133
+ function norm(a) {
134
+ return Math.sqrt(dot(a, a));
135
+ }
136
+ function normalize(a) {
137
+ const n = norm(a);
138
+ return n === 0 ? a : scale(a, 1 / n);
139
+ }
140
+ var MAX_REC_DEPTH = 512;
141
+ var _recDepth = 0;
142
+ function getPointWorld(id, state) {
143
+ const obj = state.objects[id];
144
+ if (!obj || obj.kind !== "point3d") {
145
+ throw new Error(`constraintMath: point ${id} not found`);
146
+ }
147
+ const attrs = obj.attrs;
148
+ return constraintToWorld(attrs.constraint, state);
149
+ }
150
+ function getPlaneBasis(planeObj, state) {
151
+ const a = planeObj.attrs;
152
+ let p1, p2, p3;
153
+ if (a.construction) {
154
+ const r = planeConstructionWorld(a.construction, state);
155
+ p1 = r.p1;
156
+ p2 = r.p2;
157
+ p3 = r.p3;
158
+ } else {
159
+ p1 = getPointWorld(a.p1, state);
160
+ p2 = getPointWorld(a.p2, state);
161
+ p3 = getPointWorld(a.p3, state);
162
+ }
163
+ const basis1 = sub(p2, p1);
164
+ const tmp = sub(p3, p1);
165
+ const normal = normalize(cross(basis1, tmp));
166
+ const basis2 = cross(normal, basis1);
167
+ return { origin: p1, basis1, basis2, normal };
168
+ }
169
+ function orthoBasisFromNormal(n) {
170
+ const seed = Math.abs(n[0]) < 0.9 ? [1, 0, 0] : [0, 1, 0];
171
+ const basis1 = normalize(cross(n, seed));
172
+ const basis2 = cross(n, basis1);
173
+ return { basis1, basis2 };
174
+ }
175
+ function planeConstructionWorld(c, state) {
176
+ if (_recDepth >= MAX_REC_DEPTH) throw new Error("planeConstructionWorld: \u0111\u1EC7 quy qu\xE1 s\xE2u \u2014 chu tr\xECnh tham chi\u1EBFu m\u1EB7t?");
177
+ _recDepth++;
178
+ try {
179
+ return planeConstructionWorldInner(c, state);
180
+ } finally {
181
+ _recDepth--;
182
+ }
183
+ }
184
+ function planeConstructionWorldInner(c, state) {
185
+ switch (c.kind) {
186
+ case "planeParallelThrough": {
187
+ const P = getPointWorld(c.point, state);
188
+ const ref = state.objects[c.refPlane];
189
+ if (!ref || ref.kind !== "plane3d") throw new Error("planeParallelThrough: m\u1EB7t tham chi\u1EBFu thi\u1EBFu");
190
+ const { basis1, basis2 } = getPlaneBasis(ref, state);
191
+ if (norm(basis1) < 1e-12 || norm(basis2) < 1e-12) {
192
+ return { p1: P, p2: add(P, [1, 0, 0]), p3: add(P, [0, 1, 0]) };
193
+ }
194
+ return { p1: P, p2: add(P, basis1), p3: add(P, basis2) };
195
+ }
196
+ case "planePerpToLine": {
197
+ const P = getPointWorld(c.point, state);
198
+ const dir = sub(getPointWorld(c.lineB, state), getPointWorld(c.lineA, state));
199
+ const dn = norm(dir);
200
+ if (dn < 1e-12) return { p1: P, p2: add(P, [1, 0, 0]), p3: add(P, [0, 1, 0]) };
201
+ const { basis1, basis2 } = orthoBasisFromNormal(scale(dir, 1 / dn));
202
+ return { p1: P, p2: add(P, basis1), p3: add(P, basis2) };
203
+ }
204
+ default: {
205
+ throw new Error("planeConstructionWorld: kind kh\xF4ng h\u1ED7 tr\u1EE3");
206
+ }
207
+ }
208
+ }
209
+ function lineDefiningPointIds(line) {
210
+ if (line.kind === "line3d" || line.kind === "segment3d") {
211
+ const a = line.attrs;
212
+ return a.p1 && a.p2 ? [a.p1, a.p2] : null;
213
+ }
214
+ if (line.kind === "ray3d") {
215
+ const a = line.attrs;
216
+ return [a.origin, a.through];
217
+ }
218
+ if (line.kind === "vector3d") {
219
+ const a = line.attrs;
220
+ return [a.from, a.to];
221
+ }
222
+ return null;
223
+ }
224
+ function lineEndpointsWorld(lineId, state, ctx) {
225
+ const line = state.objects[lineId];
226
+ if (!line) throw new Error(`${ctx}: \u0111\u01B0\u1EDDng ${lineId} kh\xF4ng t\u1ED3n t\u1EA1i`);
227
+ const ids = lineDefiningPointIds(line);
228
+ if (!ids) throw new Error(`${ctx}: kind ${line.kind} kh\xF4ng ph\u1EA3i \u0111\u01B0\u1EDDng`);
229
+ return { a: getPointWorld(ids[0], state), b: getPointWorld(ids[1], state) };
230
+ }
231
+ function lineLineClosestMidpoint(A, B, C, D) {
232
+ const u = sub(B, A), v = sub(D, C), w0 = sub(A, C);
233
+ const a = dot(u, u), b = dot(u, v), cc = dot(v, v), d = dot(u, w0), e = dot(v, w0);
234
+ const denom = a * cc - b * b;
235
+ if (Math.abs(denom) < 1e-12) return scale(add(A, C), 0.5);
236
+ const sc = (b * e - cc * d) / denom;
237
+ const tc = (a * e - b * d) / denom;
238
+ return scale(add(add(A, scale(u, sc)), add(C, scale(v, tc))), 0.5);
239
+ }
240
+ function planeOriginNormal(planeId, state, ctx) {
241
+ const plane = state.objects[planeId];
242
+ if (!plane || plane.kind !== "plane3d") throw new Error(`${ctx}: m\u1EB7t ph\u1EB3ng ${planeId} thi\u1EBFu`);
243
+ const { origin, normal } = getPlaneBasis(plane, state);
244
+ return { origin, normal };
245
+ }
246
+ function lineConstructionWorld(c, state) {
247
+ switch (c.kind) {
248
+ case "planePlaneIntersection": {
249
+ const P1 = planeOriginNormal(c.plane1, state, "planePlaneIntersection");
250
+ const P2 = planeOriginNormal(c.plane2, state, "planePlaneIntersection");
251
+ const d = cross(P1.normal, P2.normal);
252
+ const dd = dot(d, d);
253
+ if (dd < 1e-12) {
254
+ return { a: P1.origin, b: add(P1.origin, P2.normal) };
255
+ }
256
+ const c1 = dot(P1.normal, P1.origin);
257
+ const c2 = dot(P2.normal, P2.origin);
258
+ const p0 = scale(add(scale(cross(P2.normal, d), c1), scale(cross(d, P1.normal), c2)), 1 / dd);
259
+ return { a: p0, b: add(p0, d) };
260
+ }
261
+ case "lineParallelThrough": {
262
+ const P = getPointWorld(c.point, state);
263
+ const dir = sub(getPointWorld(c.dirB, state), getPointWorld(c.dirA, state));
264
+ if (norm(dir) < 1e-12) return { a: P, b: add(P, [1, 0, 0]) };
265
+ return { a: P, b: add(P, dir) };
266
+ }
267
+ case "linePerpToPlane": {
268
+ const P = getPointWorld(c.point, state);
269
+ const { normal } = planeOriginNormal(c.plane, state, "linePerpToPlane");
270
+ return { a: P, b: add(P, normal) };
271
+ }
272
+ default: {
273
+ throw new Error("lineConstructionWorld: kind kh\xF4ng h\u1ED7 tr\u1EE3");
274
+ }
275
+ }
276
+ }
277
+ function solve3(M, b) {
278
+ const det3 = (m) => m[0][0] * (m[1][1] * m[2][2] - m[1][2] * m[2][1]) - m[0][1] * (m[1][0] * m[2][2] - m[1][2] * m[2][0]) + m[0][2] * (m[1][0] * m[2][1] - m[1][1] * m[2][0]);
279
+ const det = det3(M);
280
+ if (Math.abs(det) < 1e-9) return null;
281
+ const colDet = (j) => {
282
+ const A = M.map((r) => r.slice());
283
+ for (let i = 0; i < 3; i++) A[i][j] = b[i];
284
+ return det3(A);
285
+ };
286
+ return [colDet(0) / det, colDet(1) / det, colDet(2) / det];
287
+ }
288
+ function constraintToWorld(c, state) {
289
+ if (_recDepth >= MAX_REC_DEPTH) throw new Error("constraintToWorld: \u0111\u1EC7 quy qu\xE1 s\xE2u \u2014 chu tr\xECnh tham chi\u1EBFu?");
290
+ _recDepth++;
291
+ try {
292
+ return constraintToWorldInner(c, state);
293
+ } finally {
294
+ _recDepth--;
295
+ }
296
+ }
297
+ function constraintToWorldInner(c, state) {
298
+ switch (c.kind) {
299
+ case "free":
300
+ return [c.x, c.y, c.z];
301
+ case "onGround":
302
+ return [c.x, c.y, 0];
303
+ case "onAxis": {
304
+ if (c.axis === "x") return [c.t, 0, 0];
305
+ if (c.axis === "y") return [0, c.t, 0];
306
+ return [0, 0, c.t];
307
+ }
308
+ case "onPlane": {
309
+ const plane = state.objects[c.planeId];
310
+ if (!plane || plane.kind !== "plane3d") throw new Error("onPlane: plane missing");
311
+ const { origin, basis1, basis2 } = getPlaneBasis(plane, state);
312
+ return add(add(origin, scale(basis1, c.u)), scale(basis2, c.v));
313
+ }
314
+ case "onLine": {
315
+ const { a, b } = lineEndpointsWorld(c.lineId, state, "onLine");
316
+ return add(a, scale(sub(b, a), c.t));
317
+ }
318
+ case "onPolygon": {
319
+ const pg = state.objects[c.polygonId];
320
+ if (!pg || pg.kind !== "polygon3d") throw new Error("onPolygon: parent missing");
321
+ const v = pg.attrs.vertices;
322
+ if (v.length < 3) throw new Error("onPolygon: < 3 vertices");
323
+ const p1 = getPointWorld(v[0], state);
324
+ const p2 = getPointWorld(v[1], state);
325
+ const p3 = getPointWorld(v[2], state);
326
+ const basis1 = sub(p2, p1);
327
+ const tmp = sub(p3, p1);
328
+ const normal = normalize(cross(basis1, tmp));
329
+ const basis2 = cross(normal, basis1);
330
+ return add(add(p1, scale(basis1, c.u)), scale(basis2, c.v));
331
+ }
332
+ case "onSphere": {
333
+ const sph = state.objects[c.sphereId];
334
+ if (!sph || sph.kind !== "sphere3d") throw new Error("onSphere: parent missing");
335
+ const a = sph.attrs;
336
+ const center = getPointWorld(a.center, state);
337
+ const surface = getPointWorld(a.surfacePoint, state);
338
+ const radius = norm(sub(surface, center));
339
+ const x = center[0] + radius * Math.sin(c.phi) * Math.cos(c.theta);
340
+ const y = center[1] + radius * Math.sin(c.phi) * Math.sin(c.theta);
341
+ const z = center[2] + radius * Math.cos(c.phi);
342
+ return [x, y, z];
343
+ }
344
+ // ───── Điểm phái sinh (v1) ─────
345
+ case "midpoint": {
346
+ const p1 = getPointWorld(c.p1, state);
347
+ const p2 = getPointWorld(c.p2, state);
348
+ return scale(add(p1, p2), 0.5);
349
+ }
350
+ case "centroid": {
351
+ const n = c.vertices.length;
352
+ if (n === 0) return [0, 0, 0];
353
+ let acc = [0, 0, 0];
354
+ for (const id of c.vertices) acc = add(acc, getPointWorld(id, state));
355
+ return scale(acc, 1 / n);
356
+ }
357
+ case "circumsphereCenter": {
358
+ const P = c.vertices.map((id) => getPointWorld(id, state));
359
+ if (P.length < 4) return P.length ? P[0] : [0, 0, 0];
360
+ const p0 = P[0];
361
+ const M = [[0, 0, 0], [0, 0, 0], [0, 0, 0]];
362
+ const rhs = [0, 0, 0];
363
+ for (let i = 1; i < P.length; i++) {
364
+ const r = [2 * (P[i][0] - p0[0]), 2 * (P[i][1] - p0[1]), 2 * (P[i][2] - p0[2])];
365
+ const bi = dot(P[i], P[i]) - dot(p0, p0);
366
+ for (let a = 0; a < 3; a++) {
367
+ for (let bcol = 0; bcol < 3; bcol++) M[a][bcol] += r[a] * r[bcol];
368
+ rhs[a] += r[a] * bi;
369
+ }
370
+ }
371
+ const sol = solve3(M, rhs);
372
+ if (sol) return sol;
373
+ let acc = [0, 0, 0];
374
+ for (const p of P) acc = add(acc, p);
375
+ return scale(acc, 1 / P.length);
376
+ }
377
+ case "pyramidInsphereCenter": {
378
+ const S = getPointWorld(c.apex, state);
379
+ const base = c.vertices.map((id) => getPointWorld(id, state));
380
+ const n = base.length;
381
+ if (n < 3) return S;
382
+ let G = [0, 0, 0];
383
+ for (const p of base) G = add(G, p);
384
+ G = scale(G, 1 / n);
385
+ let nb = normalize(cross(sub(base[1], base[0]), sub(base[2], base[0])));
386
+ if (dot(nb, sub(S, G)) < 0) nb = scale(nb, -1);
387
+ let m = normalize(cross(sub(base[0], S), sub(base[1], S)));
388
+ if (dot(m, sub(G, base[0])) < 0) m = scale(m, -1);
389
+ const denom = 1 - dot(m, nb);
390
+ const s = Math.abs(denom) < 1e-9 ? NaN : dot(m, sub(G, base[0])) / denom;
391
+ if (!Number.isFinite(s) || s <= 0) {
392
+ return scale(add(S, scale(G, n)), 1 / (n + 1));
393
+ }
394
+ return add(G, scale(nb, s));
395
+ }
396
+ case "faceCircumcenter": {
397
+ const P = c.vertices.map((id) => getPointWorld(id, state));
398
+ if (P.length < 3) return P.length ? P[0] : [0, 0, 0];
399
+ const [p0, p1, p2] = P;
400
+ const e1 = sub(p1, p0), e2 = sub(p2, p0);
401
+ const nrm = cross(e1, e2);
402
+ const M = [
403
+ [2 * e1[0], 2 * e1[1], 2 * e1[2]],
404
+ [2 * e2[0], 2 * e2[1], 2 * e2[2]],
405
+ [nrm[0], nrm[1], nrm[2]]
406
+ ];
407
+ const rhs = [dot(p1, p1) - dot(p0, p0), dot(p2, p2) - dot(p0, p0), dot(nrm, p0)];
408
+ const sol = solve3(M, rhs);
409
+ if (sol) return sol;
410
+ return scale(add(add(p0, p1), p2), 1 / 3);
411
+ }
412
+ case "pointAboveFace": {
413
+ const G = getPointWorld(c.base, state);
414
+ const P = c.vertices.map((id) => getPointWorld(id, state));
415
+ if (P.length < 3) return G;
416
+ let n = normalize(cross(sub(P[1], P[0]), sub(P[2], P[0])));
417
+ const S = getPointWorld(c.apex, state);
418
+ if (dot(n, sub(S, P[0])) < 0) n = scale(n, -1);
419
+ const h = dot(sub(S, P[0]), n);
420
+ if (!Number.isFinite(h) || h <= 1e-9) return G;
421
+ return add(G, scale(n, h));
422
+ }
423
+ case "intersectionLines": {
424
+ const A = getPointWorld(c.a1, state), B = getPointWorld(c.b1, state);
425
+ const C = getPointWorld(c.a2, state), D = getPointWorld(c.b2, state);
426
+ return lineLineClosestMidpoint(A, B, C, D);
427
+ }
428
+ case "intersectionLinePlane": {
429
+ const A = getPointWorld(c.a, state), B = getPointWorld(c.b, state);
430
+ const plane = state.objects[c.plane];
431
+ if (!plane || plane.kind !== "plane3d") throw new Error("intersectionLinePlane: m\u1EB7t ph\u1EB3ng thi\u1EBFu");
432
+ const { origin, normal } = getPlaneBasis(plane, state);
433
+ const dir = sub(B, A);
434
+ const dn = dot(dir, normal);
435
+ if (Math.abs(dn) < 1e-12) return A;
436
+ const t = dot(sub(origin, A), normal) / dn;
437
+ return add(A, scale(dir, t));
438
+ }
439
+ case "perpFootLine": {
440
+ const P = getPointWorld(c.from, state);
441
+ const A = getPointWorld(c.a, state), B = getPointWorld(c.b, state);
442
+ const u = sub(B, A);
443
+ const uu = dot(u, u);
444
+ const t = uu === 0 ? 0 : dot(sub(P, A), u) / uu;
445
+ return add(A, scale(u, t));
446
+ }
447
+ case "perpFootPlane": {
448
+ const P = getPointWorld(c.from, state);
449
+ const plane = state.objects[c.plane];
450
+ if (!plane || plane.kind !== "plane3d") throw new Error("perpFootPlane: m\u1EB7t ph\u1EB3ng thi\u1EBFu");
451
+ const { origin, normal } = getPlaneBasis(plane, state);
452
+ const dist2 = dot(sub(P, origin), normal);
453
+ return sub(P, scale(normal, dist2));
454
+ }
86
455
  }
87
456
  }
88
457
 
@@ -124,6 +493,15 @@ var def = {
124
493
  if (c.kind === "onLine") return `${obj.label} tr\xEAn \u0111\u01B0\u1EDDng ${c.lineId}`;
125
494
  if (c.kind === "onPolygon") return `${obj.label} tr\xEAn \u0111a gi\xE1c ${c.polygonId}`;
126
495
  if (c.kind === "onSphere") return `${obj.label} tr\xEAn m\u1EB7t c\u1EA7u ${c.sphereId}`;
496
+ if (c.kind === "midpoint") return `${obj.label} = trung \u0111i\u1EC3m ${c.p1}${c.p2}`;
497
+ if (c.kind === "centroid") return `${obj.label} = tr\u1ECDng t\xE2m ${c.vertices.join("")}`;
498
+ if (c.kind === "intersectionLines") return `${obj.label} = giao 2 \u0111\u01B0\u1EDDng (${c.a1}${c.b1}, ${c.a2}${c.b2})`;
499
+ if (c.kind === "intersectionLinePlane") return `${obj.label} = giao ${c.a}${c.b} \u2229 ${c.plane}`;
500
+ if (c.kind === "perpFootLine") return `${obj.label} = ch\xE2n \u22A5 t\u1EEB ${c.from} xu\u1ED1ng ${c.a}${c.b}`;
501
+ if (c.kind === "perpFootPlane") return `${obj.label} = ch\xE2n \u22A5 t\u1EEB ${c.from} xu\u1ED1ng ${c.plane}`;
502
+ if (c.kind === "pyramidInsphereCenter") return `${obj.label} = t\xE2m c\u1EA7u n\u1ED9i ti\u1EBFp ch\xF3p ${c.apex}.${c.vertices.join("")}`;
503
+ if (c.kind === "faceCircumcenter") return `${obj.label} = t\xE2m ngo\u1EA1i ti\u1EBFp ${c.vertices.join("")}`;
504
+ if (c.kind === "pointAboveFace") return `${obj.label} = \u0111\u1EC9nh tr\u1EE5c tr\u1EE5 \u22A5 m\u1EB7t ${c.vertices.join("")}`;
127
505
  return obj.label;
128
506
  },
129
507
  render: (obj, ctx) => {
@@ -173,6 +551,15 @@ var def = {
173
551
  () => sph.F(c.theta, c.phi)[2]
174
552
  ], opts);
175
553
  }
554
+ const getState = ctx.getState;
555
+ if (getState) {
556
+ const cw = () => constraintToWorld(c, getState());
557
+ return view.create("point3d", [
558
+ () => cw()[0],
559
+ () => cw()[1],
560
+ () => cw()[2]
561
+ ], { ...opts, needsRegularUpdate: true });
562
+ }
176
563
  return view.create("point3d", [0, 0, 0], opts);
177
564
  }
178
565
  };
@@ -221,28 +608,71 @@ var def2 = {
221
608
  registerKind(def2);
222
609
 
223
610
  // src/core/scene/kinds/line3d.ts
224
- registerKind({
611
+ function line3dConstructionRefs(c) {
612
+ switch (c.kind) {
613
+ case "planePlaneIntersection":
614
+ return [c.plane1, c.plane2];
615
+ case "lineParallelThrough":
616
+ return [c.point, c.dirA, c.dirB];
617
+ case "linePerpToPlane":
618
+ return [c.point, c.plane];
619
+ default: {
620
+ return [];
621
+ }
622
+ }
623
+ }
624
+ var def3 = {
225
625
  type: "line3d",
226
626
  schemaVersion: 1,
227
627
  migrate: {},
228
628
  validate: (a) => {
229
- if (!a?.p1 || !a?.p2) throw new Error("line3d: p1/p2 required");
629
+ if (a?.construction) return;
630
+ if (!a?.p1 || !a?.p2) throw new Error("line3d: p1/p2 required (ho\u1EB7c construction)");
631
+ },
632
+ dependsOn: (a) => a.construction ? line3dConstructionRefs(a.construction) : [a.p1, a.p2],
633
+ describe: (obj, state) => {
634
+ const c = obj.attrs.construction;
635
+ if (!c) return `\u0110\u01B0\u1EDDng ${obj.label} qua ${labelOf(obj.attrs.p1, state)}, ${labelOf(obj.attrs.p2, state)}`;
636
+ switch (c.kind) {
637
+ case "planePlaneIntersection":
638
+ return `Giao tuy\u1EBFn ${obj.label} = ${labelOf(c.plane1, state)} \u2229 ${labelOf(c.plane2, state)}`;
639
+ case "lineParallelThrough":
640
+ return `${obj.label} \u2225 ${labelOf(c.dirA, state)}${labelOf(c.dirB, state)} qua ${labelOf(c.point, state)}`;
641
+ case "linePerpToPlane":
642
+ return `${obj.label} \u22A5 ${labelOf(c.plane, state)} qua ${labelOf(c.point, state)}`;
643
+ default:
644
+ return obj.label;
645
+ }
230
646
  },
231
- dependsOn: (a) => [a.p1, a.p2],
232
- describe: (obj, state) => `\u0110\u01B0\u1EDDng ${obj.label} qua ${labelOf(obj.attrs.p1, state)}, ${labelOf(obj.attrs.p2, state)}`,
233
647
  render: (obj, ctx) => {
234
648
  const view = ctx.jxg;
649
+ const baseOpts = {
650
+ strokeColor: obj.attrs.color ?? "#0f172a",
651
+ strokeWidth: 2,
652
+ visible: obj.visible
653
+ };
654
+ const c = obj.attrs.construction;
655
+ if (c) {
656
+ const getState = ctx.getState;
657
+ if (!getState) throw new Error("line3d construction: c\u1EA7n ctx.getState");
658
+ const lc = () => lineConstructionWorld(c, getState());
659
+ return view.create("line3d", [() => lc().a, () => lc().b], {
660
+ ...baseOpts,
661
+ straightFirst: true,
662
+ straightLast: true,
663
+ needsRegularUpdate: true
664
+ });
665
+ }
235
666
  const pA = ctx.resolveRef(obj.attrs.p1);
236
667
  const pB = ctx.resolveRef(obj.attrs.p2);
237
668
  return view.create("line3d", [pA, pB], {
669
+ ...baseOpts,
238
670
  straightFirst: true,
239
- straightLast: true,
240
- strokeColor: obj.attrs.color ?? "#0f172a",
241
- strokeWidth: 2,
242
- visible: obj.visible
671
+ straightLast: true
243
672
  });
244
673
  }
245
- });
674
+ };
675
+ registerKind(def3);
246
676
 
247
677
  // src/core/scene/kinds/ray3d.ts
248
678
  registerKind({
@@ -294,29 +724,71 @@ registerKind({
294
724
  });
295
725
 
296
726
  // src/core/scene/kinds/plane3d.ts
297
- registerKind({
727
+ function plane3dConstructionRefs(c) {
728
+ switch (c.kind) {
729
+ case "planeParallelThrough":
730
+ return [c.point, c.refPlane];
731
+ case "planePerpToLine":
732
+ return [c.point, c.lineA, c.lineB];
733
+ default: {
734
+ return [];
735
+ }
736
+ }
737
+ }
738
+ var def4 = {
298
739
  type: "plane3d",
299
740
  schemaVersion: 1,
300
741
  migrate: {},
301
742
  validate: (a) => {
302
- if (!a?.p1 || !a?.p2 || !a?.p3) throw new Error("plane3d: c\u1EA7n 3 \u0111i\u1EC3m");
743
+ if (a?.construction) return;
744
+ if (!a?.p1 || !a?.p2 || !a?.p3) throw new Error("plane3d: c\u1EA7n 3 \u0111i\u1EC3m (ho\u1EB7c construction)");
745
+ },
746
+ dependsOn: (a) => a.construction ? plane3dConstructionRefs(a.construction) : [a.p1, a.p2, a.p3],
747
+ describe: (obj, state) => {
748
+ const c = obj.attrs.construction;
749
+ if (!c) {
750
+ return `M\u1EB7t ${obj.label} qua ${labelOf(obj.attrs.p1, state)}, ${labelOf(obj.attrs.p2, state)}, ${labelOf(obj.attrs.p3, state)}`;
751
+ }
752
+ switch (c.kind) {
753
+ case "planeParallelThrough":
754
+ return `${obj.label} \u2225 ${labelOf(c.refPlane, state)} qua ${labelOf(c.point, state)}`;
755
+ case "planePerpToLine":
756
+ return `${obj.label} \u22A5 ${labelOf(c.lineA, state)}${labelOf(c.lineB, state)} qua ${labelOf(c.point, state)}`;
757
+ default:
758
+ return obj.label;
759
+ }
303
760
  },
304
- dependsOn: (a) => [a.p1, a.p2, a.p3],
305
- describe: (obj, state) => `M\u1EB7t ${obj.label} qua ${labelOf(obj.attrs.p1, state)}, ${labelOf(obj.attrs.p2, state)}, ${labelOf(obj.attrs.p3, state)}`,
306
761
  render: (obj, ctx) => {
307
762
  const view = ctx.jxg;
308
- return view.create("plane3d", [
309
- ctx.resolveRef(obj.attrs.p1),
310
- ctx.resolveRef(obj.attrs.p2),
311
- ctx.resolveRef(obj.attrs.p3)
312
- ], {
763
+ const opts = {
313
764
  fillOpacity: 0.15,
314
765
  fillColor: obj.attrs.color ?? "#60a5fa",
315
766
  strokeColor: obj.attrs.color ?? "#60a5fa",
316
767
  visible: obj.visible
317
- });
768
+ };
769
+ const c = obj.attrs.construction;
770
+ if (c) {
771
+ const getState = ctx.getState;
772
+ if (!getState) throw new Error("plane3d construction: c\u1EA7n ctx.getState");
773
+ const pc = () => planeConstructionWorld(c, getState());
774
+ const diff = (which) => {
775
+ const r = pc();
776
+ const q = r[which];
777
+ return [q[0] - r.p1[0], q[1] - r.p1[1], q[2] - r.p1[2]];
778
+ };
779
+ return view.create("plane3d", [() => pc().p1, () => diff("p2"), () => diff("p3")], {
780
+ ...opts,
781
+ needsRegularUpdate: true
782
+ });
783
+ }
784
+ return view.create("plane3d", [
785
+ ctx.resolveRef(obj.attrs.p1),
786
+ ctx.resolveRef(obj.attrs.p2),
787
+ ctx.resolveRef(obj.attrs.p3)
788
+ ], opts);
318
789
  }
319
- });
790
+ };
791
+ registerKind(def4);
320
792
 
321
793
  // src/core/scene/kinds/polygon3d.ts
322
794
  registerKind({
@@ -396,6 +868,27 @@ registerKind({
396
868
  }
397
869
  });
398
870
 
871
+ // src/core/scene/kinds/_ringBasis.ts
872
+ function perpBasis(axis) {
873
+ const n = Math.hypot(axis[0], axis[1], axis[2]);
874
+ if (n < 1e-12) return [[1, 0, 0], [0, 1, 0]];
875
+ const a = [axis[0] / n, axis[1] / n, axis[2] / n];
876
+ const seed = Math.abs(a[2]) < 0.9 ? [0, 0, 1] : [1, 0, 0];
877
+ let u = [
878
+ seed[1] * a[2] - seed[2] * a[1],
879
+ seed[2] * a[0] - seed[0] * a[2],
880
+ seed[0] * a[1] - seed[1] * a[0]
881
+ ];
882
+ const un = Math.hypot(u[0], u[1], u[2]) || 1;
883
+ u = [u[0] / un, u[1] / un, u[2] / un];
884
+ const v = [
885
+ a[1] * u[2] - a[2] * u[1],
886
+ a[2] * u[0] - a[0] * u[2],
887
+ a[0] * u[1] - a[1] * u[0]
888
+ ];
889
+ return [u, v];
890
+ }
891
+
399
892
  // src/core/scene/kinds/cylinder3d.ts
400
893
  var CURVED_SEGMENTS = 16;
401
894
  registerKind({
@@ -415,14 +908,17 @@ registerKind({
415
908
  const r = obj.attrs.radius;
416
909
  const ax = a.X?.() ?? 0, ay = a.Y?.() ?? 0, az = a.Z?.() ?? 0;
417
910
  const bx = b.X?.() ?? 0, by = b.Y?.() ?? 0, bz = b.Z?.() ?? 0;
911
+ const [u, v] = perpBasis([bx - ax, by - ay, bz - az]);
418
912
  const baseRing = [];
419
913
  const topRing = [];
420
914
  for (let i = 0; i < CURVED_SEGMENTS; i++) {
421
915
  const theta = i / CURVED_SEGMENTS * Math.PI * 2;
422
- const dx = r * Math.cos(theta);
423
- const dy = r * Math.sin(theta);
424
- baseRing.push([ax + dx, ay + dy, az]);
425
- topRing.push([bx + dx, by + dy, bz]);
916
+ const c = Math.cos(theta), s = Math.sin(theta);
917
+ const ox = r * (c * u[0] + s * v[0]);
918
+ const oy = r * (c * u[1] + s * v[1]);
919
+ const oz = r * (c * u[2] + s * v[2]);
920
+ baseRing.push([ax + ox, ay + oy, az + oz]);
921
+ topRing.push([bx + ox, by + oy, bz + oz]);
426
922
  }
427
923
  const vertices = [...baseRing, ...topRing];
428
924
  const faces = [];
@@ -433,7 +929,7 @@ registerKind({
433
929
  faces.push([i, next, CURVED_SEGMENTS + next, CURVED_SEGMENTS + i]);
434
930
  }
435
931
  const vertJxgs = vertices.map(
436
- (v, i) => view.create("point3d", v, {
932
+ (v2, i) => view.create("point3d", v2, {
437
933
  id: `${obj.id}.v${i}`,
438
934
  visible: false,
439
935
  fixed: true,
@@ -477,10 +973,16 @@ registerKind({
477
973
  apexPt.Y?.() ?? 0,
478
974
  apexPt.Z?.() ?? 0
479
975
  ];
976
+ const [u, v] = perpBasis([apexCoords[0] - bx, apexCoords[1] - by, apexCoords[2] - bz]);
480
977
  const baseRing = [];
481
978
  for (let i = 0; i < CURVED_SEGMENTS2; i++) {
482
979
  const theta = i / CURVED_SEGMENTS2 * Math.PI * 2;
483
- baseRing.push([bx + r * Math.cos(theta), by + r * Math.sin(theta), bz]);
980
+ const c = Math.cos(theta), s = Math.sin(theta);
981
+ baseRing.push([
982
+ bx + r * (c * u[0] + s * v[0]),
983
+ by + r * (c * u[1] + s * v[1]),
984
+ bz + r * (c * u[2] + s * v[2])
985
+ ]);
484
986
  }
485
987
  const apexIdx = baseRing.length;
486
988
  const vertices = [...baseRing, apexCoords];
@@ -489,7 +991,7 @@ registerKind({
489
991
  faces.push([i, (i + 1) % CURVED_SEGMENTS2, apexIdx]);
490
992
  }
491
993
  const vertJxgs = vertices.map(
492
- (v, i) => view.create("point3d", v, {
994
+ (v2, i) => view.create("point3d", v2, {
493
995
  id: `${obj.id}.v${i}`,
494
996
  visible: false,
495
997
  fixed: true,
@@ -563,8 +1065,12 @@ function constraintRefs2D(c) {
563
1065
  return [c.line, c.circle, c.other];
564
1066
  case "tangencyPoint":
565
1067
  return [c.circle, c.onLine];
566
- case "arcMidpoint":
567
- return [c.circle, c.a, c.b, c.notContaining];
1068
+ case "arcMidpoint": {
1069
+ const containment = c.notContaining ?? c.containing;
1070
+ return containment ? [c.circle, c.a, c.b, containment] : [c.circle, c.a, c.b];
1071
+ }
1072
+ case "mixtilinearPoint":
1073
+ return [c.vertices[0], c.vertices[1], c.vertices[2]];
568
1074
  case "pointAtDistance": {
569
1075
  const d = c.distance;
570
1076
  const extra = d.kind === "circleRadius" ? [d.circle] : d.kind === "segmentLength" ? [d.p1, d.p2] : [];
@@ -572,8 +1078,16 @@ function constraintRefs2D(c) {
572
1078
  }
573
1079
  case "excenter":
574
1080
  return [c.vertices[0], c.vertices[1], c.vertices[2]];
575
- default:
1081
+ case "commonTangentPoint":
1082
+ return [c.circles[0], c.circles[1]];
1083
+ // Các kind KHÔNG có ref scene-object (toạ độ literal): liệt kê TƯỜNG MINH để
1084
+ // exhaustive never-guard bên dưới buộc khai báo khi thêm constraint kind mới.
1085
+ case "free":
1086
+ case "onAxis":
1087
+ return [];
1088
+ default: {
576
1089
  return [];
1090
+ }
577
1091
  }
578
1092
  }
579
1093
 
@@ -777,7 +1291,7 @@ function dist(p, q) {
777
1291
  function sideOf(a, b, p) {
778
1292
  return (b[0] - a[0]) * (p[1] - a[1]) - (b[1] - a[1]) * (p[0] - a[0]);
779
1293
  }
780
- function arcMidpoint(center, radius, a, b, notContaining) {
1294
+ function arcMidpoint(center, radius, a, b, reference, sameSide = false) {
781
1295
  const mcx = (a[0] + b[0]) / 2;
782
1296
  const mcy = (a[1] + b[1]) / 2;
783
1297
  let ux = mcx - center[0];
@@ -792,12 +1306,18 @@ function arcMidpoint(center, radius, a, b, notContaining) {
792
1306
  uy /= len;
793
1307
  const cand1 = [center[0] + radius * ux, center[1] + radius * uy];
794
1308
  const cand2 = [center[0] - radius * ux, center[1] - radius * uy];
1309
+ if (!reference) return cand1[1] >= cand2[1] ? cand1 : cand2;
1310
+ const notContaining = reference;
795
1311
  const sN = sideOf(a, b, notContaining);
796
1312
  if (Math.abs(sN) < 1e-9) {
797
- return dist(cand1, notContaining) >= dist(cand2, notContaining) ? cand1 : cand2;
1313
+ const far = dist(cand1, notContaining) >= dist(cand2, notContaining) ? cand1 : cand2;
1314
+ const near = far === cand1 ? cand2 : cand1;
1315
+ return sameSide ? near : far;
798
1316
  }
799
1317
  const s1 = sideOf(a, b, cand1);
800
- return s1 * sN < 0 ? cand1 : cand2;
1318
+ const opp = s1 * sN < 0 ? cand1 : cand2;
1319
+ const same = opp === cand1 ? cand2 : cand1;
1320
+ return sameSide ? same : opp;
801
1321
  }
802
1322
  function excenter(vertices, oppositeIndex) {
803
1323
  const [A, B, C] = vertices;
@@ -813,6 +1333,35 @@ function excenter(vertices, oppositeIndex) {
813
1333
  (w[0] * A[1] + w[1] * B[1] + w[2] * C[1]) / sum
814
1334
  ];
815
1335
  }
1336
+ function circumcenterXY(a, b, c) {
1337
+ const ax = a[0], ay = a[1], bx = b[0], by = b[1], cx = c[0], cy = c[1];
1338
+ const d = 2 * (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by));
1339
+ if (Math.abs(d) < 1e-12) return [(ax + bx + cx) / 3, (ay + by + cy) / 3];
1340
+ const ux = ((ax * ax + ay * ay) * (by - cy) + (bx * bx + by * by) * (cy - ay) + (cx * cx + cy * cy) * (ay - by)) / d;
1341
+ const uy = ((ax * ax + ay * ay) * (cx - bx) + (bx * bx + by * by) * (ax - cx) + (cx * cx + cy * cy) * (bx - ax)) / d;
1342
+ return [ux, uy];
1343
+ }
1344
+ function mixtilinearPoint(a, b, c, which) {
1345
+ const O = circumcenterXY(a, b, c);
1346
+ const R = Math.hypot(O[0] - a[0], O[1] - a[1]);
1347
+ const ux1 = (b[0] - a[0]) / (Math.hypot(b[0] - a[0], b[1] - a[1]) || 1);
1348
+ const uy1 = (b[1] - a[1]) / (Math.hypot(b[0] - a[0], b[1] - a[1]) || 1);
1349
+ const ux2 = (c[0] - a[0]) / (Math.hypot(c[0] - a[0], c[1] - a[1]) || 1);
1350
+ const uy2 = (c[1] - a[1]) / (Math.hypot(c[0] - a[0], c[1] - a[1]) || 1);
1351
+ let bx = ux1 + ux2, by = uy1 + uy2;
1352
+ const bl = Math.hypot(bx, by) || 1;
1353
+ bx /= bl;
1354
+ by /= bl;
1355
+ const cosA = ux1 * ux2 + uy1 * uy2;
1356
+ const sinHalf = Math.sqrt(Math.max(0, (1 - cosA) / 2));
1357
+ const cos2Half = Math.max(1e-9, (1 + cosA) / 2);
1358
+ const dotAO = bx * (O[0] - a[0]) + by * (O[1] - a[1]);
1359
+ const d = 2 * (dotAO - R * sinHalf) / cos2Half;
1360
+ const K = [a[0] + d * bx, a[1] + d * by];
1361
+ if (which === "center") return K;
1362
+ const kl = Math.hypot(K[0] - O[0], K[1] - O[1]) || 1;
1363
+ return [O[0] + R * (K[0] - O[0]) / kl, O[1] + R * (K[1] - O[1]) / kl];
1364
+ }
816
1365
  function pointAtDistanceCoord(from, through, d) {
817
1366
  const dx = through[0] - from[0];
818
1367
  const dy = through[1] - from[1];
@@ -831,29 +1380,38 @@ function radicalAxisFoot(o1, r1, o2, r2) {
831
1380
  var arcMidpointConstraint = definePointConstraint({
832
1381
  kind: "arcMidpoint",
833
1382
  validate: (c) => {
834
- if (!c.circle || !c.a || !c.b || !c.notContaining) {
835
- throw new Error("point.arcMidpoint: circle, a, b, notContaining b\u1EAFt bu\u1ED9c");
1383
+ if (!c.circle || !c.a || !c.b) {
1384
+ throw new Error("point.arcMidpoint: circle, a, b b\u1EAFt bu\u1ED9c");
1385
+ }
1386
+ if (c.notContaining && c.containing) {
1387
+ throw new Error("point.arcMidpoint: kh\xF4ng th\u1EC3 v\u1EEBa notContaining v\u1EEBa containing");
836
1388
  }
837
1389
  },
838
1390
  describe: (obj, state, c) => {
839
1391
  const al = state?.objects[c.a]?.label ?? c.a;
840
1392
  const bl = state?.objects[c.b]?.label ?? c.b;
841
- const nl = state?.objects[c.notContaining]?.label ?? c.notContaining;
842
- return `${obj.label} = trung \u0111i\u1EC3m cung ${al}${bl} (kh\xF4ng ch\u1EE9a ${nl})`;
1393
+ const ref = c.containing ?? c.notContaining;
1394
+ if (!ref) return `${obj.label} = trung \u0111i\u1EC3m cung ${al}${bl}`;
1395
+ const rl = state?.objects[ref]?.label ?? ref;
1396
+ const rel = c.containing ? "ch\u1EE9a" : "kh\xF4ng ch\u1EE9a";
1397
+ return `${obj.label} = trung \u0111i\u1EC3m cung ${al}${bl} (${rel} ${rl})`;
843
1398
  },
844
1399
  render: (obj, ctx, c, opts) => {
845
1400
  const board = ctx.jxg;
846
1401
  const circle = ctx.resolveRef(c.circle);
847
1402
  const A = ctx.resolveRef(c.a);
848
1403
  const B = ctx.resolveRef(c.b);
849
- const N = ctx.resolveRef(c.notContaining);
1404
+ const refName = c.containing ?? c.notContaining;
1405
+ const ref = refName ? ctx.resolveRef(refName) : void 0;
1406
+ const sameSide = !!c.containing;
850
1407
  const O = circle?.center ?? circle?.midpoint ?? circle;
851
1408
  const am = () => arcMidpoint(
852
1409
  [O.X(), O.Y()],
853
1410
  circle.Radius(),
854
1411
  [A.X(), A.Y()],
855
1412
  [B.X(), B.Y()],
856
- [N.X(), N.Y()]
1413
+ ref ? [ref.X(), ref.Y()] : void 0,
1414
+ sameSide
857
1415
  );
858
1416
  return board.create("point", [() => am()[0], () => am()[1]], opts);
859
1417
  }
@@ -894,6 +1452,36 @@ var excenterConstraint = definePointConstraint({
894
1452
  }
895
1453
  });
896
1454
 
1455
+ // src/core/scene/kinds/point-constraints/mixtilinearPoint.ts
1456
+ var mixtilinearPointConstraint = definePointConstraint({
1457
+ kind: "mixtilinearPoint",
1458
+ validate: (c) => {
1459
+ if (!Array.isArray(c.vertices) || c.vertices.length !== 3 || !c.vertices.every(Boolean)) {
1460
+ throw new Error("point.mixtilinearPoint: vertices ph\u1EA3i l\xE0 tuple 3 id non-empty");
1461
+ }
1462
+ if (c.which !== "center" && c.which !== "touch") {
1463
+ throw new Error("point.mixtilinearPoint: which ph\u1EA3i 'center' | 'touch'");
1464
+ }
1465
+ },
1466
+ describe: (obj, state, c) => {
1467
+ const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
1468
+ return c.which === "center" ? `${obj.label} = t\xE2m \u0111\u01B0\u1EDDng tr\xF2n mixtilinear \u0394${labels}` : `${obj.label} = ti\u1EBFp \u0111i\u1EC3m mixtilinear \u0394${labels} v\u1EDBi (O)`;
1469
+ },
1470
+ render: (obj, ctx, c, opts) => {
1471
+ const board = ctx.jxg;
1472
+ const a = ctx.resolveRef(c.vertices[0]);
1473
+ const b = ctx.resolveRef(c.vertices[1]);
1474
+ const d = ctx.resolveRef(c.vertices[2]);
1475
+ const mp = () => mixtilinearPoint(
1476
+ [a.X(), a.Y()],
1477
+ [b.X(), b.Y()],
1478
+ [d.X(), d.Y()],
1479
+ c.which
1480
+ );
1481
+ return board.create("point", [() => mp()[0], () => mp()[1]], opts);
1482
+ }
1483
+ });
1484
+
897
1485
  // src/core/scene/kinds/point-constraints/shared.ts
898
1486
  function buildJxgTransforms(board, ctx, t) {
899
1487
  switch (t.kind) {
@@ -922,19 +1510,19 @@ function buildJxgTransforms(board, ctx, t) {
922
1510
  }
923
1511
  }
924
1512
  function makeDistanceFn(ctx, d) {
925
- const scale = d.scale ?? 1;
1513
+ const scale2 = d.scale ?? 1;
926
1514
  const offset = d.offset ?? 0;
927
1515
  if (d.kind === "literal") {
928
1516
  const v = d.value;
929
- return () => scale * v + offset;
1517
+ return () => scale2 * v + offset;
930
1518
  }
931
1519
  if (d.kind === "segmentLength") {
932
1520
  const p = ctx.resolveRef(d.p1);
933
1521
  const q = ctx.resolveRef(d.p2);
934
- return () => scale * Math.hypot(p.X() - q.X(), p.Y() - q.Y()) + offset;
1522
+ return () => scale2 * Math.hypot(p.X() - q.X(), p.Y() - q.Y()) + offset;
935
1523
  }
936
1524
  const circle = ctx.resolveRef(d.circle);
937
- return () => scale * circle.Radius() + offset;
1525
+ return () => scale2 * circle.Radius() + offset;
938
1526
  }
939
1527
  function buildPointOpts(obj) {
940
1528
  return {
@@ -945,7 +1533,8 @@ function buildPointOpts(obj) {
945
1533
  strokeColor: obj.attrs.color ?? "#1e40af",
946
1534
  fillColor: obj.attrs.color ?? "#1e40af",
947
1535
  face: obj.attrs.face ?? "o",
948
- size: obj.attrs.size ?? 4
1536
+ size: obj.attrs.size ?? 4,
1537
+ ...labelOpts(obj.attrs.labelOffset, [10, 10])
949
1538
  };
950
1539
  }
951
1540
 
@@ -969,6 +1558,61 @@ var pointAtDistanceConstraint = definePointConstraint({
969
1558
  }
970
1559
  });
971
1560
 
1561
+ // src/stamps/geometry-2d/geometry/commonTangent.ts
1562
+ var EPS = 1e-9;
1563
+ function computeCommonTangentPoint(O1, r1, O2, r2, on, variant, side) {
1564
+ const dx = O2[0] - O1[0];
1565
+ const dy = O2[1] - O1[1];
1566
+ const d = Math.hypot(dx, dy);
1567
+ if (d < EPS) return null;
1568
+ const beta = Math.atan2(dy, dx);
1569
+ const ratio = variant === "external" ? (r1 - r2) / d : (r1 + r2) / d;
1570
+ if (Math.abs(ratio) > 1) return null;
1571
+ const gamma = Math.acos(ratio);
1572
+ const s = side === 0 ? 1 : -1;
1573
+ const angle = beta + s * gamma;
1574
+ const nx = Math.cos(angle);
1575
+ const ny = Math.sin(angle);
1576
+ if (on === 0) {
1577
+ return [O1[0] + r1 * nx, O1[1] + r1 * ny];
1578
+ }
1579
+ if (variant === "external") {
1580
+ return [O2[0] + r2 * nx, O2[1] + r2 * ny];
1581
+ }
1582
+ return [O2[0] - r2 * nx, O2[1] - r2 * ny];
1583
+ }
1584
+
1585
+ // src/core/scene/kinds/point-constraints/commonTangentPoint.ts
1586
+ var commonTangentPointConstraint = definePointConstraint({
1587
+ kind: "commonTangentPoint",
1588
+ describe: (obj, state, c) => {
1589
+ const c1 = state?.objects[c.circles[0]]?.label ?? c.circles[0];
1590
+ const c2 = state?.objects[c.circles[1]]?.label ?? c.circles[1];
1591
+ const v = c.variant === "internal" ? "trong" : "ngo\xE0i";
1592
+ return `${obj.label} = ti\u1EBFp \u0111i\u1EC3m ti\u1EBFp tuy\u1EBFn chung ${v} ${c1},${c2} (tr\xEAn ${c.on === 0 ? c1 : c2})`;
1593
+ },
1594
+ render: (obj, ctx, c, opts) => {
1595
+ const board = ctx.jxg;
1596
+ const k1 = ctx.resolveRef(c.circles[0]);
1597
+ const k2 = ctx.resolveRef(c.circles[1]);
1598
+ const O1 = k1?.center ?? k1?.midpoint ?? k1;
1599
+ const O2 = k2?.center ?? k2?.midpoint ?? k2;
1600
+ const compute = () => {
1601
+ const t = computeCommonTangentPoint(
1602
+ [O1.X(), O1.Y()],
1603
+ k1.Radius(),
1604
+ [O2.X(), O2.Y()],
1605
+ k2.Radius(),
1606
+ c.on,
1607
+ c.variant,
1608
+ c.side
1609
+ );
1610
+ return t ?? [0, 0];
1611
+ };
1612
+ return board.create("point", [() => compute()[0], () => compute()[1]], opts);
1613
+ }
1614
+ });
1615
+
972
1616
  // src/core/scene/kinds/point-constraints/circleIntersection.ts
973
1617
  var circleIntersectionConstraint = definePointConstraint({
974
1618
  kind: "circleIntersection",
@@ -1183,7 +1827,9 @@ var ALL = [
1183
1827
  centroidConstraint,
1184
1828
  arcMidpointConstraint,
1185
1829
  excenterConstraint,
1830
+ mixtilinearPointConstraint,
1186
1831
  pointAtDistanceConstraint,
1832
+ commonTangentPointConstraint,
1187
1833
  circleIntersectionConstraint,
1188
1834
  circleSecondIntersectionConstraint,
1189
1835
  secondIntersectionConstraint,
@@ -1198,7 +1844,7 @@ var ALL = [
1198
1844
  var POINT_CONSTRAINTS = new Map(ALL.map((m) => [m.kind, m]));
1199
1845
 
1200
1846
  // src/core/scene/kinds/point.ts
1201
- var def3 = {
1847
+ var def5 = {
1202
1848
  type: "point",
1203
1849
  schemaVersion: 1,
1204
1850
  migrate: {},
@@ -1265,7 +1911,8 @@ var def3 = {
1265
1911
  strokeColor: obj.attrs.color ?? "#1e40af",
1266
1912
  fillColor: obj.attrs.color ?? "#1e40af",
1267
1913
  face: obj.attrs.face ?? "o",
1268
- size: obj.attrs.size ?? 4
1914
+ size: obj.attrs.size ?? 4,
1915
+ ...labelOpts(obj.attrs.labelOffset, [10, 10])
1269
1916
  });
1270
1917
  } catch {
1271
1918
  }
@@ -1275,10 +1922,10 @@ var def3 = {
1275
1922
  throw new Error("point: constraint kind changed \u2014 recreate");
1276
1923
  }
1277
1924
  };
1278
- registerKind(def3);
1925
+ registerKind(def5);
1279
1926
 
1280
1927
  // src/core/scene/kinds/segment.ts
1281
- var def4 = {
1928
+ var def6 = {
1282
1929
  type: "segment",
1283
1930
  schemaVersion: 1,
1284
1931
  migrate: {},
@@ -1309,11 +1956,12 @@ var def4 = {
1309
1956
  strokeWidth: obj.attrs.width ?? 2,
1310
1957
  dash: obj.attrs.dash ?? 0,
1311
1958
  visible: obj.visible,
1312
- fixed: obj.locked
1959
+ fixed: obj.locked,
1960
+ ...labelOpts(obj.attrs.labelOffset)
1313
1961
  });
1314
1962
  }
1315
1963
  };
1316
- registerKind(def4);
1964
+ registerKind(def6);
1317
1965
 
1318
1966
  // src/core/scene/kinds/line.ts
1319
1967
  function stripBorderSuffix(id) {
@@ -1339,7 +1987,7 @@ function constructionRefs(c) {
1339
1987
  return [c.throughPoint, c.toCircle];
1340
1988
  }
1341
1989
  }
1342
- var def5 = {
1990
+ var def7 = {
1343
1991
  type: "line",
1344
1992
  schemaVersion: 1,
1345
1993
  migrate: {},
@@ -1380,7 +2028,8 @@ var def5 = {
1380
2028
  strokeWidth: obj.attrs.width ?? 2,
1381
2029
  dash: obj.attrs.dash ?? 0,
1382
2030
  visible: obj.visible,
1383
- fixed: obj.locked
2031
+ fixed: obj.locked,
2032
+ ...labelOpts(obj.attrs.labelOffset)
1384
2033
  };
1385
2034
  const c = obj.attrs.construction;
1386
2035
  if (!c) {
@@ -1534,10 +2183,10 @@ var def5 = {
1534
2183
  }
1535
2184
  }
1536
2185
  };
1537
- registerKind(def5);
2186
+ registerKind(def7);
1538
2187
 
1539
2188
  // src/core/scene/kinds/ray.ts
1540
- var def6 = {
2189
+ var def8 = {
1541
2190
  type: "ray",
1542
2191
  schemaVersion: 1,
1543
2192
  migrate: {},
@@ -1562,10 +2211,10 @@ var def6 = {
1562
2211
  });
1563
2212
  }
1564
2213
  };
1565
- registerKind(def6);
2214
+ registerKind(def8);
1566
2215
 
1567
2216
  // src/core/scene/kinds/vector.ts
1568
- var def7 = {
2217
+ var def9 = {
1569
2218
  type: "vector",
1570
2219
  schemaVersion: 1,
1571
2220
  migrate: {},
@@ -1587,7 +2236,7 @@ var def7 = {
1587
2236
  });
1588
2237
  }
1589
2238
  };
1590
- registerKind(def7);
2239
+ registerKind(def9);
1591
2240
 
1592
2241
  // src/core/scene/kinds/circle.ts
1593
2242
  function asConstruction(a) {
@@ -1615,7 +2264,7 @@ function constructionRefs2(c) {
1615
2264
  return [c.p1, c.p2];
1616
2265
  }
1617
2266
  }
1618
- var def8 = {
2267
+ var def10 = {
1619
2268
  type: "circle",
1620
2269
  schemaVersion: 1,
1621
2270
  migrate: {},
@@ -1675,15 +2324,17 @@ var def8 = {
1675
2324
  const board = ctx.jxg;
1676
2325
  const isCenterLabel = (l) => /^[A-Z]['′]?\d*$/u.test(l);
1677
2326
  const isCenter = isCenterLabel(obj.label);
2327
+ const isRenamedCircle = /_c$/.test(obj.label);
1678
2328
  const baseOpts = {
1679
2329
  name: obj.label,
1680
- withLabel: isCenter ? obj.attrs.showLabel ?? false : true,
2330
+ withLabel: isCenter ? obj.attrs.showLabel ?? false : isRenamedCircle ? false : true,
1681
2331
  strokeColor: obj.attrs.color ?? "#0f172a",
1682
2332
  strokeWidth: obj.attrs.width ?? 2,
1683
2333
  dash: obj.attrs.dash ?? 0,
1684
2334
  fillColor: "none",
1685
2335
  visible: obj.visible,
1686
- fixed: obj.locked
2336
+ fixed: obj.locked,
2337
+ ...labelOpts(obj.attrs.labelOffset)
1687
2338
  };
1688
2339
  const c = asConstruction(obj.attrs);
1689
2340
  if (c?.kind === "circumscribed") {
@@ -1765,7 +2416,7 @@ var def8 = {
1765
2416
  return board.create("circle", [center, surface], baseOpts);
1766
2417
  }
1767
2418
  };
1768
- registerKind(def8);
2419
+ registerKind(def10);
1769
2420
 
1770
2421
  // src/core/scene/kinds/arc.ts
1771
2422
  function constructionRefs3(c) {
@@ -1778,7 +2429,7 @@ function constructionRefs3(c) {
1778
2429
  return [c.p1, c.p2, c.p3];
1779
2430
  }
1780
2431
  }
1781
- var def9 = {
2432
+ var def11 = {
1782
2433
  type: "arc",
1783
2434
  schemaVersion: 1,
1784
2435
  migrate: {},
@@ -1836,10 +2487,10 @@ var def9 = {
1836
2487
  return board.create("circumcirclearc", [A, B, C], baseOpts);
1837
2488
  }
1838
2489
  };
1839
- registerKind(def9);
2490
+ registerKind(def11);
1840
2491
 
1841
2492
  // src/core/scene/kinds/sector.ts
1842
- var def10 = {
2493
+ var def12 = {
1843
2494
  type: "sector",
1844
2495
  schemaVersion: 1,
1845
2496
  migrate: {},
@@ -1879,7 +2530,7 @@ var def10 = {
1879
2530
  });
1880
2531
  }
1881
2532
  };
1882
- registerKind(def10);
2533
+ registerKind(def12);
1883
2534
 
1884
2535
  // src/core/scene/kinds/polygon.ts
1885
2536
  function regularPolygonName(n) {
@@ -1923,7 +2574,7 @@ function specialShapeName(kind) {
1923
2574
  return "";
1924
2575
  }
1925
2576
  }
1926
- var def11 = {
2577
+ var def13 = {
1927
2578
  type: "polygon",
1928
2579
  schemaVersion: 1,
1929
2580
  migrate: {},
@@ -2110,10 +2761,10 @@ var def11 = {
2110
2761
  return poly;
2111
2762
  }
2112
2763
  };
2113
- registerKind(def11);
2764
+ registerKind(def13);
2114
2765
 
2115
2766
  // src/core/scene/kinds/intersection.ts
2116
- var def12 = {
2767
+ var def14 = {
2117
2768
  type: "intersection",
2118
2769
  schemaVersion: 1,
2119
2770
  migrate: {},
@@ -2141,8 +2792,11 @@ var def12 = {
2141
2792
  const opts = {
2142
2793
  name: obj.label,
2143
2794
  withLabel: true,
2144
- strokeColor: obj.attrs.color ?? "#dc2626",
2145
- fillColor: obj.attrs.color ?? "#dc2626",
2795
+ // Cùng màu xanh với mọi điểm khác (buildPointOpts dùng '#1e40af').
2796
+ // Trước đây điểm giao tô đỏ '#dc2626' → tách biệt thị giác nhưng gây
2797
+ // khó hiểu ("vì sao điểm này khác màu?"). Giữ chung một màu.
2798
+ strokeColor: obj.attrs.color ?? "#1e40af",
2799
+ fillColor: obj.attrs.color ?? "#1e40af",
2146
2800
  visible: obj.visible,
2147
2801
  fixed: obj.locked
2148
2802
  };
@@ -2151,12 +2805,44 @@ var def12 = {
2151
2805
  }
2152
2806
  const branch = obj.attrs.branch ?? 0;
2153
2807
  return board.create("intersection", [a, b, branch], opts);
2808
+ },
2809
+ /**
2810
+ * Cập nhật TẠI CHỖ các thuộc tính "trang trí" (tên/màu/ẩn-hiện/khoá) qua
2811
+ * setAttribute — giữ nguyên JxgObj identity nên các object phụ thuộc điểm
2812
+ * giao (đường thẳng qua nó, …) KHÔNG bị stale parent ref. Mô phỏng update
2813
+ * hook của point.ts.
2814
+ *
2815
+ * Nếu định nghĩa hình học đổi (kind/ref1/ref2/branch) thì throw → renderer
2816
+ * fallback remove + create để JSXGraph dựng lại phép giao đúng.
2817
+ */
2818
+ update: (obj, prev, ctx, existing) => {
2819
+ const a = obj.attrs;
2820
+ const p = prev.attrs;
2821
+ const branchA = a.branch;
2822
+ const branchP = p.branch;
2823
+ if (a.kind !== p.kind || a.ref1 !== p.ref1 || a.ref2 !== p.ref2 || branchA !== branchP) {
2824
+ throw new Error("intersection: \u0111\u1ECBnh ngh\u0129a h\xECnh h\u1ECDc \u0111\u1ED5i \u2014 recreate");
2825
+ }
2826
+ const el = existing;
2827
+ if (typeof el.setAttribute === "function") {
2828
+ try {
2829
+ el.setAttribute({
2830
+ name: obj.label,
2831
+ withLabel: true,
2832
+ strokeColor: a.color ?? "#1e40af",
2833
+ fillColor: a.color ?? "#1e40af",
2834
+ visible: obj.visible,
2835
+ fixed: obj.locked
2836
+ });
2837
+ } catch {
2838
+ }
2839
+ }
2154
2840
  }
2155
2841
  };
2156
- registerKind(def12);
2842
+ registerKind(def14);
2157
2843
 
2158
2844
  // src/core/scene/kinds/angle.ts
2159
- var def13 = {
2845
+ var def15 = {
2160
2846
  type: "angle",
2161
2847
  schemaVersion: 1,
2162
2848
  migrate: {},
@@ -2192,10 +2878,10 @@ var def13 = {
2192
2878
  });
2193
2879
  }
2194
2880
  };
2195
- registerKind(def13);
2881
+ registerKind(def15);
2196
2882
 
2197
2883
  // src/core/scene/kinds/distance.ts
2198
- var def14 = {
2884
+ var def16 = {
2199
2885
  type: "distance",
2200
2886
  schemaVersion: 1,
2201
2887
  migrate: {},
@@ -2224,10 +2910,10 @@ var def14 = {
2224
2910
  });
2225
2911
  }
2226
2912
  };
2227
- registerKind(def14);
2913
+ registerKind(def16);
2228
2914
 
2229
2915
  // src/core/scene/kinds/function2d.ts
2230
- var def15 = {
2916
+ var def17 = {
2231
2917
  type: "function2d",
2232
2918
  schemaVersion: 1,
2233
2919
  migrate: {},
@@ -2266,10 +2952,10 @@ var def15 = {
2266
2952
  });
2267
2953
  }
2268
2954
  };
2269
- registerKind(def15);
2955
+ registerKind(def17);
2270
2956
 
2271
2957
  // src/core/scene/kinds/parameter.ts
2272
- var def16 = {
2958
+ var def18 = {
2273
2959
  type: "parameter",
2274
2960
  schemaVersion: 1,
2275
2961
  migrate: {},
@@ -2287,10 +2973,10 @@ var def16 = {
2287
2973
  render: () => null
2288
2974
  // Không render lên board
2289
2975
  };
2290
- registerKind(def16);
2976
+ registerKind(def18);
2291
2977
 
2292
2978
  // src/core/scene/kinds/pointOnCurve.ts
2293
- var def17 = {
2979
+ var def19 = {
2294
2980
  type: "pointOnCurve",
2295
2981
  schemaVersion: 1,
2296
2982
  migrate: {},
@@ -2317,10 +3003,10 @@ var def17 = {
2317
3003
  });
2318
3004
  }
2319
3005
  };
2320
- registerKind(def17);
3006
+ registerKind(def19);
2321
3007
 
2322
3008
  // src/core/scene/kinds/tangent2d.ts
2323
- var def18 = {
3009
+ var def20 = {
2324
3010
  type: "tangent2d",
2325
3011
  schemaVersion: 1,
2326
3012
  migrate: {},
@@ -2343,7 +3029,7 @@ var def18 = {
2343
3029
  });
2344
3030
  }
2345
3031
  };
2346
- registerKind(def18);
3032
+ registerKind(def20);
2347
3033
 
2348
3034
  // src/core/scene/expressions/evaluator.ts
2349
3035
  var DEFAULT_SAMPLES = 1e3;
@@ -2412,7 +3098,7 @@ function scanExtrema(fn, xMin, xMax, samples = DEFAULT_SAMPLES) {
2412
3098
  }
2413
3099
 
2414
3100
  // src/core/scene/kinds/extremum2d.ts
2415
- var def19 = {
3101
+ var def21 = {
2416
3102
  type: "extremum2d",
2417
3103
  schemaVersion: 1,
2418
3104
  migrate: {},
@@ -2445,10 +3131,10 @@ var def19 = {
2445
3131
  }));
2446
3132
  }
2447
3133
  };
2448
- registerKind(def19);
3134
+ registerKind(def21);
2449
3135
 
2450
3136
  // src/core/scene/kinds/root2d.ts
2451
- var def20 = {
3137
+ var def22 = {
2452
3138
  type: "root2d",
2453
3139
  schemaVersion: 1,
2454
3140
  migrate: {},
@@ -2478,10 +3164,10 @@ var def20 = {
2478
3164
  }));
2479
3165
  }
2480
3166
  };
2481
- registerKind(def20);
3167
+ registerKind(def22);
2482
3168
 
2483
3169
  // src/core/scene/kinds/slope2d.ts
2484
- var def21 = {
3170
+ var def23 = {
2485
3171
  type: "slope2d",
2486
3172
  schemaVersion: 1,
2487
3173
  migrate: {},
@@ -2505,7 +3191,7 @@ var def21 = {
2505
3191
  });
2506
3192
  }
2507
3193
  };
2508
- registerKind(def21);
3194
+ registerKind(def23);
2509
3195
 
2510
3196
  // src/stamps/shared/serializeScene.ts
2511
3197
  function serializeScene(state) {
@@ -2531,6 +3217,6 @@ function deserializeScene(domain, raw) {
2531
3217
  return createEmptyState(domain);
2532
3218
  }
2533
3219
 
2534
- export { deserializeScene, listObjects, nextLabel, serializeScene, useEditorState };
2535
- //# sourceMappingURL=chunk-ZTQBUKLJ.mjs.map
2536
- //# sourceMappingURL=chunk-ZTQBUKLJ.mjs.map
3220
+ export { constraintToWorld, deserializeScene, listObjects, nextLabel, planeConstructionWorld, serializeScene, useEditorState };
3221
+ //# sourceMappingURL=chunk-4EXRNZRX.mjs.map
3222
+ //# sourceMappingURL=chunk-4EXRNZRX.mjs.map