@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.
- package/dist/ai.d.mts +374 -59
- package/dist/ai.d.ts +374 -59
- package/dist/ai.js +7542 -545
- package/dist/ai.js.map +1 -1
- package/dist/ai.mjs +7243 -569
- package/dist/ai.mjs.map +1 -1
- package/dist/catalog.json +5 -5
- package/dist/{chunk-ZTQBUKLJ.mjs → chunk-4EXRNZRX.mjs} +781 -95
- package/dist/chunk-4EXRNZRX.mjs.map +1 -0
- package/dist/{chunk-PPKHCRRE.mjs → chunk-C76SOFXF.mjs} +3 -3
- package/dist/{chunk-PPKHCRRE.mjs.map → chunk-C76SOFXF.mjs.map} +1 -1
- package/dist/{chunk-IHUFOV7L.mjs → chunk-CH6SFONH.mjs} +15 -3
- package/dist/chunk-CH6SFONH.mjs.map +1 -0
- package/dist/{chunk-XVVLT6B3.mjs → chunk-DBG6DZ2V.mjs} +4 -4
- package/dist/{chunk-XVVLT6B3.mjs.map → chunk-DBG6DZ2V.mjs.map} +1 -1
- package/dist/{chunk-BU5KLO3P.mjs → chunk-DCFZTKFE.mjs} +14 -6
- package/dist/chunk-DCFZTKFE.mjs.map +1 -0
- package/dist/{chunk-SF3U7ZF4.mjs → chunk-DJM5EWTT.mjs} +231 -16
- package/dist/chunk-DJM5EWTT.mjs.map +1 -0
- package/dist/{chunk-V3YJ6JFL.mjs → chunk-F42MZ745.mjs} +3 -3
- package/dist/{chunk-V3YJ6JFL.mjs.map → chunk-F42MZ745.mjs.map} +1 -1
- package/dist/{chunk-5JM35CXV.mjs → chunk-FI2MGX5H.mjs} +4 -4
- package/dist/{chunk-5JM35CXV.mjs.map → chunk-FI2MGX5H.mjs.map} +1 -1
- package/dist/{chunk-SZDAS7LK.mjs → chunk-IE2GGHNF.mjs} +131 -81
- package/dist/chunk-IE2GGHNF.mjs.map +1 -0
- package/dist/{chunk-BNBOIDO5.mjs → chunk-K7VJU7LQ.mjs} +3 -3
- package/dist/{chunk-BNBOIDO5.mjs.map → chunk-K7VJU7LQ.mjs.map} +1 -1
- package/dist/{chunk-H22OZYTW.mjs → chunk-KLGPAMJS.mjs} +55 -43
- package/dist/chunk-KLGPAMJS.mjs.map +1 -0
- package/dist/{chunk-CXHNVYMD.mjs → chunk-KWDBVLST.mjs} +5 -5
- package/dist/{chunk-CXHNVYMD.mjs.map → chunk-KWDBVLST.mjs.map} +1 -1
- package/dist/{chunk-73Q7ADVL.mjs → chunk-MNHOFOM5.mjs} +2 -2
- package/dist/{chunk-73Q7ADVL.mjs.map → chunk-MNHOFOM5.mjs.map} +1 -1
- package/dist/{chunk-QRUAEXLR.mjs → chunk-MOAXHVKI.mjs} +5 -5
- package/dist/{chunk-QRUAEXLR.mjs.map → chunk-MOAXHVKI.mjs.map} +1 -1
- package/dist/{chunk-QGNU34T7.mjs → chunk-QLQ4MJNO.mjs} +10 -4
- package/dist/chunk-QLQ4MJNO.mjs.map +1 -0
- package/dist/chunk-SUXIMLYN.mjs +238 -0
- package/dist/chunk-SUXIMLYN.mjs.map +1 -0
- package/dist/{chunk-OQIQNKPQ.mjs → chunk-XSTGTEGP.mjs} +5 -5
- package/dist/{chunk-OQIQNKPQ.mjs.map → chunk-XSTGTEGP.mjs.map} +1 -1
- package/dist/geometry-2d.d.mts +1 -1
- package/dist/geometry-2d.d.ts +1 -1
- package/dist/geometry-2d.js +1329 -288
- package/dist/geometry-2d.js.map +1 -1
- package/dist/geometry-2d.mjs +6 -6
- package/dist/geometry-3d.d.mts +1 -1
- package/dist/geometry-3d.d.ts +1 -1
- package/dist/geometry-3d.js +4136 -462
- package/dist/geometry-3d.js.map +1 -1
- package/dist/geometry-3d.mjs +5 -5
- package/dist/graph-2d.d.mts +1 -1
- package/dist/graph-2d.d.ts +1 -1
- package/dist/graph-2d.js +1010 -208
- package/dist/graph-2d.js.map +1 -1
- package/dist/graph-2d.mjs +8 -8
- package/dist/{handleExtractProblem-BrDY9ifM.d.ts → handleExtractProblem-mtJyhEw3.d.mts} +2 -0
- package/dist/{handleExtractProblem-BrDY9ifM.d.mts → handleExtractProblem-mtJyhEw3.d.ts} +2 -0
- package/dist/{host-ZQCDAT6O.mjs → host-H2IGOKJU.mjs} +3 -3
- package/dist/{host-ZQCDAT6O.mjs.map → host-H2IGOKJU.mjs.map} +1 -1
- package/dist/{host-3UFGFMJ2.mjs → host-JNLJ5BVR.mjs} +92 -45
- package/dist/host-JNLJ5BVR.mjs.map +1 -0
- package/dist/{host-HOSJHQ5H.mjs → host-VRWLQSMH.mjs} +14 -13
- package/dist/host-VRWLQSMH.mjs.map +1 -0
- package/dist/host-W4U6YCSX.mjs +4973 -0
- package/dist/host-W4U6YCSX.mjs.map +1 -0
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +4577 -600
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +35 -29
- package/dist/index.mjs.map +1 -1
- package/dist/latex.d.mts +1 -1
- package/dist/latex.d.ts +1 -1
- package/dist/latex.js +8 -2
- package/dist/latex.js.map +1 -1
- package/dist/latex.mjs +1 -1
- package/dist/render-NMS7OAV6.mjs +10 -0
- package/dist/{render-ZX2O2IK7.mjs.map → render-NMS7OAV6.mjs.map} +1 -1
- package/dist/serialize-7UZWQQ4I.mjs +9 -0
- package/dist/{serialize-N4G6RFBB.mjs.map → serialize-7UZWQQ4I.mjs.map} +1 -1
- package/dist/{types-C3FjpoUi.d.mts → types-tePd94vW.d.mts} +8 -0
- package/dist/{types-C3FjpoUi.d.ts → types-tePd94vW.d.ts} +8 -0
- package/package.json +4 -1
- package/dist/chunk-BU5KLO3P.mjs.map +0 -1
- package/dist/chunk-H22OZYTW.mjs.map +0 -1
- package/dist/chunk-IHUFOV7L.mjs.map +0 -1
- package/dist/chunk-QGNU34T7.mjs.map +0 -1
- package/dist/chunk-QK6OVDLC.mjs +0 -103
- package/dist/chunk-QK6OVDLC.mjs.map +0 -1
- package/dist/chunk-SF3U7ZF4.mjs.map +0 -1
- package/dist/chunk-SZDAS7LK.mjs.map +0 -1
- package/dist/chunk-ZTQBUKLJ.mjs.map +0 -1
- package/dist/host-2ISGVO7O.mjs +0 -2377
- package/dist/host-2ISGVO7O.mjs.map +0 -1
- package/dist/host-3UFGFMJ2.mjs.map +0 -1
- package/dist/host-HOSJHQ5H.mjs.map +0 -1
- package/dist/render-ZX2O2IK7.mjs +0 -10
- package/dist/serialize-N4G6RFBB.mjs +0 -9
package/dist/geometry-2d.js
CHANGED
|
@@ -73,16 +73,16 @@ var init_types = __esm({
|
|
|
73
73
|
});
|
|
74
74
|
|
|
75
75
|
// src/core/scene/registry.ts
|
|
76
|
-
function registerKind(
|
|
77
|
-
if (registry.has(
|
|
78
|
-
console.warn(`[scene] kind "${
|
|
76
|
+
function registerKind(def24) {
|
|
77
|
+
if (registry.has(def24.type)) {
|
|
78
|
+
console.warn(`[scene] kind "${def24.type}" \u0111\xE3 \u0111\u01B0\u1EE3c \u0111\u0103ng k\xFD \u2014 ghi \u0111\xE8 \u0111\u1ECBnh ngh\u0129a c\u0169`);
|
|
79
79
|
}
|
|
80
|
-
registry.set(
|
|
80
|
+
registry.set(def24.type, def24);
|
|
81
81
|
}
|
|
82
82
|
function getKind(type) {
|
|
83
|
-
const
|
|
84
|
-
if (!
|
|
85
|
-
return
|
|
83
|
+
const def24 = registry.get(type);
|
|
84
|
+
if (!def24) throw new Error(`[scene] unknown kind: ${type}`);
|
|
85
|
+
return def24;
|
|
86
86
|
}
|
|
87
87
|
var registry;
|
|
88
88
|
var init_registry = __esm({
|
|
@@ -168,8 +168,8 @@ function reduce(draft, action) {
|
|
|
168
168
|
return;
|
|
169
169
|
}
|
|
170
170
|
case "TRANSACTION": {
|
|
171
|
-
for (const
|
|
172
|
-
reduce(draft,
|
|
171
|
+
for (const sub2 of action.payload.actions) {
|
|
172
|
+
reduce(draft, sub2);
|
|
173
173
|
}
|
|
174
174
|
return;
|
|
175
175
|
}
|
|
@@ -382,8 +382,36 @@ function constraintRefs(c) {
|
|
|
382
382
|
return [c.polygonId];
|
|
383
383
|
case "onSphere":
|
|
384
384
|
return [c.sphereId];
|
|
385
|
-
|
|
385
|
+
case "midpoint":
|
|
386
|
+
return [c.p1, c.p2];
|
|
387
|
+
case "centroid":
|
|
388
|
+
return [...c.vertices];
|
|
389
|
+
case "intersectionLines":
|
|
390
|
+
return [c.a1, c.b1, c.a2, c.b2];
|
|
391
|
+
case "intersectionLinePlane":
|
|
392
|
+
return [c.a, c.b, c.plane];
|
|
393
|
+
case "perpFootLine":
|
|
394
|
+
return [c.from, c.a, c.b];
|
|
395
|
+
case "perpFootPlane":
|
|
396
|
+
return [c.from, c.plane];
|
|
397
|
+
case "circumsphereCenter":
|
|
398
|
+
return [...c.vertices];
|
|
399
|
+
case "pyramidInsphereCenter":
|
|
400
|
+
return [c.apex, ...c.vertices];
|
|
401
|
+
case "faceCircumcenter":
|
|
402
|
+
return [...c.vertices];
|
|
403
|
+
case "pointAboveFace":
|
|
404
|
+
return [c.base, c.apex, ...c.vertices];
|
|
405
|
+
// Kind KHÔNG có ref scene-object (toạ độ literal): liệt kê TƯỜNG MINH để
|
|
406
|
+
// exhaustive never-guard bên dưới buộc khai báo khi thêm constraint kind mới
|
|
407
|
+
// (mô phỏng constraintRefs2D — quên case = cascade-delete/deps sai âm thầm).
|
|
408
|
+
case "free":
|
|
409
|
+
case "onGround":
|
|
410
|
+
case "onAxis":
|
|
411
|
+
return [];
|
|
412
|
+
default: {
|
|
386
413
|
return [];
|
|
414
|
+
}
|
|
387
415
|
}
|
|
388
416
|
}
|
|
389
417
|
var init_d_constraint = __esm({
|
|
@@ -391,12 +419,359 @@ var init_d_constraint = __esm({
|
|
|
391
419
|
}
|
|
392
420
|
});
|
|
393
421
|
|
|
422
|
+
// src/core/scene/kinds/constraint3d-math.ts
|
|
423
|
+
function sub(a, b) {
|
|
424
|
+
return [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
|
|
425
|
+
}
|
|
426
|
+
function add(a, b) {
|
|
427
|
+
return [a[0] + b[0], a[1] + b[1], a[2] + b[2]];
|
|
428
|
+
}
|
|
429
|
+
function scale(a, k) {
|
|
430
|
+
return [a[0] * k, a[1] * k, a[2] * k];
|
|
431
|
+
}
|
|
432
|
+
function dot(a, b) {
|
|
433
|
+
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
|
434
|
+
}
|
|
435
|
+
function cross(a, b) {
|
|
436
|
+
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]];
|
|
437
|
+
}
|
|
438
|
+
function norm(a) {
|
|
439
|
+
return Math.sqrt(dot(a, a));
|
|
440
|
+
}
|
|
441
|
+
function normalize(a) {
|
|
442
|
+
const n = norm(a);
|
|
443
|
+
return n === 0 ? a : scale(a, 1 / n);
|
|
444
|
+
}
|
|
445
|
+
function getPointWorld(id, state) {
|
|
446
|
+
const obj = state.objects[id];
|
|
447
|
+
if (!obj || obj.kind !== "point3d") {
|
|
448
|
+
throw new Error(`constraintMath: point ${id} not found`);
|
|
449
|
+
}
|
|
450
|
+
const attrs = obj.attrs;
|
|
451
|
+
return constraintToWorld(attrs.constraint, state);
|
|
452
|
+
}
|
|
453
|
+
function getPlaneBasis(planeObj, state) {
|
|
454
|
+
const a = planeObj.attrs;
|
|
455
|
+
let p1, p2, p3;
|
|
456
|
+
if (a.construction) {
|
|
457
|
+
const r = planeConstructionWorld(a.construction, state);
|
|
458
|
+
p1 = r.p1;
|
|
459
|
+
p2 = r.p2;
|
|
460
|
+
p3 = r.p3;
|
|
461
|
+
} else {
|
|
462
|
+
p1 = getPointWorld(a.p1, state);
|
|
463
|
+
p2 = getPointWorld(a.p2, state);
|
|
464
|
+
p3 = getPointWorld(a.p3, state);
|
|
465
|
+
}
|
|
466
|
+
const basis1 = sub(p2, p1);
|
|
467
|
+
const tmp = sub(p3, p1);
|
|
468
|
+
const normal = normalize(cross(basis1, tmp));
|
|
469
|
+
const basis2 = cross(normal, basis1);
|
|
470
|
+
return { origin: p1, basis1, basis2, normal };
|
|
471
|
+
}
|
|
472
|
+
function orthoBasisFromNormal(n) {
|
|
473
|
+
const seed = Math.abs(n[0]) < 0.9 ? [1, 0, 0] : [0, 1, 0];
|
|
474
|
+
const basis1 = normalize(cross(n, seed));
|
|
475
|
+
const basis2 = cross(n, basis1);
|
|
476
|
+
return { basis1, basis2 };
|
|
477
|
+
}
|
|
478
|
+
function planeConstructionWorld(c, state) {
|
|
479
|
+
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?");
|
|
480
|
+
_recDepth++;
|
|
481
|
+
try {
|
|
482
|
+
return planeConstructionWorldInner(c, state);
|
|
483
|
+
} finally {
|
|
484
|
+
_recDepth--;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
function planeConstructionWorldInner(c, state) {
|
|
488
|
+
switch (c.kind) {
|
|
489
|
+
case "planeParallelThrough": {
|
|
490
|
+
const P = getPointWorld(c.point, state);
|
|
491
|
+
const ref = state.objects[c.refPlane];
|
|
492
|
+
if (!ref || ref.kind !== "plane3d") throw new Error("planeParallelThrough: m\u1EB7t tham chi\u1EBFu thi\u1EBFu");
|
|
493
|
+
const { basis1, basis2 } = getPlaneBasis(ref, state);
|
|
494
|
+
if (norm(basis1) < 1e-12 || norm(basis2) < 1e-12) {
|
|
495
|
+
return { p1: P, p2: add(P, [1, 0, 0]), p3: add(P, [0, 1, 0]) };
|
|
496
|
+
}
|
|
497
|
+
return { p1: P, p2: add(P, basis1), p3: add(P, basis2) };
|
|
498
|
+
}
|
|
499
|
+
case "planePerpToLine": {
|
|
500
|
+
const P = getPointWorld(c.point, state);
|
|
501
|
+
const dir = sub(getPointWorld(c.lineB, state), getPointWorld(c.lineA, state));
|
|
502
|
+
const dn = norm(dir);
|
|
503
|
+
if (dn < 1e-12) return { p1: P, p2: add(P, [1, 0, 0]), p3: add(P, [0, 1, 0]) };
|
|
504
|
+
const { basis1, basis2 } = orthoBasisFromNormal(scale(dir, 1 / dn));
|
|
505
|
+
return { p1: P, p2: add(P, basis1), p3: add(P, basis2) };
|
|
506
|
+
}
|
|
507
|
+
default: {
|
|
508
|
+
throw new Error("planeConstructionWorld: kind kh\xF4ng h\u1ED7 tr\u1EE3");
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
function lineDefiningPointIds(line) {
|
|
513
|
+
if (line.kind === "line3d" || line.kind === "segment3d") {
|
|
514
|
+
const a = line.attrs;
|
|
515
|
+
return a.p1 && a.p2 ? [a.p1, a.p2] : null;
|
|
516
|
+
}
|
|
517
|
+
if (line.kind === "ray3d") {
|
|
518
|
+
const a = line.attrs;
|
|
519
|
+
return [a.origin, a.through];
|
|
520
|
+
}
|
|
521
|
+
if (line.kind === "vector3d") {
|
|
522
|
+
const a = line.attrs;
|
|
523
|
+
return [a.from, a.to];
|
|
524
|
+
}
|
|
525
|
+
return null;
|
|
526
|
+
}
|
|
527
|
+
function lineEndpointsWorld(lineId, state, ctx) {
|
|
528
|
+
const line = state.objects[lineId];
|
|
529
|
+
if (!line) throw new Error(`${ctx}: \u0111\u01B0\u1EDDng ${lineId} kh\xF4ng t\u1ED3n t\u1EA1i`);
|
|
530
|
+
const ids = lineDefiningPointIds(line);
|
|
531
|
+
if (!ids) throw new Error(`${ctx}: kind ${line.kind} kh\xF4ng ph\u1EA3i \u0111\u01B0\u1EDDng`);
|
|
532
|
+
return { a: getPointWorld(ids[0], state), b: getPointWorld(ids[1], state) };
|
|
533
|
+
}
|
|
534
|
+
function lineLineClosestMidpoint(A, B, C, D) {
|
|
535
|
+
const u = sub(B, A), v = sub(D, C), w0 = sub(A, C);
|
|
536
|
+
const a = dot(u, u), b = dot(u, v), cc = dot(v, v), d = dot(u, w0), e = dot(v, w0);
|
|
537
|
+
const denom = a * cc - b * b;
|
|
538
|
+
if (Math.abs(denom) < 1e-12) return scale(add(A, C), 0.5);
|
|
539
|
+
const sc = (b * e - cc * d) / denom;
|
|
540
|
+
const tc = (a * e - b * d) / denom;
|
|
541
|
+
return scale(add(add(A, scale(u, sc)), add(C, scale(v, tc))), 0.5);
|
|
542
|
+
}
|
|
543
|
+
function planeOriginNormal(planeId, state, ctx) {
|
|
544
|
+
const plane = state.objects[planeId];
|
|
545
|
+
if (!plane || plane.kind !== "plane3d") throw new Error(`${ctx}: m\u1EB7t ph\u1EB3ng ${planeId} thi\u1EBFu`);
|
|
546
|
+
const { origin, normal } = getPlaneBasis(plane, state);
|
|
547
|
+
return { origin, normal };
|
|
548
|
+
}
|
|
549
|
+
function lineConstructionWorld(c, state) {
|
|
550
|
+
switch (c.kind) {
|
|
551
|
+
case "planePlaneIntersection": {
|
|
552
|
+
const P1 = planeOriginNormal(c.plane1, state, "planePlaneIntersection");
|
|
553
|
+
const P2 = planeOriginNormal(c.plane2, state, "planePlaneIntersection");
|
|
554
|
+
const d = cross(P1.normal, P2.normal);
|
|
555
|
+
const dd = dot(d, d);
|
|
556
|
+
if (dd < 1e-12) {
|
|
557
|
+
return { a: P1.origin, b: add(P1.origin, P2.normal) };
|
|
558
|
+
}
|
|
559
|
+
const c1 = dot(P1.normal, P1.origin);
|
|
560
|
+
const c2 = dot(P2.normal, P2.origin);
|
|
561
|
+
const p0 = scale(add(scale(cross(P2.normal, d), c1), scale(cross(d, P1.normal), c2)), 1 / dd);
|
|
562
|
+
return { a: p0, b: add(p0, d) };
|
|
563
|
+
}
|
|
564
|
+
case "lineParallelThrough": {
|
|
565
|
+
const P = getPointWorld(c.point, state);
|
|
566
|
+
const dir = sub(getPointWorld(c.dirB, state), getPointWorld(c.dirA, state));
|
|
567
|
+
if (norm(dir) < 1e-12) return { a: P, b: add(P, [1, 0, 0]) };
|
|
568
|
+
return { a: P, b: add(P, dir) };
|
|
569
|
+
}
|
|
570
|
+
case "linePerpToPlane": {
|
|
571
|
+
const P = getPointWorld(c.point, state);
|
|
572
|
+
const { normal } = planeOriginNormal(c.plane, state, "linePerpToPlane");
|
|
573
|
+
return { a: P, b: add(P, normal) };
|
|
574
|
+
}
|
|
575
|
+
default: {
|
|
576
|
+
throw new Error("lineConstructionWorld: kind kh\xF4ng h\u1ED7 tr\u1EE3");
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
function solve3(M, b) {
|
|
581
|
+
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]);
|
|
582
|
+
const det = det3(M);
|
|
583
|
+
if (Math.abs(det) < 1e-9) return null;
|
|
584
|
+
const colDet = (j) => {
|
|
585
|
+
const A = M.map((r) => r.slice());
|
|
586
|
+
for (let i = 0; i < 3; i++) A[i][j] = b[i];
|
|
587
|
+
return det3(A);
|
|
588
|
+
};
|
|
589
|
+
return [colDet(0) / det, colDet(1) / det, colDet(2) / det];
|
|
590
|
+
}
|
|
591
|
+
function constraintToWorld(c, state) {
|
|
592
|
+
if (_recDepth >= MAX_REC_DEPTH) throw new Error("constraintToWorld: \u0111\u1EC7 quy qu\xE1 s\xE2u \u2014 chu tr\xECnh tham chi\u1EBFu?");
|
|
593
|
+
_recDepth++;
|
|
594
|
+
try {
|
|
595
|
+
return constraintToWorldInner(c, state);
|
|
596
|
+
} finally {
|
|
597
|
+
_recDepth--;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
function constraintToWorldInner(c, state) {
|
|
601
|
+
switch (c.kind) {
|
|
602
|
+
case "free":
|
|
603
|
+
return [c.x, c.y, c.z];
|
|
604
|
+
case "onGround":
|
|
605
|
+
return [c.x, c.y, 0];
|
|
606
|
+
case "onAxis": {
|
|
607
|
+
if (c.axis === "x") return [c.t, 0, 0];
|
|
608
|
+
if (c.axis === "y") return [0, c.t, 0];
|
|
609
|
+
return [0, 0, c.t];
|
|
610
|
+
}
|
|
611
|
+
case "onPlane": {
|
|
612
|
+
const plane = state.objects[c.planeId];
|
|
613
|
+
if (!plane || plane.kind !== "plane3d") throw new Error("onPlane: plane missing");
|
|
614
|
+
const { origin, basis1, basis2 } = getPlaneBasis(plane, state);
|
|
615
|
+
return add(add(origin, scale(basis1, c.u)), scale(basis2, c.v));
|
|
616
|
+
}
|
|
617
|
+
case "onLine": {
|
|
618
|
+
const { a, b } = lineEndpointsWorld(c.lineId, state, "onLine");
|
|
619
|
+
return add(a, scale(sub(b, a), c.t));
|
|
620
|
+
}
|
|
621
|
+
case "onPolygon": {
|
|
622
|
+
const pg = state.objects[c.polygonId];
|
|
623
|
+
if (!pg || pg.kind !== "polygon3d") throw new Error("onPolygon: parent missing");
|
|
624
|
+
const v = pg.attrs.vertices;
|
|
625
|
+
if (v.length < 3) throw new Error("onPolygon: < 3 vertices");
|
|
626
|
+
const p1 = getPointWorld(v[0], state);
|
|
627
|
+
const p2 = getPointWorld(v[1], state);
|
|
628
|
+
const p3 = getPointWorld(v[2], state);
|
|
629
|
+
const basis1 = sub(p2, p1);
|
|
630
|
+
const tmp = sub(p3, p1);
|
|
631
|
+
const normal = normalize(cross(basis1, tmp));
|
|
632
|
+
const basis2 = cross(normal, basis1);
|
|
633
|
+
return add(add(p1, scale(basis1, c.u)), scale(basis2, c.v));
|
|
634
|
+
}
|
|
635
|
+
case "onSphere": {
|
|
636
|
+
const sph = state.objects[c.sphereId];
|
|
637
|
+
if (!sph || sph.kind !== "sphere3d") throw new Error("onSphere: parent missing");
|
|
638
|
+
const a = sph.attrs;
|
|
639
|
+
const center = getPointWorld(a.center, state);
|
|
640
|
+
const surface = getPointWorld(a.surfacePoint, state);
|
|
641
|
+
const radius = norm(sub(surface, center));
|
|
642
|
+
const x = center[0] + radius * Math.sin(c.phi) * Math.cos(c.theta);
|
|
643
|
+
const y = center[1] + radius * Math.sin(c.phi) * Math.sin(c.theta);
|
|
644
|
+
const z = center[2] + radius * Math.cos(c.phi);
|
|
645
|
+
return [x, y, z];
|
|
646
|
+
}
|
|
647
|
+
// ───── Điểm phái sinh (v1) ─────
|
|
648
|
+
case "midpoint": {
|
|
649
|
+
const p1 = getPointWorld(c.p1, state);
|
|
650
|
+
const p2 = getPointWorld(c.p2, state);
|
|
651
|
+
return scale(add(p1, p2), 0.5);
|
|
652
|
+
}
|
|
653
|
+
case "centroid": {
|
|
654
|
+
const n = c.vertices.length;
|
|
655
|
+
if (n === 0) return [0, 0, 0];
|
|
656
|
+
let acc = [0, 0, 0];
|
|
657
|
+
for (const id of c.vertices) acc = add(acc, getPointWorld(id, state));
|
|
658
|
+
return scale(acc, 1 / n);
|
|
659
|
+
}
|
|
660
|
+
case "circumsphereCenter": {
|
|
661
|
+
const P = c.vertices.map((id) => getPointWorld(id, state));
|
|
662
|
+
if (P.length < 4) return P.length ? P[0] : [0, 0, 0];
|
|
663
|
+
const p0 = P[0];
|
|
664
|
+
const M = [[0, 0, 0], [0, 0, 0], [0, 0, 0]];
|
|
665
|
+
const rhs = [0, 0, 0];
|
|
666
|
+
for (let i = 1; i < P.length; i++) {
|
|
667
|
+
const r = [2 * (P[i][0] - p0[0]), 2 * (P[i][1] - p0[1]), 2 * (P[i][2] - p0[2])];
|
|
668
|
+
const bi = dot(P[i], P[i]) - dot(p0, p0);
|
|
669
|
+
for (let a = 0; a < 3; a++) {
|
|
670
|
+
for (let bcol = 0; bcol < 3; bcol++) M[a][bcol] += r[a] * r[bcol];
|
|
671
|
+
rhs[a] += r[a] * bi;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
const sol = solve3(M, rhs);
|
|
675
|
+
if (sol) return sol;
|
|
676
|
+
let acc = [0, 0, 0];
|
|
677
|
+
for (const p of P) acc = add(acc, p);
|
|
678
|
+
return scale(acc, 1 / P.length);
|
|
679
|
+
}
|
|
680
|
+
case "pyramidInsphereCenter": {
|
|
681
|
+
const S = getPointWorld(c.apex, state);
|
|
682
|
+
const base = c.vertices.map((id) => getPointWorld(id, state));
|
|
683
|
+
const n = base.length;
|
|
684
|
+
if (n < 3) return S;
|
|
685
|
+
let G = [0, 0, 0];
|
|
686
|
+
for (const p of base) G = add(G, p);
|
|
687
|
+
G = scale(G, 1 / n);
|
|
688
|
+
let nb = normalize(cross(sub(base[1], base[0]), sub(base[2], base[0])));
|
|
689
|
+
if (dot(nb, sub(S, G)) < 0) nb = scale(nb, -1);
|
|
690
|
+
let m = normalize(cross(sub(base[0], S), sub(base[1], S)));
|
|
691
|
+
if (dot(m, sub(G, base[0])) < 0) m = scale(m, -1);
|
|
692
|
+
const denom = 1 - dot(m, nb);
|
|
693
|
+
const s = Math.abs(denom) < 1e-9 ? NaN : dot(m, sub(G, base[0])) / denom;
|
|
694
|
+
if (!Number.isFinite(s) || s <= 0) {
|
|
695
|
+
return scale(add(S, scale(G, n)), 1 / (n + 1));
|
|
696
|
+
}
|
|
697
|
+
return add(G, scale(nb, s));
|
|
698
|
+
}
|
|
699
|
+
case "faceCircumcenter": {
|
|
700
|
+
const P = c.vertices.map((id) => getPointWorld(id, state));
|
|
701
|
+
if (P.length < 3) return P.length ? P[0] : [0, 0, 0];
|
|
702
|
+
const [p0, p1, p2] = P;
|
|
703
|
+
const e1 = sub(p1, p0), e2 = sub(p2, p0);
|
|
704
|
+
const nrm = cross(e1, e2);
|
|
705
|
+
const M = [
|
|
706
|
+
[2 * e1[0], 2 * e1[1], 2 * e1[2]],
|
|
707
|
+
[2 * e2[0], 2 * e2[1], 2 * e2[2]],
|
|
708
|
+
[nrm[0], nrm[1], nrm[2]]
|
|
709
|
+
];
|
|
710
|
+
const rhs = [dot(p1, p1) - dot(p0, p0), dot(p2, p2) - dot(p0, p0), dot(nrm, p0)];
|
|
711
|
+
const sol = solve3(M, rhs);
|
|
712
|
+
if (sol) return sol;
|
|
713
|
+
return scale(add(add(p0, p1), p2), 1 / 3);
|
|
714
|
+
}
|
|
715
|
+
case "pointAboveFace": {
|
|
716
|
+
const G = getPointWorld(c.base, state);
|
|
717
|
+
const P = c.vertices.map((id) => getPointWorld(id, state));
|
|
718
|
+
if (P.length < 3) return G;
|
|
719
|
+
let n = normalize(cross(sub(P[1], P[0]), sub(P[2], P[0])));
|
|
720
|
+
const S = getPointWorld(c.apex, state);
|
|
721
|
+
if (dot(n, sub(S, P[0])) < 0) n = scale(n, -1);
|
|
722
|
+
const h = dot(sub(S, P[0]), n);
|
|
723
|
+
if (!Number.isFinite(h) || h <= 1e-9) return G;
|
|
724
|
+
return add(G, scale(n, h));
|
|
725
|
+
}
|
|
726
|
+
case "intersectionLines": {
|
|
727
|
+
const A = getPointWorld(c.a1, state), B = getPointWorld(c.b1, state);
|
|
728
|
+
const C = getPointWorld(c.a2, state), D = getPointWorld(c.b2, state);
|
|
729
|
+
return lineLineClosestMidpoint(A, B, C, D);
|
|
730
|
+
}
|
|
731
|
+
case "intersectionLinePlane": {
|
|
732
|
+
const A = getPointWorld(c.a, state), B = getPointWorld(c.b, state);
|
|
733
|
+
const plane = state.objects[c.plane];
|
|
734
|
+
if (!plane || plane.kind !== "plane3d") throw new Error("intersectionLinePlane: m\u1EB7t ph\u1EB3ng thi\u1EBFu");
|
|
735
|
+
const { origin, normal } = getPlaneBasis(plane, state);
|
|
736
|
+
const dir = sub(B, A);
|
|
737
|
+
const dn = dot(dir, normal);
|
|
738
|
+
if (Math.abs(dn) < 1e-12) return A;
|
|
739
|
+
const t = dot(sub(origin, A), normal) / dn;
|
|
740
|
+
return add(A, scale(dir, t));
|
|
741
|
+
}
|
|
742
|
+
case "perpFootLine": {
|
|
743
|
+
const P = getPointWorld(c.from, state);
|
|
744
|
+
const A = getPointWorld(c.a, state), B = getPointWorld(c.b, state);
|
|
745
|
+
const u = sub(B, A);
|
|
746
|
+
const uu = dot(u, u);
|
|
747
|
+
const t = uu === 0 ? 0 : dot(sub(P, A), u) / uu;
|
|
748
|
+
return add(A, scale(u, t));
|
|
749
|
+
}
|
|
750
|
+
case "perpFootPlane": {
|
|
751
|
+
const P = getPointWorld(c.from, state);
|
|
752
|
+
const plane = state.objects[c.plane];
|
|
753
|
+
if (!plane || plane.kind !== "plane3d") throw new Error("perpFootPlane: m\u1EB7t ph\u1EB3ng thi\u1EBFu");
|
|
754
|
+
const { origin, normal } = getPlaneBasis(plane, state);
|
|
755
|
+
const dist2 = dot(sub(P, origin), normal);
|
|
756
|
+
return sub(P, scale(normal, dist2));
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
var MAX_REC_DEPTH, _recDepth;
|
|
761
|
+
var init_constraint3d_math = __esm({
|
|
762
|
+
"src/core/scene/kinds/constraint3d-math.ts"() {
|
|
763
|
+
MAX_REC_DEPTH = 512;
|
|
764
|
+
_recDepth = 0;
|
|
765
|
+
}
|
|
766
|
+
});
|
|
767
|
+
|
|
394
768
|
// src/core/scene/kinds/point3d.ts
|
|
395
769
|
var def;
|
|
396
770
|
var init_point3d = __esm({
|
|
397
771
|
"src/core/scene/kinds/point3d.ts"() {
|
|
398
772
|
init_registry();
|
|
399
773
|
init_d_constraint();
|
|
774
|
+
init_constraint3d_math();
|
|
400
775
|
def = {
|
|
401
776
|
type: "point3d",
|
|
402
777
|
schemaVersion: 1,
|
|
@@ -434,6 +809,15 @@ var init_point3d = __esm({
|
|
|
434
809
|
if (c.kind === "onLine") return `${obj.label} tr\xEAn \u0111\u01B0\u1EDDng ${c.lineId}`;
|
|
435
810
|
if (c.kind === "onPolygon") return `${obj.label} tr\xEAn \u0111a gi\xE1c ${c.polygonId}`;
|
|
436
811
|
if (c.kind === "onSphere") return `${obj.label} tr\xEAn m\u1EB7t c\u1EA7u ${c.sphereId}`;
|
|
812
|
+
if (c.kind === "midpoint") return `${obj.label} = trung \u0111i\u1EC3m ${c.p1}${c.p2}`;
|
|
813
|
+
if (c.kind === "centroid") return `${obj.label} = tr\u1ECDng t\xE2m ${c.vertices.join("")}`;
|
|
814
|
+
if (c.kind === "intersectionLines") return `${obj.label} = giao 2 \u0111\u01B0\u1EDDng (${c.a1}${c.b1}, ${c.a2}${c.b2})`;
|
|
815
|
+
if (c.kind === "intersectionLinePlane") return `${obj.label} = giao ${c.a}${c.b} \u2229 ${c.plane}`;
|
|
816
|
+
if (c.kind === "perpFootLine") return `${obj.label} = ch\xE2n \u22A5 t\u1EEB ${c.from} xu\u1ED1ng ${c.a}${c.b}`;
|
|
817
|
+
if (c.kind === "perpFootPlane") return `${obj.label} = ch\xE2n \u22A5 t\u1EEB ${c.from} xu\u1ED1ng ${c.plane}`;
|
|
818
|
+
if (c.kind === "pyramidInsphereCenter") return `${obj.label} = t\xE2m c\u1EA7u n\u1ED9i ti\u1EBFp ch\xF3p ${c.apex}.${c.vertices.join("")}`;
|
|
819
|
+
if (c.kind === "faceCircumcenter") return `${obj.label} = t\xE2m ngo\u1EA1i ti\u1EBFp ${c.vertices.join("")}`;
|
|
820
|
+
if (c.kind === "pointAboveFace") return `${obj.label} = \u0111\u1EC9nh tr\u1EE5c tr\u1EE5 \u22A5 m\u1EB7t ${c.vertices.join("")}`;
|
|
437
821
|
return obj.label;
|
|
438
822
|
},
|
|
439
823
|
render: (obj, ctx) => {
|
|
@@ -483,6 +867,15 @@ var init_point3d = __esm({
|
|
|
483
867
|
() => sph.F(c.theta, c.phi)[2]
|
|
484
868
|
], opts);
|
|
485
869
|
}
|
|
870
|
+
const getState = ctx.getState;
|
|
871
|
+
if (getState) {
|
|
872
|
+
const cw = () => constraintToWorld(c, getState());
|
|
873
|
+
return view.create("point3d", [
|
|
874
|
+
() => cw()[0],
|
|
875
|
+
() => cw()[1],
|
|
876
|
+
() => cw()[2]
|
|
877
|
+
], { ...opts, needsRegularUpdate: true });
|
|
878
|
+
}
|
|
486
879
|
return view.create("point3d", [0, 0, 0], opts);
|
|
487
880
|
}
|
|
488
881
|
};
|
|
@@ -544,32 +937,77 @@ var init_segment3d = __esm({
|
|
|
544
937
|
});
|
|
545
938
|
|
|
546
939
|
// src/core/scene/kinds/line3d.ts
|
|
940
|
+
function line3dConstructionRefs(c) {
|
|
941
|
+
switch (c.kind) {
|
|
942
|
+
case "planePlaneIntersection":
|
|
943
|
+
return [c.plane1, c.plane2];
|
|
944
|
+
case "lineParallelThrough":
|
|
945
|
+
return [c.point, c.dirA, c.dirB];
|
|
946
|
+
case "linePerpToPlane":
|
|
947
|
+
return [c.point, c.plane];
|
|
948
|
+
default: {
|
|
949
|
+
return [];
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
var def3;
|
|
547
954
|
var init_line3d = __esm({
|
|
548
955
|
"src/core/scene/kinds/line3d.ts"() {
|
|
549
956
|
init_registry();
|
|
550
957
|
init_labelOf();
|
|
551
|
-
|
|
958
|
+
init_constraint3d_math();
|
|
959
|
+
def3 = {
|
|
552
960
|
type: "line3d",
|
|
553
961
|
schemaVersion: 1,
|
|
554
962
|
migrate: {},
|
|
555
963
|
validate: (a) => {
|
|
556
|
-
if (
|
|
964
|
+
if (a?.construction) return;
|
|
965
|
+
if (!a?.p1 || !a?.p2) throw new Error("line3d: p1/p2 required (ho\u1EB7c construction)");
|
|
966
|
+
},
|
|
967
|
+
dependsOn: (a) => a.construction ? line3dConstructionRefs(a.construction) : [a.p1, a.p2],
|
|
968
|
+
describe: (obj, state) => {
|
|
969
|
+
const c = obj.attrs.construction;
|
|
970
|
+
if (!c) return `\u0110\u01B0\u1EDDng ${obj.label} qua ${labelOf(obj.attrs.p1, state)}, ${labelOf(obj.attrs.p2, state)}`;
|
|
971
|
+
switch (c.kind) {
|
|
972
|
+
case "planePlaneIntersection":
|
|
973
|
+
return `Giao tuy\u1EBFn ${obj.label} = ${labelOf(c.plane1, state)} \u2229 ${labelOf(c.plane2, state)}`;
|
|
974
|
+
case "lineParallelThrough":
|
|
975
|
+
return `${obj.label} \u2225 ${labelOf(c.dirA, state)}${labelOf(c.dirB, state)} qua ${labelOf(c.point, state)}`;
|
|
976
|
+
case "linePerpToPlane":
|
|
977
|
+
return `${obj.label} \u22A5 ${labelOf(c.plane, state)} qua ${labelOf(c.point, state)}`;
|
|
978
|
+
default:
|
|
979
|
+
return obj.label;
|
|
980
|
+
}
|
|
557
981
|
},
|
|
558
|
-
dependsOn: (a) => [a.p1, a.p2],
|
|
559
|
-
describe: (obj, state) => `\u0110\u01B0\u1EDDng ${obj.label} qua ${labelOf(obj.attrs.p1, state)}, ${labelOf(obj.attrs.p2, state)}`,
|
|
560
982
|
render: (obj, ctx) => {
|
|
561
983
|
const view = ctx.jxg;
|
|
984
|
+
const baseOpts = {
|
|
985
|
+
strokeColor: obj.attrs.color ?? "#0f172a",
|
|
986
|
+
strokeWidth: 2,
|
|
987
|
+
visible: obj.visible
|
|
988
|
+
};
|
|
989
|
+
const c = obj.attrs.construction;
|
|
990
|
+
if (c) {
|
|
991
|
+
const getState = ctx.getState;
|
|
992
|
+
if (!getState) throw new Error("line3d construction: c\u1EA7n ctx.getState");
|
|
993
|
+
const lc = () => lineConstructionWorld(c, getState());
|
|
994
|
+
return view.create("line3d", [() => lc().a, () => lc().b], {
|
|
995
|
+
...baseOpts,
|
|
996
|
+
straightFirst: true,
|
|
997
|
+
straightLast: true,
|
|
998
|
+
needsRegularUpdate: true
|
|
999
|
+
});
|
|
1000
|
+
}
|
|
562
1001
|
const pA = ctx.resolveRef(obj.attrs.p1);
|
|
563
1002
|
const pB = ctx.resolveRef(obj.attrs.p2);
|
|
564
1003
|
return view.create("line3d", [pA, pB], {
|
|
1004
|
+
...baseOpts,
|
|
565
1005
|
straightFirst: true,
|
|
566
|
-
straightLast: true
|
|
567
|
-
strokeColor: obj.attrs.color ?? "#0f172a",
|
|
568
|
-
strokeWidth: 2,
|
|
569
|
-
visible: obj.visible
|
|
1006
|
+
straightLast: true
|
|
570
1007
|
});
|
|
571
1008
|
}
|
|
572
|
-
}
|
|
1009
|
+
};
|
|
1010
|
+
registerKind(def3);
|
|
573
1011
|
}
|
|
574
1012
|
});
|
|
575
1013
|
|
|
@@ -635,33 +1073,77 @@ var init_vector3d = __esm({
|
|
|
635
1073
|
});
|
|
636
1074
|
|
|
637
1075
|
// src/core/scene/kinds/plane3d.ts
|
|
1076
|
+
function plane3dConstructionRefs(c) {
|
|
1077
|
+
switch (c.kind) {
|
|
1078
|
+
case "planeParallelThrough":
|
|
1079
|
+
return [c.point, c.refPlane];
|
|
1080
|
+
case "planePerpToLine":
|
|
1081
|
+
return [c.point, c.lineA, c.lineB];
|
|
1082
|
+
default: {
|
|
1083
|
+
return [];
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
var def4;
|
|
638
1088
|
var init_plane3d = __esm({
|
|
639
1089
|
"src/core/scene/kinds/plane3d.ts"() {
|
|
640
1090
|
init_registry();
|
|
641
1091
|
init_labelOf();
|
|
642
|
-
|
|
1092
|
+
init_constraint3d_math();
|
|
1093
|
+
def4 = {
|
|
643
1094
|
type: "plane3d",
|
|
644
1095
|
schemaVersion: 1,
|
|
645
1096
|
migrate: {},
|
|
646
1097
|
validate: (a) => {
|
|
647
|
-
if (
|
|
1098
|
+
if (a?.construction) return;
|
|
1099
|
+
if (!a?.p1 || !a?.p2 || !a?.p3) throw new Error("plane3d: c\u1EA7n 3 \u0111i\u1EC3m (ho\u1EB7c construction)");
|
|
1100
|
+
},
|
|
1101
|
+
dependsOn: (a) => a.construction ? plane3dConstructionRefs(a.construction) : [a.p1, a.p2, a.p3],
|
|
1102
|
+
describe: (obj, state) => {
|
|
1103
|
+
const c = obj.attrs.construction;
|
|
1104
|
+
if (!c) {
|
|
1105
|
+
return `M\u1EB7t ${obj.label} qua ${labelOf(obj.attrs.p1, state)}, ${labelOf(obj.attrs.p2, state)}, ${labelOf(obj.attrs.p3, state)}`;
|
|
1106
|
+
}
|
|
1107
|
+
switch (c.kind) {
|
|
1108
|
+
case "planeParallelThrough":
|
|
1109
|
+
return `${obj.label} \u2225 ${labelOf(c.refPlane, state)} qua ${labelOf(c.point, state)}`;
|
|
1110
|
+
case "planePerpToLine":
|
|
1111
|
+
return `${obj.label} \u22A5 ${labelOf(c.lineA, state)}${labelOf(c.lineB, state)} qua ${labelOf(c.point, state)}`;
|
|
1112
|
+
default:
|
|
1113
|
+
return obj.label;
|
|
1114
|
+
}
|
|
648
1115
|
},
|
|
649
|
-
dependsOn: (a) => [a.p1, a.p2, a.p3],
|
|
650
|
-
describe: (obj, state) => `M\u1EB7t ${obj.label} qua ${labelOf(obj.attrs.p1, state)}, ${labelOf(obj.attrs.p2, state)}, ${labelOf(obj.attrs.p3, state)}`,
|
|
651
1116
|
render: (obj, ctx) => {
|
|
652
1117
|
const view = ctx.jxg;
|
|
653
|
-
|
|
654
|
-
ctx.resolveRef(obj.attrs.p1),
|
|
655
|
-
ctx.resolveRef(obj.attrs.p2),
|
|
656
|
-
ctx.resolveRef(obj.attrs.p3)
|
|
657
|
-
], {
|
|
1118
|
+
const opts = {
|
|
658
1119
|
fillOpacity: 0.15,
|
|
659
1120
|
fillColor: obj.attrs.color ?? "#60a5fa",
|
|
660
1121
|
strokeColor: obj.attrs.color ?? "#60a5fa",
|
|
661
1122
|
visible: obj.visible
|
|
662
|
-
}
|
|
1123
|
+
};
|
|
1124
|
+
const c = obj.attrs.construction;
|
|
1125
|
+
if (c) {
|
|
1126
|
+
const getState = ctx.getState;
|
|
1127
|
+
if (!getState) throw new Error("plane3d construction: c\u1EA7n ctx.getState");
|
|
1128
|
+
const pc = () => planeConstructionWorld(c, getState());
|
|
1129
|
+
const diff = (which) => {
|
|
1130
|
+
const r = pc();
|
|
1131
|
+
const q = r[which];
|
|
1132
|
+
return [q[0] - r.p1[0], q[1] - r.p1[1], q[2] - r.p1[2]];
|
|
1133
|
+
};
|
|
1134
|
+
return view.create("plane3d", [() => pc().p1, () => diff("p2"), () => diff("p3")], {
|
|
1135
|
+
...opts,
|
|
1136
|
+
needsRegularUpdate: true
|
|
1137
|
+
});
|
|
1138
|
+
}
|
|
1139
|
+
return view.create("plane3d", [
|
|
1140
|
+
ctx.resolveRef(obj.attrs.p1),
|
|
1141
|
+
ctx.resolveRef(obj.attrs.p2),
|
|
1142
|
+
ctx.resolveRef(obj.attrs.p3)
|
|
1143
|
+
], opts);
|
|
663
1144
|
}
|
|
664
|
-
}
|
|
1145
|
+
};
|
|
1146
|
+
registerKind(def4);
|
|
665
1147
|
}
|
|
666
1148
|
});
|
|
667
1149
|
|
|
@@ -760,11 +1242,37 @@ var init_polyhedron3d = __esm({
|
|
|
760
1242
|
}
|
|
761
1243
|
});
|
|
762
1244
|
|
|
1245
|
+
// src/core/scene/kinds/_ringBasis.ts
|
|
1246
|
+
function perpBasis(axis) {
|
|
1247
|
+
const n = Math.hypot(axis[0], axis[1], axis[2]);
|
|
1248
|
+
if (n < 1e-12) return [[1, 0, 0], [0, 1, 0]];
|
|
1249
|
+
const a = [axis[0] / n, axis[1] / n, axis[2] / n];
|
|
1250
|
+
const seed = Math.abs(a[2]) < 0.9 ? [0, 0, 1] : [1, 0, 0];
|
|
1251
|
+
let u = [
|
|
1252
|
+
seed[1] * a[2] - seed[2] * a[1],
|
|
1253
|
+
seed[2] * a[0] - seed[0] * a[2],
|
|
1254
|
+
seed[0] * a[1] - seed[1] * a[0]
|
|
1255
|
+
];
|
|
1256
|
+
const un = Math.hypot(u[0], u[1], u[2]) || 1;
|
|
1257
|
+
u = [u[0] / un, u[1] / un, u[2] / un];
|
|
1258
|
+
const v = [
|
|
1259
|
+
a[1] * u[2] - a[2] * u[1],
|
|
1260
|
+
a[2] * u[0] - a[0] * u[2],
|
|
1261
|
+
a[0] * u[1] - a[1] * u[0]
|
|
1262
|
+
];
|
|
1263
|
+
return [u, v];
|
|
1264
|
+
}
|
|
1265
|
+
var init_ringBasis = __esm({
|
|
1266
|
+
"src/core/scene/kinds/_ringBasis.ts"() {
|
|
1267
|
+
}
|
|
1268
|
+
});
|
|
1269
|
+
|
|
763
1270
|
// src/core/scene/kinds/cylinder3d.ts
|
|
764
1271
|
var CURVED_SEGMENTS;
|
|
765
1272
|
var init_cylinder3d = __esm({
|
|
766
1273
|
"src/core/scene/kinds/cylinder3d.ts"() {
|
|
767
1274
|
init_registry();
|
|
1275
|
+
init_ringBasis();
|
|
768
1276
|
CURVED_SEGMENTS = 16;
|
|
769
1277
|
registerKind({
|
|
770
1278
|
type: "cylinder3d",
|
|
@@ -783,14 +1291,17 @@ var init_cylinder3d = __esm({
|
|
|
783
1291
|
const r = obj.attrs.radius;
|
|
784
1292
|
const ax = a.X?.() ?? 0, ay = a.Y?.() ?? 0, az = a.Z?.() ?? 0;
|
|
785
1293
|
const bx = b.X?.() ?? 0, by = b.Y?.() ?? 0, bz = b.Z?.() ?? 0;
|
|
1294
|
+
const [u, v] = perpBasis([bx - ax, by - ay, bz - az]);
|
|
786
1295
|
const baseRing = [];
|
|
787
1296
|
const topRing = [];
|
|
788
1297
|
for (let i = 0; i < CURVED_SEGMENTS; i++) {
|
|
789
1298
|
const theta = i / CURVED_SEGMENTS * Math.PI * 2;
|
|
790
|
-
const
|
|
791
|
-
const
|
|
792
|
-
|
|
793
|
-
|
|
1299
|
+
const c = Math.cos(theta), s = Math.sin(theta);
|
|
1300
|
+
const ox = r * (c * u[0] + s * v[0]);
|
|
1301
|
+
const oy = r * (c * u[1] + s * v[1]);
|
|
1302
|
+
const oz = r * (c * u[2] + s * v[2]);
|
|
1303
|
+
baseRing.push([ax + ox, ay + oy, az + oz]);
|
|
1304
|
+
topRing.push([bx + ox, by + oy, bz + oz]);
|
|
794
1305
|
}
|
|
795
1306
|
const vertices = [...baseRing, ...topRing];
|
|
796
1307
|
const faces = [];
|
|
@@ -801,7 +1312,7 @@ var init_cylinder3d = __esm({
|
|
|
801
1312
|
faces.push([i, next, CURVED_SEGMENTS + next, CURVED_SEGMENTS + i]);
|
|
802
1313
|
}
|
|
803
1314
|
const vertJxgs = vertices.map(
|
|
804
|
-
(
|
|
1315
|
+
(v2, i) => view.create("point3d", v2, {
|
|
805
1316
|
id: `${obj.id}.v${i}`,
|
|
806
1317
|
visible: false,
|
|
807
1318
|
fixed: true,
|
|
@@ -829,6 +1340,7 @@ var CURVED_SEGMENTS2;
|
|
|
829
1340
|
var init_cone3d = __esm({
|
|
830
1341
|
"src/core/scene/kinds/cone3d.ts"() {
|
|
831
1342
|
init_registry();
|
|
1343
|
+
init_ringBasis();
|
|
832
1344
|
CURVED_SEGMENTS2 = 16;
|
|
833
1345
|
registerKind({
|
|
834
1346
|
type: "cone3d",
|
|
@@ -851,10 +1363,16 @@ var init_cone3d = __esm({
|
|
|
851
1363
|
apexPt.Y?.() ?? 0,
|
|
852
1364
|
apexPt.Z?.() ?? 0
|
|
853
1365
|
];
|
|
1366
|
+
const [u, v] = perpBasis([apexCoords[0] - bx, apexCoords[1] - by, apexCoords[2] - bz]);
|
|
854
1367
|
const baseRing = [];
|
|
855
1368
|
for (let i = 0; i < CURVED_SEGMENTS2; i++) {
|
|
856
1369
|
const theta = i / CURVED_SEGMENTS2 * Math.PI * 2;
|
|
857
|
-
|
|
1370
|
+
const c = Math.cos(theta), s = Math.sin(theta);
|
|
1371
|
+
baseRing.push([
|
|
1372
|
+
bx + r * (c * u[0] + s * v[0]),
|
|
1373
|
+
by + r * (c * u[1] + s * v[1]),
|
|
1374
|
+
bz + r * (c * u[2] + s * v[2])
|
|
1375
|
+
]);
|
|
858
1376
|
}
|
|
859
1377
|
const apexIdx = baseRing.length;
|
|
860
1378
|
const vertices = [...baseRing, apexCoords];
|
|
@@ -863,7 +1381,7 @@ var init_cone3d = __esm({
|
|
|
863
1381
|
faces.push([i, (i + 1) % CURVED_SEGMENTS2, apexIdx]);
|
|
864
1382
|
}
|
|
865
1383
|
const vertJxgs = vertices.map(
|
|
866
|
-
(
|
|
1384
|
+
(v2, i) => view.create("point3d", v2, {
|
|
867
1385
|
id: `${obj.id}.v${i}`,
|
|
868
1386
|
visible: false,
|
|
869
1387
|
fixed: true,
|
|
@@ -939,8 +1457,12 @@ function constraintRefs2D(c) {
|
|
|
939
1457
|
return [c.line, c.circle, c.other];
|
|
940
1458
|
case "tangencyPoint":
|
|
941
1459
|
return [c.circle, c.onLine];
|
|
942
|
-
case "arcMidpoint":
|
|
943
|
-
|
|
1460
|
+
case "arcMidpoint": {
|
|
1461
|
+
const containment = c.notContaining ?? c.containing;
|
|
1462
|
+
return containment ? [c.circle, c.a, c.b, containment] : [c.circle, c.a, c.b];
|
|
1463
|
+
}
|
|
1464
|
+
case "mixtilinearPoint":
|
|
1465
|
+
return [c.vertices[0], c.vertices[1], c.vertices[2]];
|
|
944
1466
|
case "pointAtDistance": {
|
|
945
1467
|
const d = c.distance;
|
|
946
1468
|
const extra = d.kind === "circleRadius" ? [d.circle] : d.kind === "segmentLength" ? [d.p1, d.p2] : [];
|
|
@@ -948,8 +1470,16 @@ function constraintRefs2D(c) {
|
|
|
948
1470
|
}
|
|
949
1471
|
case "excenter":
|
|
950
1472
|
return [c.vertices[0], c.vertices[1], c.vertices[2]];
|
|
951
|
-
|
|
1473
|
+
case "commonTangentPoint":
|
|
1474
|
+
return [c.circles[0], c.circles[1]];
|
|
1475
|
+
// Các kind KHÔNG có ref scene-object (toạ độ literal): liệt kê TƯỜNG MINH để
|
|
1476
|
+
// exhaustive never-guard bên dưới buộc khai báo khi thêm constraint kind mới.
|
|
1477
|
+
case "free":
|
|
1478
|
+
case "onAxis":
|
|
1479
|
+
return [];
|
|
1480
|
+
default: {
|
|
952
1481
|
return [];
|
|
1482
|
+
}
|
|
953
1483
|
}
|
|
954
1484
|
}
|
|
955
1485
|
var init_d_constraint2 = __esm({
|
|
@@ -1227,7 +1757,7 @@ function dist(p, q) {
|
|
|
1227
1757
|
function sideOf(a, b, p) {
|
|
1228
1758
|
return (b[0] - a[0]) * (p[1] - a[1]) - (b[1] - a[1]) * (p[0] - a[0]);
|
|
1229
1759
|
}
|
|
1230
|
-
function arcMidpoint(center, radius, a, b,
|
|
1760
|
+
function arcMidpoint(center, radius, a, b, reference, sameSide = false) {
|
|
1231
1761
|
const mcx = (a[0] + b[0]) / 2;
|
|
1232
1762
|
const mcy = (a[1] + b[1]) / 2;
|
|
1233
1763
|
let ux = mcx - center[0];
|
|
@@ -1242,12 +1772,18 @@ function arcMidpoint(center, radius, a, b, notContaining) {
|
|
|
1242
1772
|
uy /= len;
|
|
1243
1773
|
const cand1 = [center[0] + radius * ux, center[1] + radius * uy];
|
|
1244
1774
|
const cand2 = [center[0] - radius * ux, center[1] - radius * uy];
|
|
1775
|
+
if (!reference) return cand1[1] >= cand2[1] ? cand1 : cand2;
|
|
1776
|
+
const notContaining = reference;
|
|
1245
1777
|
const sN = sideOf(a, b, notContaining);
|
|
1246
1778
|
if (Math.abs(sN) < 1e-9) {
|
|
1247
|
-
|
|
1779
|
+
const far = dist(cand1, notContaining) >= dist(cand2, notContaining) ? cand1 : cand2;
|
|
1780
|
+
const near = far === cand1 ? cand2 : cand1;
|
|
1781
|
+
return sameSide ? near : far;
|
|
1248
1782
|
}
|
|
1249
1783
|
const s1 = sideOf(a, b, cand1);
|
|
1250
|
-
|
|
1784
|
+
const opp = s1 * sN < 0 ? cand1 : cand2;
|
|
1785
|
+
const same = opp === cand1 ? cand2 : cand1;
|
|
1786
|
+
return sameSide ? same : opp;
|
|
1251
1787
|
}
|
|
1252
1788
|
function excenter(vertices, oppositeIndex) {
|
|
1253
1789
|
const [A, B, C] = vertices;
|
|
@@ -1263,6 +1799,35 @@ function excenter(vertices, oppositeIndex) {
|
|
|
1263
1799
|
(w[0] * A[1] + w[1] * B[1] + w[2] * C[1]) / sum
|
|
1264
1800
|
];
|
|
1265
1801
|
}
|
|
1802
|
+
function circumcenterXY(a, b, c) {
|
|
1803
|
+
const ax = a[0], ay = a[1], bx = b[0], by = b[1], cx = c[0], cy = c[1];
|
|
1804
|
+
const d = 2 * (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by));
|
|
1805
|
+
if (Math.abs(d) < 1e-12) return [(ax + bx + cx) / 3, (ay + by + cy) / 3];
|
|
1806
|
+
const ux = ((ax * ax + ay * ay) * (by - cy) + (bx * bx + by * by) * (cy - ay) + (cx * cx + cy * cy) * (ay - by)) / d;
|
|
1807
|
+
const uy = ((ax * ax + ay * ay) * (cx - bx) + (bx * bx + by * by) * (ax - cx) + (cx * cx + cy * cy) * (bx - ax)) / d;
|
|
1808
|
+
return [ux, uy];
|
|
1809
|
+
}
|
|
1810
|
+
function mixtilinearPoint(a, b, c, which) {
|
|
1811
|
+
const O = circumcenterXY(a, b, c);
|
|
1812
|
+
const R = Math.hypot(O[0] - a[0], O[1] - a[1]);
|
|
1813
|
+
const ux1 = (b[0] - a[0]) / (Math.hypot(b[0] - a[0], b[1] - a[1]) || 1);
|
|
1814
|
+
const uy1 = (b[1] - a[1]) / (Math.hypot(b[0] - a[0], b[1] - a[1]) || 1);
|
|
1815
|
+
const ux2 = (c[0] - a[0]) / (Math.hypot(c[0] - a[0], c[1] - a[1]) || 1);
|
|
1816
|
+
const uy2 = (c[1] - a[1]) / (Math.hypot(c[0] - a[0], c[1] - a[1]) || 1);
|
|
1817
|
+
let bx = ux1 + ux2, by = uy1 + uy2;
|
|
1818
|
+
const bl = Math.hypot(bx, by) || 1;
|
|
1819
|
+
bx /= bl;
|
|
1820
|
+
by /= bl;
|
|
1821
|
+
const cosA = ux1 * ux2 + uy1 * uy2;
|
|
1822
|
+
const sinHalf = Math.sqrt(Math.max(0, (1 - cosA) / 2));
|
|
1823
|
+
const cos2Half = Math.max(1e-9, (1 + cosA) / 2);
|
|
1824
|
+
const dotAO = bx * (O[0] - a[0]) + by * (O[1] - a[1]);
|
|
1825
|
+
const d = 2 * (dotAO - R * sinHalf) / cos2Half;
|
|
1826
|
+
const K = [a[0] + d * bx, a[1] + d * by];
|
|
1827
|
+
if (which === "center") return K;
|
|
1828
|
+
const kl = Math.hypot(K[0] - O[0], K[1] - O[1]) || 1;
|
|
1829
|
+
return [O[0] + R * (K[0] - O[0]) / kl, O[1] + R * (K[1] - O[1]) / kl];
|
|
1830
|
+
}
|
|
1266
1831
|
function pointAtDistanceCoord(from, through, d) {
|
|
1267
1832
|
const dx = through[0] - from[0];
|
|
1268
1833
|
const dy = through[1] - from[1];
|
|
@@ -1290,29 +1855,38 @@ var init_arcMidpoint = __esm({
|
|
|
1290
1855
|
arcMidpointConstraint = definePointConstraint({
|
|
1291
1856
|
kind: "arcMidpoint",
|
|
1292
1857
|
validate: (c) => {
|
|
1293
|
-
if (!c.circle || !c.a || !c.b
|
|
1294
|
-
throw new Error("point.arcMidpoint: circle, a, b
|
|
1858
|
+
if (!c.circle || !c.a || !c.b) {
|
|
1859
|
+
throw new Error("point.arcMidpoint: circle, a, b b\u1EAFt bu\u1ED9c");
|
|
1860
|
+
}
|
|
1861
|
+
if (c.notContaining && c.containing) {
|
|
1862
|
+
throw new Error("point.arcMidpoint: kh\xF4ng th\u1EC3 v\u1EEBa notContaining v\u1EEBa containing");
|
|
1295
1863
|
}
|
|
1296
1864
|
},
|
|
1297
1865
|
describe: (obj, state, c) => {
|
|
1298
1866
|
const al = state?.objects[c.a]?.label ?? c.a;
|
|
1299
1867
|
const bl = state?.objects[c.b]?.label ?? c.b;
|
|
1300
|
-
const
|
|
1301
|
-
return `${obj.label} = trung \u0111i\u1EC3m cung ${al}${bl}
|
|
1868
|
+
const ref = c.containing ?? c.notContaining;
|
|
1869
|
+
if (!ref) return `${obj.label} = trung \u0111i\u1EC3m cung ${al}${bl}`;
|
|
1870
|
+
const rl = state?.objects[ref]?.label ?? ref;
|
|
1871
|
+
const rel = c.containing ? "ch\u1EE9a" : "kh\xF4ng ch\u1EE9a";
|
|
1872
|
+
return `${obj.label} = trung \u0111i\u1EC3m cung ${al}${bl} (${rel} ${rl})`;
|
|
1302
1873
|
},
|
|
1303
1874
|
render: (obj, ctx, c, opts) => {
|
|
1304
1875
|
const board = ctx.jxg;
|
|
1305
1876
|
const circle = ctx.resolveRef(c.circle);
|
|
1306
1877
|
const A = ctx.resolveRef(c.a);
|
|
1307
1878
|
const B = ctx.resolveRef(c.b);
|
|
1308
|
-
const
|
|
1879
|
+
const refName = c.containing ?? c.notContaining;
|
|
1880
|
+
const ref = refName ? ctx.resolveRef(refName) : void 0;
|
|
1881
|
+
const sameSide = !!c.containing;
|
|
1309
1882
|
const O = circle?.center ?? circle?.midpoint ?? circle;
|
|
1310
1883
|
const am = () => arcMidpoint(
|
|
1311
1884
|
[O.X(), O.Y()],
|
|
1312
1885
|
circle.Radius(),
|
|
1313
1886
|
[A.X(), A.Y()],
|
|
1314
1887
|
[B.X(), B.Y()],
|
|
1315
|
-
[
|
|
1888
|
+
ref ? [ref.X(), ref.Y()] : void 0,
|
|
1889
|
+
sameSide
|
|
1316
1890
|
);
|
|
1317
1891
|
return board.create("point", [() => am()[0], () => am()[1]], opts);
|
|
1318
1892
|
}
|
|
@@ -1362,6 +1936,59 @@ var init_excenter = __esm({
|
|
|
1362
1936
|
}
|
|
1363
1937
|
});
|
|
1364
1938
|
|
|
1939
|
+
// src/core/scene/kinds/point-constraints/mixtilinearPoint.ts
|
|
1940
|
+
var mixtilinearPointConstraint;
|
|
1941
|
+
var init_mixtilinearPoint = __esm({
|
|
1942
|
+
"src/core/scene/kinds/point-constraints/mixtilinearPoint.ts"() {
|
|
1943
|
+
init_pointConstructions();
|
|
1944
|
+
init_types2();
|
|
1945
|
+
mixtilinearPointConstraint = definePointConstraint({
|
|
1946
|
+
kind: "mixtilinearPoint",
|
|
1947
|
+
validate: (c) => {
|
|
1948
|
+
if (!Array.isArray(c.vertices) || c.vertices.length !== 3 || !c.vertices.every(Boolean)) {
|
|
1949
|
+
throw new Error("point.mixtilinearPoint: vertices ph\u1EA3i l\xE0 tuple 3 id non-empty");
|
|
1950
|
+
}
|
|
1951
|
+
if (c.which !== "center" && c.which !== "touch") {
|
|
1952
|
+
throw new Error("point.mixtilinearPoint: which ph\u1EA3i 'center' | 'touch'");
|
|
1953
|
+
}
|
|
1954
|
+
},
|
|
1955
|
+
describe: (obj, state, c) => {
|
|
1956
|
+
const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
|
|
1957
|
+
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)`;
|
|
1958
|
+
},
|
|
1959
|
+
render: (obj, ctx, c, opts) => {
|
|
1960
|
+
const board = ctx.jxg;
|
|
1961
|
+
const a = ctx.resolveRef(c.vertices[0]);
|
|
1962
|
+
const b = ctx.resolveRef(c.vertices[1]);
|
|
1963
|
+
const d = ctx.resolveRef(c.vertices[2]);
|
|
1964
|
+
const mp = () => mixtilinearPoint(
|
|
1965
|
+
[a.X(), a.Y()],
|
|
1966
|
+
[b.X(), b.Y()],
|
|
1967
|
+
[d.X(), d.Y()],
|
|
1968
|
+
c.which
|
|
1969
|
+
);
|
|
1970
|
+
return board.create("point", [() => mp()[0], () => mp()[1]], opts);
|
|
1971
|
+
}
|
|
1972
|
+
});
|
|
1973
|
+
}
|
|
1974
|
+
});
|
|
1975
|
+
|
|
1976
|
+
// src/core/scene/kinds/_label.ts
|
|
1977
|
+
function labelOpts(labelOffset, dflt) {
|
|
1978
|
+
const offset = labelOffset ?? dflt;
|
|
1979
|
+
return { label: { fixed: false, ...offset ? { offset } : {} } };
|
|
1980
|
+
}
|
|
1981
|
+
function readLabelOffset(label) {
|
|
1982
|
+
const off = label.evalVisProp?.("offset") ?? label.visProp?.offset;
|
|
1983
|
+
const rel = label.relativeCoords?.scrCoords;
|
|
1984
|
+
if (!off || !rel || off.length < 2 || rel.length < 3) return null;
|
|
1985
|
+
return [Math.round(off[0] + rel[1]), Math.round(off[1] - rel[2])];
|
|
1986
|
+
}
|
|
1987
|
+
var init_label = __esm({
|
|
1988
|
+
"src/core/scene/kinds/_label.ts"() {
|
|
1989
|
+
}
|
|
1990
|
+
});
|
|
1991
|
+
|
|
1365
1992
|
// src/core/scene/kinds/point-constraints/shared.ts
|
|
1366
1993
|
function buildJxgTransforms(board, ctx, t) {
|
|
1367
1994
|
switch (t.kind) {
|
|
@@ -1390,19 +2017,19 @@ function buildJxgTransforms(board, ctx, t) {
|
|
|
1390
2017
|
}
|
|
1391
2018
|
}
|
|
1392
2019
|
function makeDistanceFn(ctx, d) {
|
|
1393
|
-
const
|
|
2020
|
+
const scale2 = d.scale ?? 1;
|
|
1394
2021
|
const offset = d.offset ?? 0;
|
|
1395
2022
|
if (d.kind === "literal") {
|
|
1396
2023
|
const v = d.value;
|
|
1397
|
-
return () =>
|
|
2024
|
+
return () => scale2 * v + offset;
|
|
1398
2025
|
}
|
|
1399
2026
|
if (d.kind === "segmentLength") {
|
|
1400
2027
|
const p = ctx.resolveRef(d.p1);
|
|
1401
2028
|
const q = ctx.resolveRef(d.p2);
|
|
1402
|
-
return () =>
|
|
2029
|
+
return () => scale2 * Math.hypot(p.X() - q.X(), p.Y() - q.Y()) + offset;
|
|
1403
2030
|
}
|
|
1404
2031
|
const circle = ctx.resolveRef(d.circle);
|
|
1405
|
-
return () =>
|
|
2032
|
+
return () => scale2 * circle.Radius() + offset;
|
|
1406
2033
|
}
|
|
1407
2034
|
function buildPointOpts(obj) {
|
|
1408
2035
|
return {
|
|
@@ -1413,11 +2040,13 @@ function buildPointOpts(obj) {
|
|
|
1413
2040
|
strokeColor: obj.attrs.color ?? "#1e40af",
|
|
1414
2041
|
fillColor: obj.attrs.color ?? "#1e40af",
|
|
1415
2042
|
face: obj.attrs.face ?? "o",
|
|
1416
|
-
size: obj.attrs.size ?? 4
|
|
2043
|
+
size: obj.attrs.size ?? 4,
|
|
2044
|
+
...labelOpts(obj.attrs.labelOffset, [10, 10])
|
|
1417
2045
|
};
|
|
1418
2046
|
}
|
|
1419
2047
|
var init_shared = __esm({
|
|
1420
2048
|
"src/core/scene/kinds/point-constraints/shared.ts"() {
|
|
2049
|
+
init_label();
|
|
1421
2050
|
}
|
|
1422
2051
|
});
|
|
1423
2052
|
|
|
@@ -1449,6 +2078,73 @@ var init_pointAtDistance = __esm({
|
|
|
1449
2078
|
}
|
|
1450
2079
|
});
|
|
1451
2080
|
|
|
2081
|
+
// src/stamps/geometry-2d/geometry/commonTangent.ts
|
|
2082
|
+
function computeCommonTangentPoint(O1, r1, O2, r2, on, variant, side) {
|
|
2083
|
+
const dx = O2[0] - O1[0];
|
|
2084
|
+
const dy = O2[1] - O1[1];
|
|
2085
|
+
const d = Math.hypot(dx, dy);
|
|
2086
|
+
if (d < EPS) return null;
|
|
2087
|
+
const beta = Math.atan2(dy, dx);
|
|
2088
|
+
const ratio = variant === "external" ? (r1 - r2) / d : (r1 + r2) / d;
|
|
2089
|
+
if (Math.abs(ratio) > 1) return null;
|
|
2090
|
+
const gamma = Math.acos(ratio);
|
|
2091
|
+
const s = side === 0 ? 1 : -1;
|
|
2092
|
+
const angle = beta + s * gamma;
|
|
2093
|
+
const nx = Math.cos(angle);
|
|
2094
|
+
const ny = Math.sin(angle);
|
|
2095
|
+
if (on === 0) {
|
|
2096
|
+
return [O1[0] + r1 * nx, O1[1] + r1 * ny];
|
|
2097
|
+
}
|
|
2098
|
+
if (variant === "external") {
|
|
2099
|
+
return [O2[0] + r2 * nx, O2[1] + r2 * ny];
|
|
2100
|
+
}
|
|
2101
|
+
return [O2[0] - r2 * nx, O2[1] - r2 * ny];
|
|
2102
|
+
}
|
|
2103
|
+
var EPS;
|
|
2104
|
+
var init_commonTangent = __esm({
|
|
2105
|
+
"src/stamps/geometry-2d/geometry/commonTangent.ts"() {
|
|
2106
|
+
EPS = 1e-9;
|
|
2107
|
+
}
|
|
2108
|
+
});
|
|
2109
|
+
|
|
2110
|
+
// src/core/scene/kinds/point-constraints/commonTangentPoint.ts
|
|
2111
|
+
var commonTangentPointConstraint;
|
|
2112
|
+
var init_commonTangentPoint = __esm({
|
|
2113
|
+
"src/core/scene/kinds/point-constraints/commonTangentPoint.ts"() {
|
|
2114
|
+
init_commonTangent();
|
|
2115
|
+
init_types2();
|
|
2116
|
+
commonTangentPointConstraint = definePointConstraint({
|
|
2117
|
+
kind: "commonTangentPoint",
|
|
2118
|
+
describe: (obj, state, c) => {
|
|
2119
|
+
const c1 = state?.objects[c.circles[0]]?.label ?? c.circles[0];
|
|
2120
|
+
const c2 = state?.objects[c.circles[1]]?.label ?? c.circles[1];
|
|
2121
|
+
const v = c.variant === "internal" ? "trong" : "ngo\xE0i";
|
|
2122
|
+
return `${obj.label} = ti\u1EBFp \u0111i\u1EC3m ti\u1EBFp tuy\u1EBFn chung ${v} ${c1},${c2} (tr\xEAn ${c.on === 0 ? c1 : c2})`;
|
|
2123
|
+
},
|
|
2124
|
+
render: (obj, ctx, c, opts) => {
|
|
2125
|
+
const board = ctx.jxg;
|
|
2126
|
+
const k1 = ctx.resolveRef(c.circles[0]);
|
|
2127
|
+
const k2 = ctx.resolveRef(c.circles[1]);
|
|
2128
|
+
const O1 = k1?.center ?? k1?.midpoint ?? k1;
|
|
2129
|
+
const O2 = k2?.center ?? k2?.midpoint ?? k2;
|
|
2130
|
+
const compute = () => {
|
|
2131
|
+
const t = computeCommonTangentPoint(
|
|
2132
|
+
[O1.X(), O1.Y()],
|
|
2133
|
+
k1.Radius(),
|
|
2134
|
+
[O2.X(), O2.Y()],
|
|
2135
|
+
k2.Radius(),
|
|
2136
|
+
c.on,
|
|
2137
|
+
c.variant,
|
|
2138
|
+
c.side
|
|
2139
|
+
);
|
|
2140
|
+
return t ?? [0, 0];
|
|
2141
|
+
};
|
|
2142
|
+
return board.create("point", [() => compute()[0], () => compute()[1]], opts);
|
|
2143
|
+
}
|
|
2144
|
+
});
|
|
2145
|
+
}
|
|
2146
|
+
});
|
|
2147
|
+
|
|
1452
2148
|
// src/core/scene/kinds/point-constraints/circleIntersection.ts
|
|
1453
2149
|
var circleIntersectionConstraint;
|
|
1454
2150
|
var init_circleIntersection = __esm({
|
|
@@ -1726,7 +2422,9 @@ var init_registry2 = __esm({
|
|
|
1726
2422
|
init_centroid();
|
|
1727
2423
|
init_arcMidpoint();
|
|
1728
2424
|
init_excenter();
|
|
2425
|
+
init_mixtilinearPoint();
|
|
1729
2426
|
init_pointAtDistance();
|
|
2427
|
+
init_commonTangentPoint();
|
|
1730
2428
|
init_circleIntersection();
|
|
1731
2429
|
init_circleSecondIntersection();
|
|
1732
2430
|
init_secondIntersection();
|
|
@@ -1751,7 +2449,9 @@ var init_registry2 = __esm({
|
|
|
1751
2449
|
centroidConstraint,
|
|
1752
2450
|
arcMidpointConstraint,
|
|
1753
2451
|
excenterConstraint,
|
|
2452
|
+
mixtilinearPointConstraint,
|
|
1754
2453
|
pointAtDistanceConstraint,
|
|
2454
|
+
commonTangentPointConstraint,
|
|
1755
2455
|
circleIntersectionConstraint,
|
|
1756
2456
|
circleSecondIntersectionConstraint,
|
|
1757
2457
|
secondIntersectionConstraint,
|
|
@@ -1768,14 +2468,15 @@ var init_registry2 = __esm({
|
|
|
1768
2468
|
});
|
|
1769
2469
|
|
|
1770
2470
|
// src/core/scene/kinds/point.ts
|
|
1771
|
-
var
|
|
2471
|
+
var def5;
|
|
1772
2472
|
var init_point = __esm({
|
|
1773
2473
|
"src/core/scene/kinds/point.ts"() {
|
|
1774
2474
|
init_registry();
|
|
1775
2475
|
init_d_constraint2();
|
|
1776
2476
|
init_registry2();
|
|
1777
2477
|
init_shared();
|
|
1778
|
-
|
|
2478
|
+
init_label();
|
|
2479
|
+
def5 = {
|
|
1779
2480
|
type: "point",
|
|
1780
2481
|
schemaVersion: 1,
|
|
1781
2482
|
migrate: {},
|
|
@@ -1842,7 +2543,8 @@ var init_point = __esm({
|
|
|
1842
2543
|
strokeColor: obj.attrs.color ?? "#1e40af",
|
|
1843
2544
|
fillColor: obj.attrs.color ?? "#1e40af",
|
|
1844
2545
|
face: obj.attrs.face ?? "o",
|
|
1845
|
-
size: obj.attrs.size ?? 4
|
|
2546
|
+
size: obj.attrs.size ?? 4,
|
|
2547
|
+
...labelOpts(obj.attrs.labelOffset, [10, 10])
|
|
1846
2548
|
});
|
|
1847
2549
|
} catch {
|
|
1848
2550
|
}
|
|
@@ -1852,17 +2554,18 @@ var init_point = __esm({
|
|
|
1852
2554
|
throw new Error("point: constraint kind changed \u2014 recreate");
|
|
1853
2555
|
}
|
|
1854
2556
|
};
|
|
1855
|
-
registerKind(
|
|
2557
|
+
registerKind(def5);
|
|
1856
2558
|
}
|
|
1857
2559
|
});
|
|
1858
2560
|
|
|
1859
2561
|
// src/core/scene/kinds/segment.ts
|
|
1860
|
-
var
|
|
2562
|
+
var def6;
|
|
1861
2563
|
var init_segment = __esm({
|
|
1862
2564
|
"src/core/scene/kinds/segment.ts"() {
|
|
1863
2565
|
init_registry();
|
|
1864
2566
|
init_labelOf();
|
|
1865
|
-
|
|
2567
|
+
init_label();
|
|
2568
|
+
def6 = {
|
|
1866
2569
|
type: "segment",
|
|
1867
2570
|
schemaVersion: 1,
|
|
1868
2571
|
migrate: {},
|
|
@@ -1893,11 +2596,12 @@ var init_segment = __esm({
|
|
|
1893
2596
|
strokeWidth: obj.attrs.width ?? 2,
|
|
1894
2597
|
dash: obj.attrs.dash ?? 0,
|
|
1895
2598
|
visible: obj.visible,
|
|
1896
|
-
fixed: obj.locked
|
|
2599
|
+
fixed: obj.locked,
|
|
2600
|
+
...labelOpts(obj.attrs.labelOffset)
|
|
1897
2601
|
});
|
|
1898
2602
|
}
|
|
1899
2603
|
};
|
|
1900
|
-
registerKind(
|
|
2604
|
+
registerKind(def6);
|
|
1901
2605
|
}
|
|
1902
2606
|
});
|
|
1903
2607
|
|
|
@@ -1925,13 +2629,14 @@ function constructionRefs(c) {
|
|
|
1925
2629
|
return [c.throughPoint, c.toCircle];
|
|
1926
2630
|
}
|
|
1927
2631
|
}
|
|
1928
|
-
var
|
|
2632
|
+
var def7;
|
|
1929
2633
|
var init_line = __esm({
|
|
1930
2634
|
"src/core/scene/kinds/line.ts"() {
|
|
1931
2635
|
init_registry();
|
|
1932
2636
|
init_labelOf();
|
|
2637
|
+
init_label();
|
|
1933
2638
|
init_pointConstructions();
|
|
1934
|
-
|
|
2639
|
+
def7 = {
|
|
1935
2640
|
type: "line",
|
|
1936
2641
|
schemaVersion: 1,
|
|
1937
2642
|
migrate: {},
|
|
@@ -1972,7 +2677,8 @@ var init_line = __esm({
|
|
|
1972
2677
|
strokeWidth: obj.attrs.width ?? 2,
|
|
1973
2678
|
dash: obj.attrs.dash ?? 0,
|
|
1974
2679
|
visible: obj.visible,
|
|
1975
|
-
fixed: obj.locked
|
|
2680
|
+
fixed: obj.locked,
|
|
2681
|
+
...labelOpts(obj.attrs.labelOffset)
|
|
1976
2682
|
};
|
|
1977
2683
|
const c = obj.attrs.construction;
|
|
1978
2684
|
if (!c) {
|
|
@@ -2126,17 +2832,17 @@ var init_line = __esm({
|
|
|
2126
2832
|
}
|
|
2127
2833
|
}
|
|
2128
2834
|
};
|
|
2129
|
-
registerKind(
|
|
2835
|
+
registerKind(def7);
|
|
2130
2836
|
}
|
|
2131
2837
|
});
|
|
2132
2838
|
|
|
2133
2839
|
// src/core/scene/kinds/ray.ts
|
|
2134
|
-
var
|
|
2840
|
+
var def8;
|
|
2135
2841
|
var init_ray = __esm({
|
|
2136
2842
|
"src/core/scene/kinds/ray.ts"() {
|
|
2137
2843
|
init_registry();
|
|
2138
2844
|
init_labelOf();
|
|
2139
|
-
|
|
2845
|
+
def8 = {
|
|
2140
2846
|
type: "ray",
|
|
2141
2847
|
schemaVersion: 1,
|
|
2142
2848
|
migrate: {},
|
|
@@ -2161,17 +2867,17 @@ var init_ray = __esm({
|
|
|
2161
2867
|
});
|
|
2162
2868
|
}
|
|
2163
2869
|
};
|
|
2164
|
-
registerKind(
|
|
2870
|
+
registerKind(def8);
|
|
2165
2871
|
}
|
|
2166
2872
|
});
|
|
2167
2873
|
|
|
2168
2874
|
// src/core/scene/kinds/vector.ts
|
|
2169
|
-
var
|
|
2875
|
+
var def9;
|
|
2170
2876
|
var init_vector = __esm({
|
|
2171
2877
|
"src/core/scene/kinds/vector.ts"() {
|
|
2172
2878
|
init_registry();
|
|
2173
2879
|
init_labelOf();
|
|
2174
|
-
|
|
2880
|
+
def9 = {
|
|
2175
2881
|
type: "vector",
|
|
2176
2882
|
schemaVersion: 1,
|
|
2177
2883
|
migrate: {},
|
|
@@ -2193,7 +2899,7 @@ var init_vector = __esm({
|
|
|
2193
2899
|
});
|
|
2194
2900
|
}
|
|
2195
2901
|
};
|
|
2196
|
-
registerKind(
|
|
2902
|
+
registerKind(def9);
|
|
2197
2903
|
}
|
|
2198
2904
|
});
|
|
2199
2905
|
|
|
@@ -2223,13 +2929,14 @@ function constructionRefs2(c) {
|
|
|
2223
2929
|
return [c.p1, c.p2];
|
|
2224
2930
|
}
|
|
2225
2931
|
}
|
|
2226
|
-
var
|
|
2932
|
+
var def10;
|
|
2227
2933
|
var init_circle = __esm({
|
|
2228
2934
|
"src/core/scene/kinds/circle.ts"() {
|
|
2229
2935
|
init_registry();
|
|
2230
2936
|
init_labelOf();
|
|
2937
|
+
init_label();
|
|
2231
2938
|
init_pointConstructions();
|
|
2232
|
-
|
|
2939
|
+
def10 = {
|
|
2233
2940
|
type: "circle",
|
|
2234
2941
|
schemaVersion: 1,
|
|
2235
2942
|
migrate: {},
|
|
@@ -2289,15 +2996,17 @@ var init_circle = __esm({
|
|
|
2289
2996
|
const board = ctx.jxg;
|
|
2290
2997
|
const isCenterLabel = (l) => /^[A-Z]['′]?\d*$/u.test(l);
|
|
2291
2998
|
const isCenter = isCenterLabel(obj.label);
|
|
2999
|
+
const isRenamedCircle = /_c$/.test(obj.label);
|
|
2292
3000
|
const baseOpts = {
|
|
2293
3001
|
name: obj.label,
|
|
2294
|
-
withLabel: isCenter ? obj.attrs.showLabel ?? false : true,
|
|
3002
|
+
withLabel: isCenter ? obj.attrs.showLabel ?? false : isRenamedCircle ? false : true,
|
|
2295
3003
|
strokeColor: obj.attrs.color ?? "#0f172a",
|
|
2296
3004
|
strokeWidth: obj.attrs.width ?? 2,
|
|
2297
3005
|
dash: obj.attrs.dash ?? 0,
|
|
2298
3006
|
fillColor: "none",
|
|
2299
3007
|
visible: obj.visible,
|
|
2300
|
-
fixed: obj.locked
|
|
3008
|
+
fixed: obj.locked,
|
|
3009
|
+
...labelOpts(obj.attrs.labelOffset)
|
|
2301
3010
|
};
|
|
2302
3011
|
const c = asConstruction(obj.attrs);
|
|
2303
3012
|
if (c?.kind === "circumscribed") {
|
|
@@ -2379,7 +3088,7 @@ var init_circle = __esm({
|
|
|
2379
3088
|
return board.create("circle", [center, surface], baseOpts);
|
|
2380
3089
|
}
|
|
2381
3090
|
};
|
|
2382
|
-
registerKind(
|
|
3091
|
+
registerKind(def10);
|
|
2383
3092
|
}
|
|
2384
3093
|
});
|
|
2385
3094
|
|
|
@@ -2394,12 +3103,12 @@ function constructionRefs3(c) {
|
|
|
2394
3103
|
return [c.p1, c.p2, c.p3];
|
|
2395
3104
|
}
|
|
2396
3105
|
}
|
|
2397
|
-
var
|
|
3106
|
+
var def11;
|
|
2398
3107
|
var init_arc = __esm({
|
|
2399
3108
|
"src/core/scene/kinds/arc.ts"() {
|
|
2400
3109
|
init_registry();
|
|
2401
3110
|
init_labelOf();
|
|
2402
|
-
|
|
3111
|
+
def11 = {
|
|
2403
3112
|
type: "arc",
|
|
2404
3113
|
schemaVersion: 1,
|
|
2405
3114
|
migrate: {},
|
|
@@ -2457,17 +3166,17 @@ var init_arc = __esm({
|
|
|
2457
3166
|
return board.create("circumcirclearc", [A, B, C], baseOpts);
|
|
2458
3167
|
}
|
|
2459
3168
|
};
|
|
2460
|
-
registerKind(
|
|
3169
|
+
registerKind(def11);
|
|
2461
3170
|
}
|
|
2462
3171
|
});
|
|
2463
3172
|
|
|
2464
3173
|
// src/core/scene/kinds/sector.ts
|
|
2465
|
-
var
|
|
3174
|
+
var def12;
|
|
2466
3175
|
var init_sector = __esm({
|
|
2467
3176
|
"src/core/scene/kinds/sector.ts"() {
|
|
2468
3177
|
init_registry();
|
|
2469
3178
|
init_labelOf();
|
|
2470
|
-
|
|
3179
|
+
def12 = {
|
|
2471
3180
|
type: "sector",
|
|
2472
3181
|
schemaVersion: 1,
|
|
2473
3182
|
migrate: {},
|
|
@@ -2507,7 +3216,7 @@ var init_sector = __esm({
|
|
|
2507
3216
|
});
|
|
2508
3217
|
}
|
|
2509
3218
|
};
|
|
2510
|
-
registerKind(
|
|
3219
|
+
registerKind(def12);
|
|
2511
3220
|
}
|
|
2512
3221
|
});
|
|
2513
3222
|
|
|
@@ -2553,12 +3262,12 @@ function specialShapeName(kind) {
|
|
|
2553
3262
|
return "";
|
|
2554
3263
|
}
|
|
2555
3264
|
}
|
|
2556
|
-
var
|
|
3265
|
+
var def13;
|
|
2557
3266
|
var init_polygon = __esm({
|
|
2558
3267
|
"src/core/scene/kinds/polygon.ts"() {
|
|
2559
3268
|
init_registry();
|
|
2560
3269
|
init_labelOf();
|
|
2561
|
-
|
|
3270
|
+
def13 = {
|
|
2562
3271
|
type: "polygon",
|
|
2563
3272
|
schemaVersion: 1,
|
|
2564
3273
|
migrate: {},
|
|
@@ -2745,16 +3454,16 @@ var init_polygon = __esm({
|
|
|
2745
3454
|
return poly;
|
|
2746
3455
|
}
|
|
2747
3456
|
};
|
|
2748
|
-
registerKind(
|
|
3457
|
+
registerKind(def13);
|
|
2749
3458
|
}
|
|
2750
3459
|
});
|
|
2751
3460
|
|
|
2752
3461
|
// src/core/scene/kinds/intersection.ts
|
|
2753
|
-
var
|
|
3462
|
+
var def14;
|
|
2754
3463
|
var init_intersection = __esm({
|
|
2755
3464
|
"src/core/scene/kinds/intersection.ts"() {
|
|
2756
3465
|
init_registry();
|
|
2757
|
-
|
|
3466
|
+
def14 = {
|
|
2758
3467
|
type: "intersection",
|
|
2759
3468
|
schemaVersion: 1,
|
|
2760
3469
|
migrate: {},
|
|
@@ -2782,8 +3491,11 @@ var init_intersection = __esm({
|
|
|
2782
3491
|
const opts = {
|
|
2783
3492
|
name: obj.label,
|
|
2784
3493
|
withLabel: true,
|
|
2785
|
-
|
|
2786
|
-
|
|
3494
|
+
// Cùng màu xanh với mọi điểm khác (buildPointOpts dùng '#1e40af').
|
|
3495
|
+
// Trước đây điểm giao tô đỏ '#dc2626' → tách biệt thị giác nhưng gây
|
|
3496
|
+
// khó hiểu ("vì sao điểm này khác màu?"). Giữ chung một màu.
|
|
3497
|
+
strokeColor: obj.attrs.color ?? "#1e40af",
|
|
3498
|
+
fillColor: obj.attrs.color ?? "#1e40af",
|
|
2787
3499
|
visible: obj.visible,
|
|
2788
3500
|
fixed: obj.locked
|
|
2789
3501
|
};
|
|
@@ -2792,19 +3504,51 @@ var init_intersection = __esm({
|
|
|
2792
3504
|
}
|
|
2793
3505
|
const branch = obj.attrs.branch ?? 0;
|
|
2794
3506
|
return board.create("intersection", [a, b, branch], opts);
|
|
3507
|
+
},
|
|
3508
|
+
/**
|
|
3509
|
+
* Cập nhật TẠI CHỖ các thuộc tính "trang trí" (tên/màu/ẩn-hiện/khoá) qua
|
|
3510
|
+
* setAttribute — giữ nguyên JxgObj identity nên các object phụ thuộc điểm
|
|
3511
|
+
* giao (đường thẳng qua nó, …) KHÔNG bị stale parent ref. Mô phỏng update
|
|
3512
|
+
* hook của point.ts.
|
|
3513
|
+
*
|
|
3514
|
+
* Nếu định nghĩa hình học đổi (kind/ref1/ref2/branch) thì throw → renderer
|
|
3515
|
+
* fallback remove + create để JSXGraph dựng lại phép giao đúng.
|
|
3516
|
+
*/
|
|
3517
|
+
update: (obj, prev, ctx, existing) => {
|
|
3518
|
+
const a = obj.attrs;
|
|
3519
|
+
const p = prev.attrs;
|
|
3520
|
+
const branchA = a.branch;
|
|
3521
|
+
const branchP = p.branch;
|
|
3522
|
+
if (a.kind !== p.kind || a.ref1 !== p.ref1 || a.ref2 !== p.ref2 || branchA !== branchP) {
|
|
3523
|
+
throw new Error("intersection: \u0111\u1ECBnh ngh\u0129a h\xECnh h\u1ECDc \u0111\u1ED5i \u2014 recreate");
|
|
3524
|
+
}
|
|
3525
|
+
const el = existing;
|
|
3526
|
+
if (typeof el.setAttribute === "function") {
|
|
3527
|
+
try {
|
|
3528
|
+
el.setAttribute({
|
|
3529
|
+
name: obj.label,
|
|
3530
|
+
withLabel: true,
|
|
3531
|
+
strokeColor: a.color ?? "#1e40af",
|
|
3532
|
+
fillColor: a.color ?? "#1e40af",
|
|
3533
|
+
visible: obj.visible,
|
|
3534
|
+
fixed: obj.locked
|
|
3535
|
+
});
|
|
3536
|
+
} catch {
|
|
3537
|
+
}
|
|
3538
|
+
}
|
|
2795
3539
|
}
|
|
2796
3540
|
};
|
|
2797
|
-
registerKind(
|
|
3541
|
+
registerKind(def14);
|
|
2798
3542
|
}
|
|
2799
3543
|
});
|
|
2800
3544
|
|
|
2801
3545
|
// src/core/scene/kinds/angle.ts
|
|
2802
|
-
var
|
|
3546
|
+
var def15;
|
|
2803
3547
|
var init_angle = __esm({
|
|
2804
3548
|
"src/core/scene/kinds/angle.ts"() {
|
|
2805
3549
|
init_registry();
|
|
2806
3550
|
init_labelOf();
|
|
2807
|
-
|
|
3551
|
+
def15 = {
|
|
2808
3552
|
type: "angle",
|
|
2809
3553
|
schemaVersion: 1,
|
|
2810
3554
|
migrate: {},
|
|
@@ -2840,17 +3584,17 @@ var init_angle = __esm({
|
|
|
2840
3584
|
});
|
|
2841
3585
|
}
|
|
2842
3586
|
};
|
|
2843
|
-
registerKind(
|
|
3587
|
+
registerKind(def15);
|
|
2844
3588
|
}
|
|
2845
3589
|
});
|
|
2846
3590
|
|
|
2847
3591
|
// src/core/scene/kinds/distance.ts
|
|
2848
|
-
var
|
|
3592
|
+
var def16;
|
|
2849
3593
|
var init_distance = __esm({
|
|
2850
3594
|
"src/core/scene/kinds/distance.ts"() {
|
|
2851
3595
|
init_registry();
|
|
2852
3596
|
init_labelOf();
|
|
2853
|
-
|
|
3597
|
+
def16 = {
|
|
2854
3598
|
type: "distance",
|
|
2855
3599
|
schemaVersion: 1,
|
|
2856
3600
|
migrate: {},
|
|
@@ -2879,7 +3623,7 @@ var init_distance = __esm({
|
|
|
2879
3623
|
});
|
|
2880
3624
|
}
|
|
2881
3625
|
};
|
|
2882
|
-
registerKind(
|
|
3626
|
+
registerKind(def16);
|
|
2883
3627
|
}
|
|
2884
3628
|
});
|
|
2885
3629
|
|
|
@@ -3008,12 +3752,12 @@ var init_parser = __esm({
|
|
|
3008
3752
|
});
|
|
3009
3753
|
|
|
3010
3754
|
// src/core/scene/kinds/function2d.ts
|
|
3011
|
-
var
|
|
3755
|
+
var def17;
|
|
3012
3756
|
var init_function2d = __esm({
|
|
3013
3757
|
"src/core/scene/kinds/function2d.ts"() {
|
|
3014
3758
|
init_registry();
|
|
3015
3759
|
init_parser();
|
|
3016
|
-
|
|
3760
|
+
def17 = {
|
|
3017
3761
|
type: "function2d",
|
|
3018
3762
|
schemaVersion: 1,
|
|
3019
3763
|
migrate: {},
|
|
@@ -3052,16 +3796,16 @@ var init_function2d = __esm({
|
|
|
3052
3796
|
});
|
|
3053
3797
|
}
|
|
3054
3798
|
};
|
|
3055
|
-
registerKind(
|
|
3799
|
+
registerKind(def17);
|
|
3056
3800
|
}
|
|
3057
3801
|
});
|
|
3058
3802
|
|
|
3059
3803
|
// src/core/scene/kinds/parameter.ts
|
|
3060
|
-
var
|
|
3804
|
+
var def18;
|
|
3061
3805
|
var init_parameter = __esm({
|
|
3062
3806
|
"src/core/scene/kinds/parameter.ts"() {
|
|
3063
3807
|
init_registry();
|
|
3064
|
-
|
|
3808
|
+
def18 = {
|
|
3065
3809
|
type: "parameter",
|
|
3066
3810
|
schemaVersion: 1,
|
|
3067
3811
|
migrate: {},
|
|
@@ -3079,16 +3823,16 @@ var init_parameter = __esm({
|
|
|
3079
3823
|
render: () => null
|
|
3080
3824
|
// Không render lên board
|
|
3081
3825
|
};
|
|
3082
|
-
registerKind(
|
|
3826
|
+
registerKind(def18);
|
|
3083
3827
|
}
|
|
3084
3828
|
});
|
|
3085
3829
|
|
|
3086
3830
|
// src/core/scene/kinds/pointOnCurve.ts
|
|
3087
|
-
var
|
|
3831
|
+
var def19;
|
|
3088
3832
|
var init_pointOnCurve = __esm({
|
|
3089
3833
|
"src/core/scene/kinds/pointOnCurve.ts"() {
|
|
3090
3834
|
init_registry();
|
|
3091
|
-
|
|
3835
|
+
def19 = {
|
|
3092
3836
|
type: "pointOnCurve",
|
|
3093
3837
|
schemaVersion: 1,
|
|
3094
3838
|
migrate: {},
|
|
@@ -3115,16 +3859,16 @@ var init_pointOnCurve = __esm({
|
|
|
3115
3859
|
});
|
|
3116
3860
|
}
|
|
3117
3861
|
};
|
|
3118
|
-
registerKind(
|
|
3862
|
+
registerKind(def19);
|
|
3119
3863
|
}
|
|
3120
3864
|
});
|
|
3121
3865
|
|
|
3122
3866
|
// src/core/scene/kinds/tangent2d.ts
|
|
3123
|
-
var
|
|
3867
|
+
var def20;
|
|
3124
3868
|
var init_tangent2d = __esm({
|
|
3125
3869
|
"src/core/scene/kinds/tangent2d.ts"() {
|
|
3126
3870
|
init_registry();
|
|
3127
|
-
|
|
3871
|
+
def20 = {
|
|
3128
3872
|
type: "tangent2d",
|
|
3129
3873
|
schemaVersion: 1,
|
|
3130
3874
|
migrate: {},
|
|
@@ -3147,7 +3891,7 @@ var init_tangent2d = __esm({
|
|
|
3147
3891
|
});
|
|
3148
3892
|
}
|
|
3149
3893
|
};
|
|
3150
|
-
registerKind(
|
|
3894
|
+
registerKind(def20);
|
|
3151
3895
|
}
|
|
3152
3896
|
});
|
|
3153
3897
|
|
|
@@ -3223,13 +3967,13 @@ var init_evaluator = __esm({
|
|
|
3223
3967
|
});
|
|
3224
3968
|
|
|
3225
3969
|
// src/core/scene/kinds/extremum2d.ts
|
|
3226
|
-
var
|
|
3970
|
+
var def21;
|
|
3227
3971
|
var init_extremum2d = __esm({
|
|
3228
3972
|
"src/core/scene/kinds/extremum2d.ts"() {
|
|
3229
3973
|
init_registry();
|
|
3230
3974
|
init_evaluator();
|
|
3231
3975
|
init_parser();
|
|
3232
|
-
|
|
3976
|
+
def21 = {
|
|
3233
3977
|
type: "extremum2d",
|
|
3234
3978
|
schemaVersion: 1,
|
|
3235
3979
|
migrate: {},
|
|
@@ -3262,18 +4006,18 @@ var init_extremum2d = __esm({
|
|
|
3262
4006
|
}));
|
|
3263
4007
|
}
|
|
3264
4008
|
};
|
|
3265
|
-
registerKind(
|
|
4009
|
+
registerKind(def21);
|
|
3266
4010
|
}
|
|
3267
4011
|
});
|
|
3268
4012
|
|
|
3269
4013
|
// src/core/scene/kinds/root2d.ts
|
|
3270
|
-
var
|
|
4014
|
+
var def22;
|
|
3271
4015
|
var init_root2d = __esm({
|
|
3272
4016
|
"src/core/scene/kinds/root2d.ts"() {
|
|
3273
4017
|
init_registry();
|
|
3274
4018
|
init_evaluator();
|
|
3275
4019
|
init_parser();
|
|
3276
|
-
|
|
4020
|
+
def22 = {
|
|
3277
4021
|
type: "root2d",
|
|
3278
4022
|
schemaVersion: 1,
|
|
3279
4023
|
migrate: {},
|
|
@@ -3303,16 +4047,16 @@ var init_root2d = __esm({
|
|
|
3303
4047
|
}));
|
|
3304
4048
|
}
|
|
3305
4049
|
};
|
|
3306
|
-
registerKind(
|
|
4050
|
+
registerKind(def22);
|
|
3307
4051
|
}
|
|
3308
4052
|
});
|
|
3309
4053
|
|
|
3310
4054
|
// src/core/scene/kinds/slope2d.ts
|
|
3311
|
-
var
|
|
4055
|
+
var def23;
|
|
3312
4056
|
var init_slope2d = __esm({
|
|
3313
4057
|
"src/core/scene/kinds/slope2d.ts"() {
|
|
3314
4058
|
init_registry();
|
|
3315
|
-
|
|
4059
|
+
def23 = {
|
|
3316
4060
|
type: "slope2d",
|
|
3317
4061
|
schemaVersion: 1,
|
|
3318
4062
|
migrate: {},
|
|
@@ -3336,7 +4080,7 @@ var init_slope2d = __esm({
|
|
|
3336
4080
|
});
|
|
3337
4081
|
}
|
|
3338
4082
|
};
|
|
3339
|
-
registerKind(
|
|
4083
|
+
registerKind(def23);
|
|
3340
4084
|
}
|
|
3341
4085
|
});
|
|
3342
4086
|
|
|
@@ -3478,6 +4222,7 @@ var init_JxgRenderer = __esm({
|
|
|
3478
4222
|
init_registry();
|
|
3479
4223
|
init_types2d();
|
|
3480
4224
|
init_parser();
|
|
4225
|
+
init_label();
|
|
3481
4226
|
JxgRenderer = class {
|
|
3482
4227
|
constructor(store, board, options = {}) {
|
|
3483
4228
|
this.elements = /* @__PURE__ */ new Map();
|
|
@@ -3541,11 +4286,12 @@ var init_JxgRenderer = __esm({
|
|
|
3541
4286
|
}
|
|
3542
4287
|
create(obj) {
|
|
3543
4288
|
try {
|
|
3544
|
-
const
|
|
3545
|
-
const el =
|
|
4289
|
+
const def24 = getKind(obj.kind);
|
|
4290
|
+
const el = def24.render(obj, this.ctx());
|
|
3546
4291
|
this.elements.set(obj.id, el);
|
|
3547
4292
|
this.attachFreePointDragSync(obj, el);
|
|
3548
4293
|
this.attachGliderDragSync(obj, el);
|
|
4294
|
+
this.attachLabelDragSync(obj, el);
|
|
3549
4295
|
} catch (err) {
|
|
3550
4296
|
console.warn(`[scene/render/2d] kh\xF4ng render \u0111\u01B0\u1EE3c ${obj.kind} id="${obj.id}":`, err);
|
|
3551
4297
|
}
|
|
@@ -3660,6 +4406,54 @@ var init_JxgRenderer = __esm({
|
|
|
3660
4406
|
});
|
|
3661
4407
|
});
|
|
3662
4408
|
}
|
|
4409
|
+
/**
|
|
4410
|
+
* Cho phép kéo NHÃN của bất kỳ object nào có label (point/line/segment/
|
|
4411
|
+
* circle...). Khi user kéo nhãn, JSXGraph cộng dồn vào label.relativeCoords
|
|
4412
|
+
* (drag-delta screen px) nhưng KHÔNG đổi attribute offset → vị trí cuối =
|
|
4413
|
+
* offset + relativeCoords. Ta gộp lại thành offset thuần (readLabelOffset),
|
|
4414
|
+
* zero relativeCoords để khỏi double-count khi update-hook/recreate áp lại
|
|
4415
|
+
* offset, rồi dispatch UPDATE_ATTRS { labelOffset }. Right-click → reset.
|
|
4416
|
+
*/
|
|
4417
|
+
attachLabelDragSync(obj, el) {
|
|
4418
|
+
const label = el?.label;
|
|
4419
|
+
if (!label || typeof label.on !== "function") return;
|
|
4420
|
+
const sceneId = obj.id;
|
|
4421
|
+
label.on("up", () => {
|
|
4422
|
+
if (this.disposed) return;
|
|
4423
|
+
const off = readLabelOffset(label);
|
|
4424
|
+
if (!off) return;
|
|
4425
|
+
const cur = this.store.getState().objects[sceneId];
|
|
4426
|
+
if (!cur) return;
|
|
4427
|
+
const prev = cur.attrs.labelOffset;
|
|
4428
|
+
if (prev && prev[0] === off[0] && prev[1] === off[1]) return;
|
|
4429
|
+
try {
|
|
4430
|
+
label.setAttribute({ offset: off });
|
|
4431
|
+
if (label.relativeCoords?.scrCoords) {
|
|
4432
|
+
label.relativeCoords.scrCoords[1] = 0;
|
|
4433
|
+
label.relativeCoords.scrCoords[2] = 0;
|
|
4434
|
+
}
|
|
4435
|
+
} catch {
|
|
4436
|
+
}
|
|
4437
|
+
this.store.dispatch({
|
|
4438
|
+
type: "UPDATE_ATTRS",
|
|
4439
|
+
payload: { id: sceneId, patch: { labelOffset: off } }
|
|
4440
|
+
});
|
|
4441
|
+
});
|
|
4442
|
+
const node = label.rendNode;
|
|
4443
|
+
if (node?.addEventListener) {
|
|
4444
|
+
node.addEventListener("contextmenu", (ev) => {
|
|
4445
|
+
if (this.disposed) return;
|
|
4446
|
+
ev.preventDefault();
|
|
4447
|
+
const c = this.store.getState().objects[sceneId];
|
|
4448
|
+
if (!c) return;
|
|
4449
|
+
if (c.attrs.labelOffset === void 0) return;
|
|
4450
|
+
this.store.dispatch({
|
|
4451
|
+
type: "UPDATE_ATTRS",
|
|
4452
|
+
payload: { id: sceneId, patch: { labelOffset: void 0 } }
|
|
4453
|
+
});
|
|
4454
|
+
});
|
|
4455
|
+
}
|
|
4456
|
+
}
|
|
3663
4457
|
remove(id) {
|
|
3664
4458
|
this.removeHalo(id);
|
|
3665
4459
|
this.selectedIds.delete(id);
|
|
@@ -3697,11 +4491,11 @@ var init_JxgRenderer = __esm({
|
|
|
3697
4491
|
continue;
|
|
3698
4492
|
}
|
|
3699
4493
|
if (Object.is(old, cur)) continue;
|
|
3700
|
-
const
|
|
4494
|
+
const def24 = getKind(cur.kind);
|
|
3701
4495
|
const existing = this.elements.get(id);
|
|
3702
|
-
if (
|
|
4496
|
+
if (def24.update && existing) {
|
|
3703
4497
|
try {
|
|
3704
|
-
|
|
4498
|
+
def24.update(cur, old, this.ctx(), existing);
|
|
3705
4499
|
continue;
|
|
3706
4500
|
} catch (err) {
|
|
3707
4501
|
console.warn(`[scene/render/2d] update fail, recreate:`, err);
|
|
@@ -3796,88 +4590,89 @@ var init_JxgRenderer = __esm({
|
|
|
3796
4590
|
layer: 4,
|
|
3797
4591
|
needsRegularUpdate: true
|
|
3798
4592
|
};
|
|
4593
|
+
const elementClass = el.elementClass;
|
|
4594
|
+
const elType = el.elType;
|
|
4595
|
+
const isPointLike = elementClass === 1 || elType === "point" || elType === "glider" || elType === "intersection";
|
|
4596
|
+
const isPolygonLike = Array.isArray(el.vertices) && el.vertices.length >= 3;
|
|
4597
|
+
const isCircleLike = elementClass === 3 || elType === "circle" || elType === "circumcircle" || elType === "incircle";
|
|
4598
|
+
const isSegment = elType === "segment";
|
|
4599
|
+
const isLineLike = elementClass === 2 || elType === "line" || elType === "arrow" || elType === "ray" || elType === "vector" || elType === "tangent" || elType === "normal" || elType === "parallel" || elType === "perpendicular" || elType === "bisector";
|
|
3799
4600
|
const halos = [];
|
|
3800
4601
|
try {
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
});
|
|
3828
|
-
halos.push(halo);
|
|
3829
|
-
}
|
|
3830
|
-
break;
|
|
3831
|
-
}
|
|
3832
|
-
case "line":
|
|
3833
|
-
case "arrow":
|
|
3834
|
-
case "ray":
|
|
3835
|
-
case "vector":
|
|
3836
|
-
case "tangent":
|
|
3837
|
-
case "normal":
|
|
3838
|
-
case "parallel":
|
|
3839
|
-
case "perpendicular":
|
|
3840
|
-
case "bisector": {
|
|
3841
|
-
if (el.point1 && el.point2) {
|
|
3842
|
-
const halo = board.create("line", [el.point1, el.point2], {
|
|
3843
|
-
...haloBase,
|
|
3844
|
-
strokeWidth: 9
|
|
3845
|
-
});
|
|
3846
|
-
halos.push(halo);
|
|
3847
|
-
}
|
|
3848
|
-
break;
|
|
3849
|
-
}
|
|
3850
|
-
case "circle": {
|
|
3851
|
-
if (el.center && typeof el.Radius === "function") {
|
|
3852
|
-
const halo = board.create("circle", [el.center, () => el.Radius?.() ?? 0], {
|
|
3853
|
-
...haloBase,
|
|
3854
|
-
strokeWidth: 9,
|
|
3855
|
-
fillOpacity: 0
|
|
3856
|
-
});
|
|
3857
|
-
halos.push(halo);
|
|
3858
|
-
}
|
|
3859
|
-
break;
|
|
3860
|
-
}
|
|
3861
|
-
case "polygon": {
|
|
3862
|
-
if (Array.isArray(el.vertices) && el.vertices.length >= 3) {
|
|
3863
|
-
const last = el.vertices.length - 1;
|
|
3864
|
-
const verts = el.vertices[last] === el.vertices[0] ? el.vertices.slice(0, last) : el.vertices.slice();
|
|
3865
|
-
const halo = board.create("polygon", verts, {
|
|
3866
|
-
...haloBase,
|
|
3867
|
-
fillOpacity: 0.2,
|
|
3868
|
-
borders: {
|
|
3869
|
-
strokeColor: SEL_STROKE,
|
|
3870
|
-
strokeWidth: 7,
|
|
3871
|
-
strokeOpacity: 0.55,
|
|
3872
|
-
highlight: false
|
|
3873
|
-
}
|
|
3874
|
-
});
|
|
3875
|
-
halos.push(halo);
|
|
4602
|
+
if (isPointLike) {
|
|
4603
|
+
const baseSize = el.getAttribute?.("size") ?? 4;
|
|
4604
|
+
const halo = board.create("point", [
|
|
4605
|
+
() => el.X?.() ?? 0,
|
|
4606
|
+
() => el.Y?.() ?? 0
|
|
4607
|
+
], {
|
|
4608
|
+
...haloBase,
|
|
4609
|
+
size: baseSize + 6,
|
|
4610
|
+
face: "o",
|
|
4611
|
+
strokeWidth: 2,
|
|
4612
|
+
strokeOpacity: 0.75,
|
|
4613
|
+
fillOpacity: 0.25
|
|
4614
|
+
});
|
|
4615
|
+
halos.push(halo);
|
|
4616
|
+
} else if (isPolygonLike) {
|
|
4617
|
+
const verts = el.vertices;
|
|
4618
|
+
const last = verts.length - 1;
|
|
4619
|
+
const trimmed = verts[last] === verts[0] ? verts.slice(0, last) : verts.slice();
|
|
4620
|
+
const halo = board.create("polygon", trimmed, {
|
|
4621
|
+
...haloBase,
|
|
4622
|
+
fillOpacity: 0.2,
|
|
4623
|
+
borders: {
|
|
4624
|
+
strokeColor: SEL_STROKE,
|
|
4625
|
+
strokeWidth: 7,
|
|
4626
|
+
strokeOpacity: 0.55,
|
|
4627
|
+
highlight: false
|
|
3876
4628
|
}
|
|
3877
|
-
|
|
4629
|
+
});
|
|
4630
|
+
halos.push(halo);
|
|
4631
|
+
} else if (isCircleLike && el.center && typeof el.Radius === "function") {
|
|
4632
|
+
const halo = board.create("circle", [el.center, () => el.Radius?.() ?? 0], {
|
|
4633
|
+
...haloBase,
|
|
4634
|
+
strokeWidth: 9,
|
|
4635
|
+
fillOpacity: 0
|
|
4636
|
+
});
|
|
4637
|
+
halos.push(halo);
|
|
4638
|
+
} else if (isSegment && el.point1 && el.point2) {
|
|
4639
|
+
const halo = board.create("segment", [el.point1, el.point2], {
|
|
4640
|
+
...haloBase,
|
|
4641
|
+
strokeWidth: 9,
|
|
4642
|
+
straightFirst: false,
|
|
4643
|
+
straightLast: false
|
|
4644
|
+
});
|
|
4645
|
+
halos.push(halo);
|
|
4646
|
+
} else if (isLineLike && el.point1 && el.point2) {
|
|
4647
|
+
const halo = board.create("line", [el.point1, el.point2], {
|
|
4648
|
+
...haloBase,
|
|
4649
|
+
strokeWidth: 9
|
|
4650
|
+
});
|
|
4651
|
+
halos.push(halo);
|
|
4652
|
+
} else if (el.center && (el.radiuspoint ?? el.radiusPoint) && (el.anglepoint ?? el.anglePoint) && (elType === "arc" || elType === "semicircle" || elType === "circumcirclearc" || elType === "sector" || elType === "angle")) {
|
|
4653
|
+
const rp = el.radiuspoint ?? el.radiusPoint;
|
|
4654
|
+
const ap = el.anglepoint ?? el.anglePoint;
|
|
4655
|
+
if (elType === "sector") {
|
|
4656
|
+
halos.push(board.create("sector", [el.center, rp, ap], {
|
|
4657
|
+
...haloBase,
|
|
4658
|
+
strokeWidth: 7,
|
|
4659
|
+
fillOpacity: 0.2
|
|
4660
|
+
}));
|
|
4661
|
+
} else if (elType === "angle") {
|
|
4662
|
+
halos.push(board.create("angle", [rp, el.center, ap], {
|
|
4663
|
+
...haloBase,
|
|
4664
|
+
strokeWidth: 7,
|
|
4665
|
+
fillOpacity: 0.2,
|
|
4666
|
+
radius: el.getAttribute?.("radius") ?? 1
|
|
4667
|
+
}));
|
|
4668
|
+
} else {
|
|
4669
|
+
halos.push(board.create("arc", [el.center, rp, ap], {
|
|
4670
|
+
...haloBase,
|
|
4671
|
+
strokeWidth: 9,
|
|
4672
|
+
fillColor: "none",
|
|
4673
|
+
fillOpacity: 0
|
|
4674
|
+
}));
|
|
3878
4675
|
}
|
|
3879
|
-
default:
|
|
3880
|
-
break;
|
|
3881
4676
|
}
|
|
3882
4677
|
} catch (err) {
|
|
3883
4678
|
console.warn("[scene/render/2d] halo create fail:", err);
|
|
@@ -3977,15 +4772,18 @@ function computeAutoFitBbox(points, circles, aspect, padPct = 0.12) {
|
|
|
3977
4772
|
const xs = points.map((p) => p[0]).filter(Number.isFinite);
|
|
3978
4773
|
const ys = points.map((p) => p[1]).filter(Number.isFinite);
|
|
3979
4774
|
let xmin = Infinity, xmax = -Infinity, ymin = Infinity, ymax = -Infinity;
|
|
3980
|
-
let
|
|
4775
|
+
let fullDiag = 0;
|
|
3981
4776
|
if (xs.length >= 2 && ys.length >= 2) {
|
|
3982
4777
|
[xmin, xmax] = robustRange(xs);
|
|
3983
4778
|
[ymin, ymax] = robustRange(ys);
|
|
3984
|
-
|
|
4779
|
+
fullDiag = Math.hypot(
|
|
4780
|
+
Math.max(...xs) - Math.min(...xs),
|
|
4781
|
+
Math.max(...ys) - Math.min(...ys)
|
|
4782
|
+
);
|
|
3985
4783
|
}
|
|
3986
4784
|
for (const c of circles) {
|
|
3987
4785
|
if (!Number.isFinite(c.cx) || !Number.isFinite(c.cy) || !Number.isFinite(c.r)) continue;
|
|
3988
|
-
if (
|
|
4786
|
+
if (fullDiag > 0 && c.r > CIRCLE_MAX_FACTOR * fullDiag) continue;
|
|
3989
4787
|
xmin = Math.min(xmin, c.cx - c.r);
|
|
3990
4788
|
xmax = Math.max(xmax, c.cx + c.r);
|
|
3991
4789
|
ymin = Math.min(ymin, c.cy - c.r);
|
|
@@ -4037,6 +4835,49 @@ var init_autoFitBbox = __esm({
|
|
|
4037
4835
|
}
|
|
4038
4836
|
});
|
|
4039
4837
|
|
|
4838
|
+
// src/stamps/geometry-2d/autoFitBoard.ts
|
|
4839
|
+
function isDefaultBbox(bbox) {
|
|
4840
|
+
const d = DEFAULT_VIEW_2D.bbox;
|
|
4841
|
+
return bbox.length === 4 && bbox[0] === d[0] && bbox[1] === d[1] && bbox[2] === d[2] && bbox[3] === d[3];
|
|
4842
|
+
}
|
|
4843
|
+
function fittedBboxFromBoard(board, aspect) {
|
|
4844
|
+
const objs = board?.objectsList;
|
|
4845
|
+
if (!Array.isArray(objs)) return null;
|
|
4846
|
+
const points = [];
|
|
4847
|
+
const circles = [];
|
|
4848
|
+
for (const raw of objs) {
|
|
4849
|
+
const o = raw;
|
|
4850
|
+
if (o?.elementClass === 1 && typeof o.X === "function" && typeof o.Y === "function") {
|
|
4851
|
+
const x = o.X(), y = o.Y();
|
|
4852
|
+
if (Number.isFinite(x) && Number.isFinite(y)) points.push([x, y]);
|
|
4853
|
+
} else if (o?.elementClass === 3 && o.center?.X && typeof o.Radius === "function") {
|
|
4854
|
+
const cx = o.center.X(), cy = o.center.Y(), r = o.Radius();
|
|
4855
|
+
if (Number.isFinite(cx) && Number.isFinite(cy) && Number.isFinite(r)) {
|
|
4856
|
+
circles.push({ cx, cy, r });
|
|
4857
|
+
}
|
|
4858
|
+
}
|
|
4859
|
+
}
|
|
4860
|
+
const safeAspect = Number.isFinite(aspect) && aspect > 0 ? aspect : 1;
|
|
4861
|
+
return computeAutoFitBbox(points, circles, safeAspect);
|
|
4862
|
+
}
|
|
4863
|
+
function autoFitBoardToContent(board, aspect) {
|
|
4864
|
+
const bbox = fittedBboxFromBoard(board, aspect);
|
|
4865
|
+
if (!bbox) return;
|
|
4866
|
+
const b = board;
|
|
4867
|
+
try {
|
|
4868
|
+
b.setBoundingBox?.(bbox);
|
|
4869
|
+
if (typeof b.update === "function") b.update();
|
|
4870
|
+
if (typeof b.fullUpdate === "function") b.fullUpdate();
|
|
4871
|
+
} catch {
|
|
4872
|
+
}
|
|
4873
|
+
}
|
|
4874
|
+
var init_autoFitBoard = __esm({
|
|
4875
|
+
"src/stamps/geometry-2d/autoFitBoard.ts"() {
|
|
4876
|
+
init_types();
|
|
4877
|
+
init_autoFitBbox();
|
|
4878
|
+
}
|
|
4879
|
+
});
|
|
4880
|
+
|
|
4040
4881
|
// src/stamps/geometry-2d/labelLayout.ts
|
|
4041
4882
|
function radialLabelOffsets(points, R = 14) {
|
|
4042
4883
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -4071,8 +4912,9 @@ function containerDimsForBbox(bbox) {
|
|
|
4071
4912
|
if (!Number.isFinite(w) || !Number.isFinite(h) || w <= 0 || h <= 0) {
|
|
4072
4913
|
return { width: FALLBACK_W, height: FALLBACK_H };
|
|
4073
4914
|
}
|
|
4074
|
-
|
|
4075
|
-
let
|
|
4915
|
+
const pxPerUnit = DEFAULT_VIEW_PX / Math.max(w, h);
|
|
4916
|
+
let width = w * pxPerUnit;
|
|
4917
|
+
let height = h * pxPerUnit;
|
|
4076
4918
|
const maxAxis = Math.max(width, height);
|
|
4077
4919
|
if (maxAxis > MAX_DIM) {
|
|
4078
4920
|
const ratio = MAX_DIM / maxAxis;
|
|
@@ -4087,31 +4929,6 @@ function containerDimsForBbox(bbox) {
|
|
|
4087
4929
|
}
|
|
4088
4930
|
return { width: Math.round(width), height: Math.round(height) };
|
|
4089
4931
|
}
|
|
4090
|
-
function autoFitBboxFromBoard(board, aspect) {
|
|
4091
|
-
const objs = board?.objectsList;
|
|
4092
|
-
if (!Array.isArray(objs)) return;
|
|
4093
|
-
const points = [];
|
|
4094
|
-
const circles = [];
|
|
4095
|
-
for (const o of objs) {
|
|
4096
|
-
if (o?.elementClass === 1 && typeof o.X === "function" && typeof o.Y === "function") {
|
|
4097
|
-
const x = o.X(), y = o.Y();
|
|
4098
|
-
if (Number.isFinite(x) && Number.isFinite(y)) points.push([x, y]);
|
|
4099
|
-
} else if (o?.elementClass === 3 && o.center?.X && typeof o.Radius === "function") {
|
|
4100
|
-
const cx = o.center.X(), cy = o.center.Y(), r = o.Radius();
|
|
4101
|
-
if (Number.isFinite(cx) && Number.isFinite(cy) && Number.isFinite(r)) {
|
|
4102
|
-
circles.push({ cx, cy, r });
|
|
4103
|
-
}
|
|
4104
|
-
}
|
|
4105
|
-
}
|
|
4106
|
-
const bbox = computeAutoFitBbox(points, circles, aspect);
|
|
4107
|
-
if (!bbox) return;
|
|
4108
|
-
try {
|
|
4109
|
-
board.setBoundingBox(bbox);
|
|
4110
|
-
if (typeof board.update === "function") board.update();
|
|
4111
|
-
if (typeof board.fullUpdate === "function") board.fullUpdate();
|
|
4112
|
-
} catch {
|
|
4113
|
-
}
|
|
4114
|
-
}
|
|
4115
4932
|
function applyRadialLabelOffsets(board) {
|
|
4116
4933
|
const objs = board?.objectsList;
|
|
4117
4934
|
if (!Array.isArray(objs)) return;
|
|
@@ -4137,10 +4954,6 @@ function applyRadialLabelOffsets(board) {
|
|
|
4137
4954
|
} catch {
|
|
4138
4955
|
}
|
|
4139
4956
|
}
|
|
4140
|
-
function isDefaultBbox(bbox) {
|
|
4141
|
-
const d = DEFAULT_VIEW_2D.bbox;
|
|
4142
|
-
return bbox.length === 4 && bbox[0] === d[0] && bbox[1] === d[1] && bbox[2] === d[2] && bbox[3] === d[3];
|
|
4143
|
-
}
|
|
4144
4957
|
async function renderGeometrySvgFromState(jsonState) {
|
|
4145
4958
|
const state = deserializeBoard(jsonState);
|
|
4146
4959
|
const view = state.meta.domain === "2d" ? state.meta.view : DEFAULT_VIEW_2D;
|
|
@@ -4175,7 +4988,7 @@ async function renderGeometrySvgFromState(jsonState) {
|
|
|
4175
4988
|
const store = createStore(state);
|
|
4176
4989
|
const renderer = new JxgRenderer(store, board);
|
|
4177
4990
|
if (shouldAutoFit) {
|
|
4178
|
-
|
|
4991
|
+
autoFitBoardToContent(board, dims.width / dims.height);
|
|
4179
4992
|
applyRadialLabelOffsets(board);
|
|
4180
4993
|
}
|
|
4181
4994
|
return renderer;
|
|
@@ -4183,7 +4996,7 @@ async function renderGeometrySvgFromState(jsonState) {
|
|
|
4183
4996
|
});
|
|
4184
4997
|
return svgString;
|
|
4185
4998
|
}
|
|
4186
|
-
var
|
|
4999
|
+
var DEFAULT_VIEW_PX, MIN_DIM, MAX_DIM, FALLBACK_W, FALLBACK_H;
|
|
4187
5000
|
var init_render = __esm({
|
|
4188
5001
|
"src/stamps/geometry-2d/render.ts"() {
|
|
4189
5002
|
init_serialize();
|
|
@@ -4192,9 +5005,9 @@ var init_render = __esm({
|
|
|
4192
5005
|
init_types();
|
|
4193
5006
|
init_JxgRenderer();
|
|
4194
5007
|
init_jxgOffscreenRender();
|
|
4195
|
-
|
|
5008
|
+
init_autoFitBoard();
|
|
4196
5009
|
init_labelLayout();
|
|
4197
|
-
|
|
5010
|
+
DEFAULT_VIEW_PX = 500;
|
|
4198
5011
|
MIN_DIM = 100;
|
|
4199
5012
|
MAX_DIM = 1200;
|
|
4200
5013
|
FALLBACK_W = 400;
|
|
@@ -4833,7 +5646,7 @@ var init_useToolHoverTooltip = __esm({
|
|
|
4833
5646
|
init_types4();
|
|
4834
5647
|
}
|
|
4835
5648
|
});
|
|
4836
|
-
function
|
|
5649
|
+
function normalize2(s) {
|
|
4837
5650
|
return s.toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/đ/g, "d").replace(/Đ/g, "d");
|
|
4838
5651
|
}
|
|
4839
5652
|
function SearchIcon() {
|
|
@@ -4880,12 +5693,12 @@ function ToolGrid(props) {
|
|
|
4880
5693
|
const { tools, groupOrder, groupLabels, activeTool, onToolChange, chord } = props;
|
|
4881
5694
|
const { hover, portalReady, showHover, hideHover } = useToolHoverTooltip();
|
|
4882
5695
|
const [query, setQuery] = React2.useState("");
|
|
4883
|
-
const normalizedQuery = React2.useMemo(() =>
|
|
5696
|
+
const normalizedQuery = React2.useMemo(() => normalize2(query.trim()), [query]);
|
|
4884
5697
|
const filteredTools = React2.useMemo(() => {
|
|
4885
5698
|
if (!normalizedQuery) return tools;
|
|
4886
5699
|
return tools.filter((t) => {
|
|
4887
|
-
if (
|
|
4888
|
-
if (t.hint &&
|
|
5700
|
+
if (normalize2(t.label).includes(normalizedQuery)) return true;
|
|
5701
|
+
if (t.hint && normalize2(t.hint).includes(normalizedQuery)) return true;
|
|
4889
5702
|
return false;
|
|
4890
5703
|
});
|
|
4891
5704
|
}, [tools, normalizedQuery]);
|
|
@@ -5754,41 +6567,48 @@ var init_icons = __esm({
|
|
|
5754
6567
|
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "4", cy: "4", r: "1.7", fill: C_POINT })
|
|
5755
6568
|
] }),
|
|
5756
6569
|
// ===== Nâng cao / kind chưa có icon =====
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
/* @__PURE__ */ jsxRuntime.jsx("
|
|
6570
|
+
// Hệ màu đồng bộ: XANH = input click sẵn, ĐỎ = output suy ra. Toạ độ tính để
|
|
6571
|
+
// điểm nằm ĐÚNG trên đường tròn/cung/tiếp tuyến (không trôi nổi).
|
|
6572
|
+
excenter: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
6573
|
+
/* @__PURE__ */ jsxRuntime.jsx("polygon", { points: "12,3 15,15 9,15", stroke: "currentColor", strokeWidth: "1.4", fill: "none" }),
|
|
6574
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "9", y1: "15", x2: "8", y2: "18.9", stroke: "#94a3b8", strokeWidth: "1", strokeDasharray: "1.6 1.6" }),
|
|
6575
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "15", y1: "15", x2: "16", y2: "18.9", stroke: "#94a3b8", strokeWidth: "1", strokeDasharray: "1.6 1.6" }),
|
|
6576
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "18.9", r: "3.8", stroke: "currentColor", strokeWidth: "1.3" }),
|
|
6577
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "3", r: "1.6", fill: C_POINT }),
|
|
6578
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "9", cy: "15", r: "1.6", fill: C_POINT }),
|
|
6579
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "15", cy: "15", r: "1.6", fill: C_POINT }),
|
|
6580
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "18.9", r: "2.2", fill: C_CONSTRUCT })
|
|
5761
6581
|
] }),
|
|
5762
|
-
tangencyPoint: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", children: [
|
|
5763
|
-
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "
|
|
5764
|
-
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "16.5", y1: "3", x2: "16.5", y2: "
|
|
5765
|
-
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "16.5", cy: "12", r: "2.
|
|
6582
|
+
tangencyPoint: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", strokeLinecap: "round", children: [
|
|
6583
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "9.5", cy: "12", r: "7", stroke: "currentColor", strokeWidth: "1.4" }),
|
|
6584
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "16.5", y1: "3.5", x2: "16.5", y2: "20.5", stroke: "currentColor", strokeWidth: "1.4" }),
|
|
6585
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "16.5", cy: "12", r: "2.3", fill: C_CONSTRUCT })
|
|
5766
6586
|
] }),
|
|
5767
|
-
secondIntersection: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", children: [
|
|
5768
|
-
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "11", cy: "12", r: "6.5", stroke: "currentColor", strokeWidth: "1.
|
|
5769
|
-
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "2", y1: "8", x2: "
|
|
5770
|
-
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "
|
|
5771
|
-
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "
|
|
6587
|
+
secondIntersection: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", strokeLinecap: "round", children: [
|
|
6588
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "11", cy: "12", r: "6.5", stroke: "currentColor", strokeWidth: "1.4" }),
|
|
6589
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "2.4", y1: "8.2", x2: "20.4", y2: "11.4", stroke: "currentColor", strokeWidth: "1.4" }),
|
|
6590
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "5.37", cy: "8.75", r: "1.8", fill: C_POINT }),
|
|
6591
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "17.4", cy: "10.87", r: "2.4", fill: C_CONSTRUCT })
|
|
5772
6592
|
] }),
|
|
5773
6593
|
arcMidpoint: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", strokeLinecap: "round", children: [
|
|
5774
|
-
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 17 A
|
|
5775
|
-
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "4", cy: "17", r: "1.
|
|
5776
|
-
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "20", cy: "17", r: "1.
|
|
5777
|
-
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "
|
|
6594
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 17 A 8.5 8.5 0 0 1 20 17", stroke: "currentColor", strokeWidth: "1.6", fill: "none" }),
|
|
6595
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "4", cy: "17", r: "1.8", fill: C_POINT }),
|
|
6596
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "20", cy: "17", r: "1.8", fill: C_POINT }),
|
|
6597
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "11.4", r: "2.3", fill: C_CONSTRUCT })
|
|
5778
6598
|
] }),
|
|
5779
6599
|
circleIntersection: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", children: [
|
|
5780
|
-
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "9", cy: "12", r: "6", stroke: "currentColor", strokeWidth: "1.
|
|
5781
|
-
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "15", cy: "12", r: "6", stroke: "currentColor", strokeWidth: "1.
|
|
5782
|
-
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "
|
|
5783
|
-
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "
|
|
6600
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "9", cy: "12", r: "6", stroke: "currentColor", strokeWidth: "1.4" }),
|
|
6601
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "15", cy: "12", r: "6", stroke: "currentColor", strokeWidth: "1.4" }),
|
|
6602
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "6.8", r: "2", fill: C_CONSTRUCT }),
|
|
6603
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "17.2", r: "2", fill: C_CONSTRUCT })
|
|
5784
6604
|
] }),
|
|
5785
6605
|
tangentPointExt: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", strokeLinecap: "round", children: [
|
|
5786
|
-
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "
|
|
5787
|
-
/* @__PURE__ */ jsxRuntime.jsx("
|
|
5788
|
-
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "3
|
|
5789
|
-
/* @__PURE__ */ jsxRuntime.jsx("
|
|
5790
|
-
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "
|
|
5791
|
-
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "
|
|
6606
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "13", cy: "12", r: "6", stroke: "currentColor", strokeWidth: "1.4" }),
|
|
6607
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "3", y1: "12", x2: "11.8", y2: "18.6", stroke: "currentColor", strokeWidth: "1.3" }),
|
|
6608
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "3", y1: "12", x2: "11.8", y2: "5.4", stroke: "currentColor", strokeWidth: "1.3" }),
|
|
6609
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "3", cy: "12", r: "2", fill: C_POINT }),
|
|
6610
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "9.4", cy: "16.8", r: "1.8", fill: C_CONSTRUCT }),
|
|
6611
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "9.4", cy: "7.2", r: "1.8", fill: C_CONSTRUCT })
|
|
5792
6612
|
] }),
|
|
5793
6613
|
circleCR: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", children: [
|
|
5794
6614
|
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "7.5", stroke: "currentColor", strokeWidth: "1.3" }),
|
|
@@ -5799,9 +6619,11 @@ var init_icons = __esm({
|
|
|
5799
6619
|
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3 19 L12 4 L21 19 Z", stroke: "currentColor", strokeWidth: "1.3", strokeLinejoin: "round" }),
|
|
5800
6620
|
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "14", r: "4.2", fill: "none", stroke: C_CONSTRUCT, strokeWidth: "1.3" })
|
|
5801
6621
|
] }),
|
|
5802
|
-
excircle: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", children: [
|
|
5803
|
-
/* @__PURE__ */ jsxRuntime.jsx("
|
|
5804
|
-
/* @__PURE__ */ jsxRuntime.jsx("
|
|
6622
|
+
excircle: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
6623
|
+
/* @__PURE__ */ jsxRuntime.jsx("polygon", { points: "12,3 15,15 9,15", stroke: "currentColor", strokeWidth: "1.4", fill: "none" }),
|
|
6624
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "9", y1: "15", x2: "8", y2: "18.9", stroke: "#94a3b8", strokeWidth: "1", strokeDasharray: "1.6 1.6" }),
|
|
6625
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "15", y1: "15", x2: "16", y2: "18.9", stroke: "#94a3b8", strokeWidth: "1", strokeDasharray: "1.6 1.6" }),
|
|
6626
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "18.9", r: "3.8", stroke: C_CONSTRUCT, strokeWidth: "1.5", fill: C_CONSTRUCT, fillOpacity: "0.12" })
|
|
5805
6627
|
] }),
|
|
5806
6628
|
pointOn: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", children: [
|
|
5807
6629
|
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "7.5", stroke: "currentColor", strokeWidth: "1.3" }),
|
|
@@ -6584,8 +7406,8 @@ var init_circles = __esm({
|
|
|
6584
7406
|
const ax = picks[0].X(), ay = picks[0].Y();
|
|
6585
7407
|
const bx = picks[1].X(), by = picks[1].Y();
|
|
6586
7408
|
const cx = picks[2].X(), cy = picks[2].Y();
|
|
6587
|
-
const
|
|
6588
|
-
if (Math.abs(
|
|
7409
|
+
const cross2 = (bx - ax) * (cy - ay) - (by - ay) * (cx - ax);
|
|
7410
|
+
if (Math.abs(cross2) < 1e-6) {
|
|
6589
7411
|
ctx.toast?.("Kh\xF4ng v\u1EBD \u0111\u01B0\u1EE3c cung qua 3 \u0111i\u1EC3m th\u1EB3ng h\xE0ng", {
|
|
6590
7412
|
variant: "warning",
|
|
6591
7413
|
id: "arc3-collinear"
|
|
@@ -7811,11 +8633,11 @@ function buildObjectSnapshot(state, id, anchorScreen) {
|
|
|
7811
8633
|
const obj = state.objects[id];
|
|
7812
8634
|
if (!obj) return null;
|
|
7813
8635
|
const k = obj.kind;
|
|
7814
|
-
if (k !== "point" && k !== "line" && k !== "circle" && k !== "segment" && k !== "ray" && k !== "vector") {
|
|
8636
|
+
if (k !== "point" && k !== "line" && k !== "circle" && k !== "segment" && k !== "ray" && k !== "vector" && k !== "intersection") {
|
|
7815
8637
|
return null;
|
|
7816
8638
|
}
|
|
7817
8639
|
const a = obj.attrs;
|
|
7818
|
-
const jKind = k === "point" ? "point" : k === "circle" ? "circle" : "line";
|
|
8640
|
+
const jKind = k === "point" || k === "intersection" ? "point" : k === "circle" ? "circle" : "line";
|
|
7819
8641
|
return {
|
|
7820
8642
|
id,
|
|
7821
8643
|
kind: jKind,
|
|
@@ -8223,6 +9045,7 @@ var init_MiniBoard = __esm({
|
|
|
8223
9045
|
init_safeJsx();
|
|
8224
9046
|
init_attachJxgWheelZoom();
|
|
8225
9047
|
init_initJxgBoard();
|
|
9048
|
+
init_autoFitBoard();
|
|
8226
9049
|
init_snapshot();
|
|
8227
9050
|
init_previewActions();
|
|
8228
9051
|
init_buildHandle();
|
|
@@ -8462,6 +9285,11 @@ var init_MiniBoard = __esm({
|
|
|
8462
9285
|
rendererRef.current = new JxgRenderer(store, board, {
|
|
8463
9286
|
theme: paletteFor(isDarkRef.current)
|
|
8464
9287
|
});
|
|
9288
|
+
if (isDefaultBbox(initialView.bbox)) {
|
|
9289
|
+
const el = containerRef.current;
|
|
9290
|
+
const aspect = el && el.clientHeight > 0 ? el.clientWidth / el.clientHeight : 1;
|
|
9291
|
+
safeJsx("MiniBoard.autoFit", () => autoFitBoardToContent(board, aspect));
|
|
9292
|
+
}
|
|
8465
9293
|
if (containerRef.current) {
|
|
8466
9294
|
wheelCleanup = attachJxgWheelZoom(containerRef.current, board, "MiniBoard.2d");
|
|
8467
9295
|
}
|
|
@@ -9259,6 +10087,7 @@ function useAiFigure(generator) {
|
|
|
9259
10087
|
const [prompt, setPrompt] = React2.useState("");
|
|
9260
10088
|
const [isLoading, setIsLoading] = React2.useState(false);
|
|
9261
10089
|
const [error, setError] = React2.useState(null);
|
|
10090
|
+
const [notice, setNotice] = React2.useState(null);
|
|
9262
10091
|
const [tokens, setTokens] = React2.useState(0);
|
|
9263
10092
|
const abortRef = React2.useRef(null);
|
|
9264
10093
|
const requestIdRef = React2.useRef(0);
|
|
@@ -9279,6 +10108,7 @@ function useAiFigure(generator) {
|
|
|
9279
10108
|
abortRef.current = controller;
|
|
9280
10109
|
setIsLoading(true);
|
|
9281
10110
|
setError(null);
|
|
10111
|
+
setNotice(null);
|
|
9282
10112
|
setTokens(0);
|
|
9283
10113
|
try {
|
|
9284
10114
|
const generated = await generator(problem, {
|
|
@@ -9292,6 +10122,7 @@ function useAiFigure(generator) {
|
|
|
9292
10122
|
setError(generated.message);
|
|
9293
10123
|
return null;
|
|
9294
10124
|
}
|
|
10125
|
+
if (generated.partial) setNotice(generated.partial.message);
|
|
9295
10126
|
return generated.state;
|
|
9296
10127
|
} catch (caught) {
|
|
9297
10128
|
if (controller.signal.aborted || caught instanceof DOMException && caught.name === "AbortError") {
|
|
@@ -9318,6 +10149,7 @@ function useAiFigure(generator) {
|
|
|
9318
10149
|
setPrompt,
|
|
9319
10150
|
isLoading,
|
|
9320
10151
|
error,
|
|
10152
|
+
notice,
|
|
9321
10153
|
submit,
|
|
9322
10154
|
cancel,
|
|
9323
10155
|
tokens
|
|
@@ -9354,6 +10186,141 @@ var init_tesseract = __esm({
|
|
|
9354
10186
|
}
|
|
9355
10187
|
});
|
|
9356
10188
|
|
|
10189
|
+
// src/stamps/geometry-2d/ai/vision/repairOcrSymbols.ts
|
|
10190
|
+
function repairOcrSymbols(text) {
|
|
10191
|
+
let t = text;
|
|
10192
|
+
t = t.replace(PERP_RE, "$1 \u22A5 $3");
|
|
10193
|
+
t = t.replace(TRI_STRICT_RE, "tam gi\xE1c $1 $2");
|
|
10194
|
+
t = t.replace(TRI_DOUBLE_RE, "tam gi\xE1c A$1");
|
|
10195
|
+
t = t.replace(CIRCLE_O_RE, "(O)");
|
|
10196
|
+
t = t.replace(ELEM_RE, "$1 \u2208 (O)");
|
|
10197
|
+
t = t.replace(INTERSECT_RE, "$1 \u2229 $2 = {");
|
|
10198
|
+
t = t.replace(SQUARE_RE, "$1\xB2");
|
|
10199
|
+
t = t.replace(HORN_O_C_RE, "C");
|
|
10200
|
+
t = t.replace(DUONG_DBAR_RE, "\u0110\u01B0\u1EDDng");
|
|
10201
|
+
t = t.replace(DUONG_LOWER_RE, "\u0111\u01B0\u1EDDng");
|
|
10202
|
+
t = t.replace(DUONG_TRON_RE, "$1tr\xF2n");
|
|
10203
|
+
t = t.replace(FLORIN_F_RE, "F");
|
|
10204
|
+
t = t.replace(TAI_TAI_RE, "t\u1EA1i$1");
|
|
10205
|
+
t = t.replace(TAM_TAM_RE, "t\xE2m$1");
|
|
10206
|
+
t = t.replace(EURO_ELEM_RE, "\u2208");
|
|
10207
|
+
t = t.replace(PARALLEL_RE, "\u2225");
|
|
10208
|
+
t = t.replace(CENT_C_RE, "c");
|
|
10209
|
+
t = t.replace(ETH_D_RE, "D");
|
|
10210
|
+
t = t.replace(SLASH_O_RE, "O");
|
|
10211
|
+
t = t.replace(GOI_RE, "G\u1ECDi");
|
|
10212
|
+
t = t.replace(LAY_RE, "L\u1EA5y");
|
|
10213
|
+
t = t.replace(DIQUA_RE, "\u0111i qua");
|
|
10214
|
+
t = t.replace(VA_VAND_RE, "v\xE0 ");
|
|
10215
|
+
t = t.replace(TREN_RE, "$1r\xEAn");
|
|
10216
|
+
t = t.replace(DAY_RE, "d\xE2y");
|
|
10217
|
+
t = t.replace(CHUNG_MINH_RE, "$1minh");
|
|
10218
|
+
t = t.replace(THANG_HANG_RE, "th\u1EB3ng h\xE0ng");
|
|
10219
|
+
t = t.replace(TINH_RE, "T\xEDnh");
|
|
10220
|
+
t = t.replace(DO_DAI_RE, "\u0111\u1ED9 d\xE0i");
|
|
10221
|
+
t = t.replace(NIM_RE, "n\u1EB1m");
|
|
10222
|
+
t = t.replace(NAM_GIUA_RE, "n\u1EB1m");
|
|
10223
|
+
t = t.replace(ANGLE_Z_RE, "\u2220$1");
|
|
10224
|
+
t = t.replace(DE_PAREN_RE, "(\u0110\u1EC1 ");
|
|
10225
|
+
t = t.replace(DE_VERB_RE, "$1xu\u1EA5t$2");
|
|
10226
|
+
t = t.replace(DE_THI_LOWER_RE, "\u0111\u1EC1");
|
|
10227
|
+
t = t.replace(DI_DONG_RE, "di \u0111\u1ED9ng");
|
|
10228
|
+
t = t.replace(POUND_E_RE, "E");
|
|
10229
|
+
t = t.replace(TRI_SPACE_2_1, "$1$2$3$4");
|
|
10230
|
+
t = t.replace(TRI_SPACE_1_2, "$1$2$3$4");
|
|
10231
|
+
t = t.replace(DIAERESIS_O_RE, "O");
|
|
10232
|
+
t = t.replace(INTERSECT_PAREN_RE, "$1 \u2229 $2$3");
|
|
10233
|
+
return t;
|
|
10234
|
+
}
|
|
10235
|
+
var PT, PERP_RE, TRI_STRICT_RE, TRI_DOUBLE_RE, CIRCLE_O_RE, ELEM_RE, INTERSECT_RE, SQUARE_RE, HORN_O_C_RE, DUONG_DBAR_RE, DUONG_TRON_RE, FLORIN_F_RE, TAI_TAI_RE, TAM_TAM_RE, EURO_ELEM_RE, PARALLEL_RE, CENT_C_RE, ETH_D_RE, SLASH_O_RE, GOI_RE, LAY_RE, DIQUA_RE, VA_VAND_RE, TREN_RE, DUONG_LOWER_RE, DAY_RE, CHUNG_MINH_RE, THANG_HANG_RE, TINH_RE, DO_DAI_RE, NIM_RE, NAM_GIUA_RE, ANGLE_Z_RE, DE_PAREN_RE, DE_VERB_RE, DE_THI_LOWER_RE, DI_DONG_RE, POUND_E_RE, TRI_SPACE_2_1, TRI_SPACE_1_2, DIAERESIS_O_RE, INTERSECT_PAREN_RE;
|
|
10236
|
+
var init_repairOcrSymbols = __esm({
|
|
10237
|
+
"src/stamps/geometry-2d/ai/vision/repairOcrSymbols.ts"() {
|
|
10238
|
+
PT = `[A-Z]{2,3}['\u2032]?`;
|
|
10239
|
+
PERP_RE = new RegExp(
|
|
10240
|
+
`(?<![\\p{L}\\d])(${PT})\\s+([1|L])\\s+(${PT})(?![\\p{L}\\d])`,
|
|
10241
|
+
"gu"
|
|
10242
|
+
);
|
|
10243
|
+
TRI_STRICT_RE = /(?<!giác )(?<!thang )(?<![\p{L}\d])A([A-Z]{3})\s+(đều|cân|nhọn|vuông)(?![\p{L}])/gu;
|
|
10244
|
+
TRI_DOUBLE_RE = /(?<![\p{L}\d])AA([A-Z]{2})(?![A-Z])/gu;
|
|
10245
|
+
CIRCLE_O_RE = /\(0\)/gu;
|
|
10246
|
+
ELEM_RE = /(?<![\p{L}\d])([A-Z](?:\s*,\s*[A-Z])+)\s*e\s*\((?:O|0)\)/gu;
|
|
10247
|
+
INTERSECT_RE = /([A-Z]{2,3})N\s+([A-Z]\S{0,2})\s*=\s*\{/gu;
|
|
10248
|
+
SQUARE_RE = /([A-Z])\?(?=\s*[-=+)])/gu;
|
|
10249
|
+
HORN_O_C_RE = /(?<!\p{Ll})Ơ(?!\p{Ll})/gu;
|
|
10250
|
+
DUONG_DBAR_RE = /D(?:ường|uong)(?= (?:tròn|tron|thẳng|kính|cao|chéo|trung|phân|vuông|nối|gấp|tâm))/gu;
|
|
10251
|
+
DUONG_TRON_RE = /([Đđ]ường\s+)tron(?![\p{L}])/gu;
|
|
10252
|
+
FLORIN_F_RE = /Ƒ/gu;
|
|
10253
|
+
TAI_TAI_RE = /(?<![\p{L}])tai(\s+)(?=[A-Z(]|điểm)/gu;
|
|
10254
|
+
TAM_TAM_RE = /(?<![\p{L}])tam(\s+)(?=đường|\(|đối xứng)/gu;
|
|
10255
|
+
EURO_ELEM_RE = /€/gu;
|
|
10256
|
+
PARALLEL_RE = /\|\|/gu;
|
|
10257
|
+
CENT_C_RE = /¢/gu;
|
|
10258
|
+
ETH_D_RE = /(?<!\p{Ll})Ð(?!\p{Ll})/gu;
|
|
10259
|
+
SLASH_O_RE = /(?<!\p{Ll})Ø(?!\p{Ll})/gu;
|
|
10260
|
+
GOI_RE = /(?<!\p{L})Goi(?!\p{L})/gu;
|
|
10261
|
+
LAY_RE = /(?<!\p{L})Lay(?!\p{L})/gu;
|
|
10262
|
+
DIQUA_RE = /(?<!\p{L})di qua(?!\p{L})/gu;
|
|
10263
|
+
VA_VAND_RE = /(?<!\p{L})va (?=[A-Z(]|dây|đường)/gu;
|
|
10264
|
+
TREN_RE = /([Tt])rén(?![\p{L}])/gu;
|
|
10265
|
+
DUONG_LOWER_RE = /(?<![\p{L}])dường(?= (?:tròn|tron|thẳng|kính|cao|chéo|trung|phân|vuông|nối|gấp|tâm))/gu;
|
|
10266
|
+
DAY_RE = /(?<![\p{L}])day(?=\s+(?:cung(?![\p{L}])|[A-Z]{2}))/gu;
|
|
10267
|
+
CHUNG_MINH_RE = /([Cc]hứng )mình(?![\p{L}])/gu;
|
|
10268
|
+
THANG_HANG_RE = /thẳng hang(?![\p{L}])/gu;
|
|
10269
|
+
TINH_RE = /(?<![\p{L}])Tinh(?= (?:[A-Z]|do(?![\p{L}])|độ|diện|bán|chu|số|giá))/gu;
|
|
10270
|
+
DO_DAI_RE = /(?<![\p{L}])do dai(?![\p{L}])/gu;
|
|
10271
|
+
NIM_RE = /(?<![\p{L}])nim(?= (?:giữa|trên|trong)(?![\p{L}]))/gu;
|
|
10272
|
+
NAM_GIUA_RE = /(?<![\p{L}])năm(?= (?:giữa|trên) [A-Z])/gu;
|
|
10273
|
+
ANGLE_Z_RE = /(?<![\p{L}\d])4?Z([A-Z]{2,3})(?![A-Z])/gu;
|
|
10274
|
+
DE_PAREN_RE = /\(Dé /gu;
|
|
10275
|
+
DE_VERB_RE = /(\(Đề )\S+( bởi)/gu;
|
|
10276
|
+
DE_THI_LOWER_RE = /(?<![\p{L}])dé(?= thi)/gu;
|
|
10277
|
+
DI_DONG_RE = /(?<![\p{L}])di dong(?![\p{L}])/gu;
|
|
10278
|
+
POUND_E_RE = /£/gu;
|
|
10279
|
+
TRI_SPACE_2_1 = /(tam\s*giác\s+)([A-Z])([A-Z])\s+([A-Z])(?![A-Z\p{Ll}])/gu;
|
|
10280
|
+
TRI_SPACE_1_2 = /(tam\s*giác\s+)([A-Z])\s+([A-Z])([A-Z])(?![A-Z\p{Ll}])/gu;
|
|
10281
|
+
DIAERESIS_O_RE = /(?<!\p{Ll})Ö(?!\p{Ll})/gu;
|
|
10282
|
+
INTERSECT_PAREN_RE = /(\([A-Z]{3}\))\s+N\s+([A-Z]{2})(\s*=\s*\{)/gu;
|
|
10283
|
+
}
|
|
10284
|
+
});
|
|
10285
|
+
|
|
10286
|
+
// src/stamps/geometry-2d/ai/vision/detectFormulaRisk.ts
|
|
10287
|
+
function hasManglingLtGt(text) {
|
|
10288
|
+
const re = /[<>]/gu;
|
|
10289
|
+
let m;
|
|
10290
|
+
while ((m = re.exec(text)) !== null) {
|
|
10291
|
+
const prev = text.slice(0, m.index).replace(/\s+$/u, "").slice(-1);
|
|
10292
|
+
const next = text.slice(m.index + 1).replace(/^\s+/u, "").slice(0, 1);
|
|
10293
|
+
const prevOperand = /[A-Za-z0-9]/.test(prev);
|
|
10294
|
+
const nextOperand = /[A-Za-z0-9]/.test(next);
|
|
10295
|
+
if (!(prevOperand && nextOperand)) return true;
|
|
10296
|
+
}
|
|
10297
|
+
return false;
|
|
10298
|
+
}
|
|
10299
|
+
function detectFormulaRisk(text) {
|
|
10300
|
+
const reasons = [];
|
|
10301
|
+
if (hasManglingLtGt(text)) {
|
|
10302
|
+
reasons.push('ph\xE2n s\u1ED1/bi\u1EC3u th\u1EE9c nghi b\u1ECB nu\u1ED1t (k\xFD hi\u1EC7u "<"/">")');
|
|
10303
|
+
}
|
|
10304
|
+
if (SUP_RESIDUE.test(text)) {
|
|
10305
|
+
reasons.push('s\u1ED1 m\u0169 ho\u1EB7c \u0111\u1ED9 nghi b\u1ECB m\u1EA5t (d\u1EA5u "?")');
|
|
10306
|
+
}
|
|
10307
|
+
for (const m of text.matchAll(ANGLE_DEG)) {
|
|
10308
|
+
const tok = m[1];
|
|
10309
|
+
if (/[A-Z]/.test(tok) && /[^\x00-\x7F]/u.test(tok)) {
|
|
10310
|
+
reasons.push("t\xEAn g\xF3c nghi b\u1ECB nh\u1EADn d\u1EA1ng sai (k\xFD t\u1EF1 c\xF3 d\u1EA5u)");
|
|
10311
|
+
break;
|
|
10312
|
+
}
|
|
10313
|
+
}
|
|
10314
|
+
return reasons;
|
|
10315
|
+
}
|
|
10316
|
+
var SUP_RESIDUE, ANGLE_DEG;
|
|
10317
|
+
var init_detectFormulaRisk = __esm({
|
|
10318
|
+
"src/stamps/geometry-2d/ai/vision/detectFormulaRisk.ts"() {
|
|
10319
|
+
SUP_RESIDUE = /[A-Z0-9]\?(?=\s*[-=+)(])/u;
|
|
10320
|
+
ANGLE_DEG = /([\p{L}]{1,6})\s*=\s*\d+\s*°/gu;
|
|
10321
|
+
}
|
|
10322
|
+
});
|
|
10323
|
+
|
|
9357
10324
|
// src/stamps/geometry-2d/ai/vision/extractProblem.ts
|
|
9358
10325
|
async function extractProblemFromImage(image, opts = {}) {
|
|
9359
10326
|
let raw;
|
|
@@ -9381,6 +10348,7 @@ async function extractProblemFromImage(image, opts = {}) {
|
|
|
9381
10348
|
ok: true,
|
|
9382
10349
|
text,
|
|
9383
10350
|
confidence,
|
|
10351
|
+
warnings: detectFormulaRisk(text),
|
|
9384
10352
|
usage: { inputTokens: 0, outputTokens: 0 }
|
|
9385
10353
|
};
|
|
9386
10354
|
}
|
|
@@ -9392,6 +10360,7 @@ function postProcess(raw) {
|
|
|
9392
10360
|
t = t.replace(/```[\s\S]*?```/g, "").replace(/`([^`]+)`/g, "$1");
|
|
9393
10361
|
t = t.replace(/\s+/g, " ").trim();
|
|
9394
10362
|
t = t.normalize("NFC");
|
|
10363
|
+
t = repairOcrSymbols(t);
|
|
9395
10364
|
if (t.length > MAX_TEXT_CHARS) t = t.slice(0, MAX_TEXT_CHARS);
|
|
9396
10365
|
return t;
|
|
9397
10366
|
}
|
|
@@ -9399,6 +10368,8 @@ var MIN_HIGH_CONFIDENCE_CHARS, MAX_TEXT_CHARS, TESSERACT_HIGH_CONFIDENCE_THRESHO
|
|
|
9399
10368
|
var init_extractProblem = __esm({
|
|
9400
10369
|
"src/stamps/geometry-2d/ai/vision/extractProblem.ts"() {
|
|
9401
10370
|
init_tesseract();
|
|
10371
|
+
init_repairOcrSymbols();
|
|
10372
|
+
init_detectFormulaRisk();
|
|
9402
10373
|
MIN_HIGH_CONFIDENCE_CHARS = 10;
|
|
9403
10374
|
MAX_TEXT_CHARS = 2e3;
|
|
9404
10375
|
TESSERACT_HIGH_CONFIDENCE_THRESHOLD = 70;
|
|
@@ -9410,6 +10381,14 @@ async function handleExtractProblem(image, opts = {}) {
|
|
|
9410
10381
|
try {
|
|
9411
10382
|
const r = await extractProblemFromImage(image, opts);
|
|
9412
10383
|
if (r.ok) {
|
|
10384
|
+
if (r.warnings.length > 0) {
|
|
10385
|
+
return {
|
|
10386
|
+
kind: "low-confidence",
|
|
10387
|
+
text: r.text,
|
|
10388
|
+
warning: `C\xF3 th\u1EC3 c\xF3 c\xF4ng th\u1EE9c/k\xFD hi\u1EC7u b\u1ECB nh\u1EADn d\u1EA1ng sai (${r.warnings.join("; ")}). Ki\u1EC3m tra k\u1EF9 tr\u01B0\u1EDBc khi v\u1EBD.`,
|
|
10389
|
+
usage: r.usage
|
|
10390
|
+
};
|
|
10391
|
+
}
|
|
9413
10392
|
if (r.confidence === "low") {
|
|
9414
10393
|
return {
|
|
9415
10394
|
kind: "low-confidence",
|
|
@@ -9474,9 +10453,9 @@ async function fileToImagePart(file) {
|
|
|
9474
10453
|
const bitmap = await createImageBitmap(file);
|
|
9475
10454
|
const { width, height } = bitmap;
|
|
9476
10455
|
const maxEdge = Math.max(width, height);
|
|
9477
|
-
const
|
|
9478
|
-
const targetW = Math.round(width *
|
|
9479
|
-
const targetH = Math.round(height *
|
|
10456
|
+
const scale2 = maxEdge > MAX_EDGE_PX ? MAX_EDGE_PX / maxEdge : 1;
|
|
10457
|
+
const targetW = Math.round(width * scale2);
|
|
10458
|
+
const targetH = Math.round(height * scale2);
|
|
9480
10459
|
const canvas = typeof OffscreenCanvas !== "undefined" ? new OffscreenCanvas(targetW, targetH) : Object.assign(document.createElement("canvas"), { width: targetW, height: targetH });
|
|
9481
10460
|
const ctx = canvas.getContext("2d");
|
|
9482
10461
|
if (!ctx) throw new Error("Kh\xF4ng t\u1EA1o \u0111\u01B0\u1EE3c canvas 2D context");
|
|
@@ -9528,6 +10507,7 @@ function AiFigurePrompt({ generator, onGenerated }) {
|
|
|
9528
10507
|
setPrompt,
|
|
9529
10508
|
isLoading,
|
|
9530
10509
|
error,
|
|
10510
|
+
notice,
|
|
9531
10511
|
submit,
|
|
9532
10512
|
cancel,
|
|
9533
10513
|
tokens
|
|
@@ -9541,6 +10521,10 @@ function AiFigurePrompt({ generator, onGenerated }) {
|
|
|
9541
10521
|
const id = setInterval(() => setElapsed((s) => s + 1), 1e3);
|
|
9542
10522
|
return () => clearInterval(id);
|
|
9543
10523
|
}, [isLoading]);
|
|
10524
|
+
const [noticeDismissed, setNoticeDismissed] = React2.useState(false);
|
|
10525
|
+
React2.useEffect(() => {
|
|
10526
|
+
setNoticeDismissed(false);
|
|
10527
|
+
}, [notice]);
|
|
9544
10528
|
const [image, setImage] = React2.useState(null);
|
|
9545
10529
|
const [ocrLoading, setOcrLoading] = React2.useState(false);
|
|
9546
10530
|
const [ocrError, setOcrError] = React2.useState(null);
|
|
@@ -9756,7 +10740,30 @@ function AiFigurePrompt({ generator, onGenerated }) {
|
|
|
9756
10740
|
}
|
|
9757
10741
|
),
|
|
9758
10742
|
ocrError && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", className: "mt-1 px-1 text-xs text-red-600", children: ocrError }),
|
|
9759
|
-
error && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", className: "mt-1 px-1 text-xs text-red-600", children: error })
|
|
10743
|
+
error && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", className: "mt-1 px-1 text-xs text-red-600", children: error }),
|
|
10744
|
+
notice && !noticeDismissed && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
10745
|
+
"div",
|
|
10746
|
+
{
|
|
10747
|
+
role: "status",
|
|
10748
|
+
"data-testid": "geometry-ai-partial-notice",
|
|
10749
|
+
className: "relative mt-2 whitespace-pre-wrap rounded-lg border border-amber-200 bg-amber-50 py-2 pl-3 pr-8 text-xs leading-relaxed text-amber-800",
|
|
10750
|
+
children: [
|
|
10751
|
+
notice,
|
|
10752
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
10753
|
+
"button",
|
|
10754
|
+
{
|
|
10755
|
+
type: "button",
|
|
10756
|
+
onClick: () => setNoticeDismissed(true),
|
|
10757
|
+
"aria-label": "\u0110\xF3ng th\xF4ng b\xE1o",
|
|
10758
|
+
title: "\u0110\xF3ng th\xF4ng b\xE1o",
|
|
10759
|
+
"data-testid": "geometry-ai-partial-dismiss",
|
|
10760
|
+
className: "absolute right-1 top-1 flex h-5 w-5 items-center justify-center rounded text-amber-500 transition hover:bg-amber-100 hover:text-amber-800",
|
|
10761
|
+
children: "\xD7"
|
|
10762
|
+
}
|
|
10763
|
+
)
|
|
10764
|
+
]
|
|
10765
|
+
}
|
|
10766
|
+
)
|
|
9760
10767
|
] });
|
|
9761
10768
|
}
|
|
9762
10769
|
var ArrowUpIcon, StopIcon, PaperclipIcon;
|
|
@@ -10381,11 +11388,17 @@ async function insertStampImage(api, opts) {
|
|
|
10381
11388
|
const elements = api.getSceneElements();
|
|
10382
11389
|
const editingId = opts.editingElementId ?? null;
|
|
10383
11390
|
if (editingId) {
|
|
11391
|
+
const old = elements.find((e) => e.id === editingId) ;
|
|
11392
|
+
const oldLongest = old ? Math.max(old.width ?? 0, old.height ?? 0) : 0;
|
|
11393
|
+
const newLongest = Math.max(width, height);
|
|
11394
|
+
const scale2 = oldLongest > 0 && newLongest > 0 ? oldLongest / newLongest : 1;
|
|
11395
|
+
const w = width * scale2;
|
|
11396
|
+
const h = height * scale2;
|
|
10384
11397
|
const updated = elements.map(
|
|
10385
|
-
(e) => e.id === editingId ? { ...e, fileId, customData, width, height } : e
|
|
11398
|
+
(e) => e.id === editingId ? { ...e, fileId, customData, width: w, height: h } : e
|
|
10386
11399
|
);
|
|
10387
11400
|
api.updateScene({ elements: updated, appState: clearAppStateAfterInsert() });
|
|
10388
|
-
return { fileId, width, height, elementId: editingId };
|
|
11401
|
+
return { fileId, width: w, height: h, elementId: editingId };
|
|
10389
11402
|
}
|
|
10390
11403
|
const newElement = buildStampImageElement(
|
|
10391
11404
|
api,
|
|
@@ -10539,8 +11552,11 @@ function serializePoint(obj, state) {
|
|
|
10539
11552
|
};
|
|
10540
11553
|
}
|
|
10541
11554
|
case "arcMidpoint": {
|
|
10542
|
-
const
|
|
10543
|
-
|
|
11555
|
+
const containment = c.containing ?? c.notContaining;
|
|
11556
|
+
const baseRefs = [c.circle, c.a, c.b];
|
|
11557
|
+
if (containment) baseRefs.push(containment);
|
|
11558
|
+
const refs = resolveRefs(baseRefs, state);
|
|
11559
|
+
if (!refs) return fail("unresolved-ref", baseRefs.join(","));
|
|
10544
11560
|
return {
|
|
10545
11561
|
ok: true,
|
|
10546
11562
|
entity: {
|
|
@@ -10549,7 +11565,7 @@ function serializePoint(obj, state) {
|
|
|
10549
11565
|
circle: refs[0],
|
|
10550
11566
|
a: refs[1],
|
|
10551
11567
|
b: refs[2],
|
|
10552
|
-
notContaining: refs[3]
|
|
11568
|
+
...c.containing ? { containing: refs[3] } : c.notContaining ? { notContaining: refs[3] } : {}
|
|
10553
11569
|
}
|
|
10554
11570
|
};
|
|
10555
11571
|
}
|
|
@@ -10586,6 +11602,21 @@ function serializePoint(obj, state) {
|
|
|
10586
11602
|
entity: { name: obj.label, kind: "pointAtDistance", from: refs[0], through: refs[1], distance }
|
|
10587
11603
|
};
|
|
10588
11604
|
}
|
|
11605
|
+
case "commonTangentPoint": {
|
|
11606
|
+
const refs = resolveRefs([c.circles[0], c.circles[1]], state);
|
|
11607
|
+
if (!refs) return fail("unresolved-ref", `${c.circles[0]},${c.circles[1]}`);
|
|
11608
|
+
return {
|
|
11609
|
+
ok: true,
|
|
11610
|
+
entity: {
|
|
11611
|
+
name: obj.label,
|
|
11612
|
+
kind: "commonTangentPoint",
|
|
11613
|
+
circles: [refs[0], refs[1]],
|
|
11614
|
+
on: c.on,
|
|
11615
|
+
variant: c.variant,
|
|
11616
|
+
side: c.side
|
|
11617
|
+
}
|
|
11618
|
+
};
|
|
11619
|
+
}
|
|
10589
11620
|
// Out of DSL v1:
|
|
10590
11621
|
case "onAxis":
|
|
10591
11622
|
case "onPolygon":
|
|
@@ -10593,6 +11624,7 @@ function serializePoint(obj, state) {
|
|
|
10593
11624
|
case "onPerpendicular":
|
|
10594
11625
|
case "onPerpBisector":
|
|
10595
11626
|
case "onCircleAroundPoint":
|
|
11627
|
+
case "mixtilinearPoint":
|
|
10596
11628
|
return fail("unsupported-constraint", c.kind);
|
|
10597
11629
|
default: {
|
|
10598
11630
|
return fail("unsupported-constraint");
|
|
@@ -10895,9 +11927,11 @@ function describeEntity(e) {
|
|
|
10895
11927
|
return `${e.name} = \u0111\u01B0\u1EDDng tr\xF2n b\xE0ng ti\u1EBFp ${e.vertices.join("")} \u0111\u1ED1i di\u1EC7n ${e.opposite}`;
|
|
10896
11928
|
// Cụm A
|
|
10897
11929
|
case "arcMidpoint":
|
|
10898
|
-
return `${e.name} = trung \u0111i\u1EC3m cung ${e.a}${e.b} (kh\xF4ng ch\u1EE9a ${e.notContaining}) tr\xEAn ${e.circle}`;
|
|
11930
|
+
return `${e.name} = trung \u0111i\u1EC3m cung ${e.a}${e.b}${e.containing || e.notContaining ? ` (${e.containing ? "ch\u1EE9a" : "kh\xF4ng ch\u1EE9a"} ${e.containing ?? e.notContaining})` : ""} tr\xEAn ${e.circle}`;
|
|
10899
11931
|
case "excenter":
|
|
10900
11932
|
return `${e.name} = t\xE2m b\xE0ng ti\u1EBFp ${e.vertices.join("")} \u0111\u1ED1i di\u1EC7n ${e.opposite}`;
|
|
11933
|
+
case "mixtilinearPoint":
|
|
11934
|
+
return `${e.name} = ${e.which === "center" ? "t\xE2m" : "ti\u1EBFp \u0111i\u1EC3m"} mixtilinear ${e.vertices.join("")}`;
|
|
10901
11935
|
case "reflectPoint":
|
|
10902
11936
|
return `${e.name} = \u0111\u1ED1i x\u1EE9ng ${e.of} qua \u0111i\u1EC3m ${e.through}`;
|
|
10903
11937
|
case "reflectLine":
|
|
@@ -10908,6 +11942,12 @@ function describeEntity(e) {
|
|
|
10908
11942
|
const distStr = d.kind === "circleRadius" ? `r(${d.circle})` : d.kind === "segmentLength" ? `|${d.p1}${d.p2}|` : `${d.value}`;
|
|
10909
11943
|
return `${e.name} = \u0111i\u1EC3m tr\xEAn tia ${e.from}${e.through} c\xE1ch ${e.through} m\u1ED9t kho\u1EA3ng ${distStr}`;
|
|
10910
11944
|
}
|
|
11945
|
+
case "onPerpBisector":
|
|
11946
|
+
return `${e.name} = \u0111i\u1EC3m tr\xEAn trung tr\u1EF1c ${e.p1}${e.p2}`;
|
|
11947
|
+
case "commonTangentPoint": {
|
|
11948
|
+
const v = e.variant === "internal" ? "trong" : "ngo\xE0i";
|
|
11949
|
+
return `${e.name} = ti\u1EBFp \u0111i\u1EC3m ti\u1EBFp tuy\u1EBFn chung ${v} ${e.circles[0]},${e.circles[1]} (tr\xEAn ${e.circles[e.on]})`;
|
|
11950
|
+
}
|
|
10911
11951
|
default: {
|
|
10912
11952
|
return "";
|
|
10913
11953
|
}
|
|
@@ -11030,7 +12070,8 @@ var init_host = __esm({
|
|
|
11030
12070
|
version: 1,
|
|
11031
12071
|
jsonState
|
|
11032
12072
|
}),
|
|
11033
|
-
editingElementId: editingElement?.id ?? null
|
|
12073
|
+
editingElementId: editingElement?.id ?? null,
|
|
12074
|
+
preserveExistingSize: true
|
|
11034
12075
|
});
|
|
11035
12076
|
} catch (err) {
|
|
11036
12077
|
console.error("Geometry insert failed:", err);
|