@xom11/whiteboard 0.25.0 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/dist/{ExcalidrawWithMenus-WENZRYYE.mjs → ExcalidrawWithMenus-2QPPTXJM.mjs} +3 -2
  2. package/dist/ExcalidrawWithMenus-2QPPTXJM.mjs.map +1 -0
  3. package/dist/ai.d.mts +3035 -108
  4. package/dist/ai.d.ts +3035 -108
  5. package/dist/ai.js +6736 -775
  6. package/dist/ai.js.map +1 -1
  7. package/dist/ai.mjs +5110 -578
  8. package/dist/ai.mjs.map +1 -1
  9. package/dist/catalog.json +5 -5
  10. package/dist/{chunk-ESVPQWHX.mjs → chunk-4ETJ4CDY.mjs} +5 -5
  11. package/dist/{chunk-ESVPQWHX.mjs.map → chunk-4ETJ4CDY.mjs.map} +1 -1
  12. package/dist/{chunk-VNCCIV6O.mjs → chunk-AJAHD35N.mjs} +779 -9
  13. package/dist/chunk-AJAHD35N.mjs.map +1 -0
  14. package/dist/chunk-AYJPOHCI.mjs +265 -0
  15. package/dist/chunk-AYJPOHCI.mjs.map +1 -0
  16. package/dist/{chunk-M42TGYT6.mjs → chunk-BNBOIDO5.mjs} +3 -3
  17. package/dist/{chunk-M42TGYT6.mjs.map → chunk-BNBOIDO5.mjs.map} +1 -1
  18. package/dist/{chunk-CJBLJUWG.mjs → chunk-CXHNVYMD.mjs} +4 -4
  19. package/dist/{chunk-CJBLJUWG.mjs.map → chunk-CXHNVYMD.mjs.map} +1 -1
  20. package/dist/{chunk-NDEZJKNY.mjs → chunk-D5JLJ3PT.mjs} +4 -4
  21. package/dist/{chunk-NDEZJKNY.mjs.map → chunk-D5JLJ3PT.mjs.map} +1 -1
  22. package/dist/{chunk-REIJZDVZ.mjs → chunk-D5LWSN2Y.mjs} +942 -195
  23. package/dist/chunk-D5LWSN2Y.mjs.map +1 -0
  24. package/dist/{chunk-I24QOHPU.mjs → chunk-HLAOGXEK.mjs} +3 -3
  25. package/dist/{chunk-I24QOHPU.mjs.map → chunk-HLAOGXEK.mjs.map} +1 -1
  26. package/dist/{chunk-TB4CL25L.mjs → chunk-I3L56GVH.mjs} +206 -66
  27. package/dist/chunk-I3L56GVH.mjs.map +1 -0
  28. package/dist/chunk-J5LGTIGS.mjs +10 -0
  29. package/dist/chunk-J5LGTIGS.mjs.map +1 -0
  30. package/dist/{chunk-ONBCUWVI.mjs → chunk-KYMBUTPO.mjs} +3 -3
  31. package/dist/{chunk-ONBCUWVI.mjs.map → chunk-KYMBUTPO.mjs.map} +1 -1
  32. package/dist/{chunk-YSJOVBCD.mjs → chunk-KZGPSTZI.mjs} +4 -4
  33. package/dist/{chunk-YSJOVBCD.mjs.map → chunk-KZGPSTZI.mjs.map} +1 -1
  34. package/dist/{chunk-SGFJLHHG.mjs → chunk-PPKHCRRE.mjs} +3 -3
  35. package/dist/{chunk-SGFJLHHG.mjs.map → chunk-PPKHCRRE.mjs.map} +1 -1
  36. package/dist/{chunk-AYSFWUPK.mjs → chunk-SZDAS7LK.mjs} +79 -2
  37. package/dist/chunk-SZDAS7LK.mjs.map +1 -0
  38. package/dist/chunk-T3SOHYB2.mjs +851 -0
  39. package/dist/chunk-T3SOHYB2.mjs.map +1 -0
  40. package/dist/geometry-2d.d.mts +1 -1
  41. package/dist/geometry-2d.d.ts +1 -1
  42. package/dist/geometry-2d.js +5389 -1362
  43. package/dist/geometry-2d.js.map +1 -1
  44. package/dist/geometry-2d.mjs +5 -4
  45. package/dist/geometry-3d.d.mts +1 -1
  46. package/dist/geometry-3d.d.ts +1 -1
  47. package/dist/geometry-3d.js +1333 -251
  48. package/dist/geometry-3d.js.map +1 -1
  49. package/dist/geometry-3d.mjs +4 -3
  50. package/dist/graph-2d.d.mts +1 -1
  51. package/dist/graph-2d.d.ts +1 -1
  52. package/dist/graph-2d.js +1499 -340
  53. package/dist/graph-2d.js.map +1 -1
  54. package/dist/graph-2d.mjs +7 -6
  55. package/dist/handleExtractProblem-C-U5KluK.d.mts +158 -0
  56. package/dist/handleExtractProblem-C-U5KluK.d.ts +158 -0
  57. package/dist/{host-L7FMFZUW.mjs → host-HAYCJJ2T.mjs} +1258 -441
  58. package/dist/host-HAYCJJ2T.mjs.map +1 -0
  59. package/dist/{host-QK53UYMD.mjs → host-LTJHAY5A.mjs} +10 -9
  60. package/dist/host-LTJHAY5A.mjs.map +1 -0
  61. package/dist/{host-A64ITWVX.mjs → host-M26FS244.mjs} +6 -5
  62. package/dist/host-M26FS244.mjs.map +1 -0
  63. package/dist/{host-QS2EOTRJ.mjs → host-ZQCDAT6O.mjs} +3 -2
  64. package/dist/host-ZQCDAT6O.mjs.map +1 -0
  65. package/dist/index.d.mts +3 -2
  66. package/dist/index.d.ts +3 -2
  67. package/dist/index.js +5621 -1590
  68. package/dist/index.js.map +1 -1
  69. package/dist/index.mjs +22 -20
  70. package/dist/index.mjs.map +1 -1
  71. package/dist/latex.d.mts +1 -1
  72. package/dist/latex.d.ts +1 -1
  73. package/dist/latex.mjs +2 -1
  74. package/dist/render-ZX2O2IK7.mjs +10 -0
  75. package/dist/{render-3WTY7NZB.mjs.map → render-ZX2O2IK7.mjs.map} +1 -1
  76. package/dist/serialize-C3LSUMSA.mjs +9 -0
  77. package/dist/{serialize-SRJVKYUG.mjs.map → serialize-C3LSUMSA.mjs.map} +1 -1
  78. package/dist/{types-DWRyCa2m.d.ts → types-zc_Pa0mp.d.mts} +105 -0
  79. package/dist/{types-DWRyCa2m.d.mts → types-zc_Pa0mp.d.ts} +105 -0
  80. package/package.json +10 -1
  81. package/dist/ExcalidrawWithMenus-WENZRYYE.mjs.map +0 -1
  82. package/dist/chunk-AYSFWUPK.mjs.map +0 -1
  83. package/dist/chunk-REIJZDVZ.mjs.map +0 -1
  84. package/dist/chunk-TB4CL25L.mjs.map +0 -1
  85. package/dist/chunk-VNCCIV6O.mjs.map +0 -1
  86. package/dist/chunk-VRHWDZ66.mjs +0 -96
  87. package/dist/chunk-VRHWDZ66.mjs.map +0 -1
  88. package/dist/host-A64ITWVX.mjs.map +0 -1
  89. package/dist/host-L7FMFZUW.mjs.map +0 -1
  90. package/dist/host-QK53UYMD.mjs.map +0 -1
  91. package/dist/host-QS2EOTRJ.mjs.map +0 -1
  92. package/dist/render-3WTY7NZB.mjs +0 -9
  93. package/dist/serialize-SRJVKYUG.mjs +0 -8
@@ -547,12 +547,354 @@ function constraintRefs2D(c) {
547
547
  return [c.vertices[0], c.vertices[1], c.vertices[2]];
548
548
  case "orthocenter":
549
549
  return [c.vertices[0], c.vertices[1], c.vertices[2]];
550
+ case "onPerpendicular":
551
+ return [c.through, c.perpToA, c.perpToB];
552
+ case "onPerpBisector":
553
+ return [c.p1, c.p2];
554
+ case "onCircleAroundPoint":
555
+ return [c.center, c.radiusPoint];
556
+ case "tangentPointExt":
557
+ return [c.from, c.circle];
558
+ case "circleIntersection":
559
+ return [c.c1, c.c2];
560
+ case "circleSecondIntersection":
561
+ return [c.c1, c.c2, c.exclude];
562
+ case "secondIntersection":
563
+ return [c.line, c.circle, c.other];
564
+ case "tangencyPoint":
565
+ return [c.circle, c.onLine];
566
+ case "arcMidpoint":
567
+ return [c.circle, c.a, c.b, c.notContaining];
568
+ case "pointAtDistance": {
569
+ const d = c.distance;
570
+ const extra = d.kind === "circleRadius" ? [d.circle] : d.kind === "segmentLength" ? [d.p1, d.p2] : [];
571
+ return [c.from, c.through, ...extra];
572
+ }
573
+ case "excenter":
574
+ return [c.vertices[0], c.vertices[1], c.vertices[2]];
550
575
  default:
551
576
  return [];
552
577
  }
553
578
  }
554
579
 
555
- // src/core/scene/kinds/point.ts
580
+ // src/core/scene/kinds/point-constraints/_types.ts
581
+ function definePointConstraint(m) {
582
+ return m;
583
+ }
584
+
585
+ // src/core/scene/kinds/point-constraints/free.ts
586
+ var freeConstraint = definePointConstraint({
587
+ kind: "free",
588
+ describe: (obj) => `\u0110i\u1EC3m ${obj.label}`,
589
+ render: (obj, ctx, c, opts) => {
590
+ const board = ctx.jxg;
591
+ return board.create("point", [c.x, c.y], opts);
592
+ }
593
+ });
594
+
595
+ // src/core/scene/kinds/point-constraints/onAxis.ts
596
+ var onAxisConstraint = definePointConstraint({
597
+ kind: "onAxis",
598
+ describe: (obj, _state, c) => `${obj.label} tr\xEAn tr\u1EE5c ${c.axis}`,
599
+ render: (obj, ctx, c, opts) => {
600
+ const board = ctx.jxg;
601
+ const coords = c.axis === "x" ? [c.t, 0] : [0, c.t];
602
+ return board.create("point", coords, opts);
603
+ }
604
+ });
605
+
606
+ // src/core/scene/kinds/point-constraints/midpoint.ts
607
+ var midpointConstraint = definePointConstraint({
608
+ kind: "midpoint",
609
+ describe: (obj, state, c) => {
610
+ const l1 = state?.objects[c.p1]?.label ?? c.p1;
611
+ const l2 = state?.objects[c.p2]?.label ?? c.p2;
612
+ return `${obj.label} = trung \u0111i\u1EC3m ${l1}${l2}`;
613
+ },
614
+ render: (obj, ctx, c, opts) => {
615
+ const board = ctx.jxg;
616
+ const p1 = ctx.resolveRef(c.p1);
617
+ const p2 = ctx.resolveRef(c.p2);
618
+ return board.create("midpoint", [p1, p2], opts);
619
+ }
620
+ });
621
+
622
+ // src/core/scene/kinds/point-constraints/perpFoot.ts
623
+ var perpFootConstraint = definePointConstraint({
624
+ kind: "perpFoot",
625
+ validate: (c) => {
626
+ if (!c.from || !c.onLine) {
627
+ throw new Error("point.perpFoot: from v\xE0 onLine b\u1EAFt bu\u1ED9c");
628
+ }
629
+ },
630
+ describe: (obj, state, c) => {
631
+ const fromLabel = state?.objects[c.from]?.label ?? c.from;
632
+ const lineLabel = state?.objects[c.onLine]?.label ?? c.onLine;
633
+ return `${obj.label} = ch\xE2n \u27C2 t\u1EEB ${fromLabel} xu\u1ED1ng ${lineLabel}`;
634
+ },
635
+ render: (obj, ctx, c, opts) => {
636
+ const board = ctx.jxg;
637
+ const from = ctx.resolveRef(c.from);
638
+ const onLine = ctx.resolveRef(c.onLine);
639
+ return board.create("perpendicularpoint", [onLine, from], opts);
640
+ }
641
+ });
642
+
643
+ // src/core/scene/kinds/point-constraints/circumcenter.ts
644
+ var circumcenterConstraint = definePointConstraint({
645
+ kind: "circumcenter",
646
+ validate: (c) => {
647
+ if (!Array.isArray(c.vertices) || c.vertices.length !== 3) {
648
+ throw new Error("point.circumcenter: vertices ph\u1EA3i l\xE0 tuple 3 id");
649
+ }
650
+ if (!c.vertices[0] || !c.vertices[1] || !c.vertices[2]) {
651
+ throw new Error("point.circumcenter: 3 vertex id ph\u1EA3i non-empty");
652
+ }
653
+ },
654
+ describe: (obj, state, c) => {
655
+ const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
656
+ return `${obj.label} = t\xE2m ngo\u1EA1i ti\u1EBFp \u0394${labels}`;
657
+ },
658
+ render: (obj, ctx, c, opts) => {
659
+ const board = ctx.jxg;
660
+ const a = ctx.resolveRef(c.vertices[0]);
661
+ const b = ctx.resolveRef(c.vertices[1]);
662
+ const c3 = ctx.resolveRef(c.vertices[2]);
663
+ return board.create("circumcenter", [a, b, c3], opts);
664
+ }
665
+ });
666
+
667
+ // src/core/scene/kinds/point-constraints/incenter.ts
668
+ var incenterConstraint = definePointConstraint({
669
+ kind: "incenter",
670
+ validate: (c) => {
671
+ if (!Array.isArray(c.vertices) || c.vertices.length !== 3) {
672
+ throw new Error("point.incenter: vertices ph\u1EA3i l\xE0 tuple 3 id");
673
+ }
674
+ if (!c.vertices[0] || !c.vertices[1] || !c.vertices[2]) {
675
+ throw new Error("point.incenter: 3 vertex id ph\u1EA3i non-empty");
676
+ }
677
+ },
678
+ describe: (obj, state, c) => {
679
+ const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
680
+ return `${obj.label} = t\xE2m n\u1ED9i ti\u1EBFp \u0394${labels}`;
681
+ },
682
+ render: (obj, ctx, c, opts) => {
683
+ const board = ctx.jxg;
684
+ const a = ctx.resolveRef(c.vertices[0]);
685
+ const b = ctx.resolveRef(c.vertices[1]);
686
+ const c3 = ctx.resolveRef(c.vertices[2]);
687
+ return board.create("incenter", [a, b, c3], opts);
688
+ }
689
+ });
690
+
691
+ // src/core/scene/kinds/point-constraints/onLine.ts
692
+ var onLineConstraint = definePointConstraint({
693
+ kind: "onLine",
694
+ describe: (obj, state, c) => `${obj.label} tr\xEAn \u0111\u01B0\u1EDDng ${state?.objects[c.lineId]?.label ?? c.lineId}`,
695
+ render: (obj, ctx, c, opts) => {
696
+ const board = ctx.jxg;
697
+ const line = ctx.resolveRef(c.lineId);
698
+ const p1 = line.point1;
699
+ const p2 = line.point2;
700
+ const sx = p1 && p2 ? p1.X() + c.t * (p2.X() - p1.X()) : c.t;
701
+ const sy = p1 && p2 ? p1.Y() + c.t * (p2.Y() - p1.Y()) : c.t;
702
+ return board.create("glider", [sx, sy, line], opts);
703
+ }
704
+ });
705
+
706
+ // src/core/scene/kinds/point-constraints/onSegment.ts
707
+ var onSegmentConstraint = definePointConstraint({
708
+ kind: "onSegment",
709
+ describe: (obj, state, c) => `${obj.label} tr\xEAn \u0111o\u1EA1n ${state?.objects[c.segmentId]?.label ?? c.segmentId}`,
710
+ render: (obj, ctx, c, opts) => {
711
+ const board = ctx.jxg;
712
+ const seg = ctx.resolveRef(c.segmentId);
713
+ const p1 = seg.point1;
714
+ const p2 = seg.point2;
715
+ const sx = p1 && p2 ? p1.X() + c.t * (p2.X() - p1.X()) : c.t;
716
+ const sy = p1 && p2 ? p1.Y() + c.t * (p2.Y() - p1.Y()) : c.t;
717
+ return board.create("glider", [sx, sy, seg], opts);
718
+ }
719
+ });
720
+
721
+ // src/core/scene/kinds/point-constraints/onCircle.ts
722
+ var onCircleConstraint = definePointConstraint({
723
+ kind: "onCircle",
724
+ describe: (obj, state, c) => `${obj.label} tr\xEAn \u0111\u01B0\u1EDDng tr\xF2n ${state?.objects[c.circleId]?.label ?? c.circleId}`,
725
+ render: (obj, ctx, c, opts) => {
726
+ const board = ctx.jxg;
727
+ const circle = ctx.resolveRef(c.circleId);
728
+ const O = circle.center ?? circle.midpoint;
729
+ const ox = O ? O.X() : 0;
730
+ const oy = O ? O.Y() : 0;
731
+ return board.create("glider", [ox + Math.cos(c.theta), oy + Math.sin(c.theta), circle], opts);
732
+ }
733
+ });
734
+
735
+ // src/core/scene/kinds/point-constraints/onPolygon.ts
736
+ var onPolygonConstraint = definePointConstraint({
737
+ kind: "onPolygon",
738
+ describe: (obj, state, c) => `${obj.label} tr\xEAn \u0111a gi\xE1c ${state?.objects[c.polygonId]?.label ?? c.polygonId}`,
739
+ render: (obj, ctx, c, opts) => {
740
+ const board = ctx.jxg;
741
+ const poly = ctx.resolveRef(c.polygonId);
742
+ return board.create("glider", [c.u, c.v, poly], opts);
743
+ }
744
+ });
745
+
746
+ // src/core/scene/kinds/point-constraints/centroid.ts
747
+ var centroidConstraint = definePointConstraint({
748
+ kind: "centroid",
749
+ validate: (c) => {
750
+ if (!Array.isArray(c.vertices) || c.vertices.length !== 3) {
751
+ throw new Error("point.centroid: vertices ph\u1EA3i l\xE0 tuple 3 id");
752
+ }
753
+ if (!c.vertices[0] || !c.vertices[1] || !c.vertices[2]) {
754
+ throw new Error("point.centroid: 3 vertex id ph\u1EA3i non-empty");
755
+ }
756
+ },
757
+ describe: (obj, state, c) => {
758
+ const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
759
+ return `${obj.label} = tr\u1ECDng t\xE2m \u0394${labels}`;
760
+ },
761
+ render: (obj, ctx, c, opts) => {
762
+ const board = ctx.jxg;
763
+ const a = ctx.resolveRef(c.vertices[0]);
764
+ const b = ctx.resolveRef(c.vertices[1]);
765
+ const c3 = ctx.resolveRef(c.vertices[2]);
766
+ return board.create("point", [
767
+ () => (a.X() + b.X() + c3.X()) / 3,
768
+ () => (a.Y() + b.Y() + c3.Y()) / 3
769
+ ], opts);
770
+ }
771
+ });
772
+
773
+ // src/core/scene/kinds/pointConstructions.ts
774
+ function dist(p, q) {
775
+ return Math.hypot(p[0] - q[0], p[1] - q[1]);
776
+ }
777
+ function sideOf(a, b, p) {
778
+ return (b[0] - a[0]) * (p[1] - a[1]) - (b[1] - a[1]) * (p[0] - a[0]);
779
+ }
780
+ function arcMidpoint(center, radius, a, b, notContaining) {
781
+ const mcx = (a[0] + b[0]) / 2;
782
+ const mcy = (a[1] + b[1]) / 2;
783
+ let ux = mcx - center[0];
784
+ let uy = mcy - center[1];
785
+ let len = Math.hypot(ux, uy);
786
+ if (len < 1e-9) {
787
+ ux = -(b[1] - a[1]);
788
+ uy = b[0] - a[0];
789
+ len = Math.hypot(ux, uy) || 1;
790
+ }
791
+ ux /= len;
792
+ uy /= len;
793
+ const cand1 = [center[0] + radius * ux, center[1] + radius * uy];
794
+ const cand2 = [center[0] - radius * ux, center[1] - radius * uy];
795
+ const sN = sideOf(a, b, notContaining);
796
+ if (Math.abs(sN) < 1e-9) {
797
+ return dist(cand1, notContaining) >= dist(cand2, notContaining) ? cand1 : cand2;
798
+ }
799
+ const s1 = sideOf(a, b, cand1);
800
+ return s1 * sN < 0 ? cand1 : cand2;
801
+ }
802
+ function excenter(vertices, oppositeIndex) {
803
+ const [A, B, C] = vertices;
804
+ const a = dist(B, C);
805
+ const b = dist(C, A);
806
+ const c = dist(A, B);
807
+ const w = [a, b, c];
808
+ w[oppositeIndex] = -w[oppositeIndex];
809
+ const sum = w[0] + w[1] + w[2];
810
+ if (Math.abs(sum) < 1e-9) return A;
811
+ return [
812
+ (w[0] * A[0] + w[1] * B[0] + w[2] * C[0]) / sum,
813
+ (w[0] * A[1] + w[1] * B[1] + w[2] * C[1]) / sum
814
+ ];
815
+ }
816
+ function pointAtDistanceCoord(from, through, d) {
817
+ const dx = through[0] - from[0];
818
+ const dy = through[1] - from[1];
819
+ const len = Math.hypot(dx, dy) || 1;
820
+ return [through[0] + d * dx / len, through[1] + d * dy / len];
821
+ }
822
+ function radicalAxisFoot(o1, r1, o2, r2) {
823
+ const dx = o2[0] - o1[0], dy = o2[1] - o1[1];
824
+ const d2 = dx * dx + dy * dy;
825
+ if (d2 < 1e-12) return o1;
826
+ const t = (d2 + r1 * r1 - r2 * r2) / (2 * d2);
827
+ return [o1[0] + t * dx, o1[1] + t * dy];
828
+ }
829
+
830
+ // src/core/scene/kinds/point-constraints/arcMidpoint.ts
831
+ var arcMidpointConstraint = definePointConstraint({
832
+ kind: "arcMidpoint",
833
+ validate: (c) => {
834
+ if (!c.circle || !c.a || !c.b || !c.notContaining) {
835
+ throw new Error("point.arcMidpoint: circle, a, b, notContaining b\u1EAFt bu\u1ED9c");
836
+ }
837
+ },
838
+ describe: (obj, state, c) => {
839
+ const al = state?.objects[c.a]?.label ?? c.a;
840
+ const bl = state?.objects[c.b]?.label ?? c.b;
841
+ const nl = state?.objects[c.notContaining]?.label ?? c.notContaining;
842
+ return `${obj.label} = trung \u0111i\u1EC3m cung ${al}${bl} (kh\xF4ng ch\u1EE9a ${nl})`;
843
+ },
844
+ render: (obj, ctx, c, opts) => {
845
+ const board = ctx.jxg;
846
+ const circle = ctx.resolveRef(c.circle);
847
+ const A = ctx.resolveRef(c.a);
848
+ const B = ctx.resolveRef(c.b);
849
+ const N = ctx.resolveRef(c.notContaining);
850
+ const O = circle?.center ?? circle?.midpoint ?? circle;
851
+ const am = () => arcMidpoint(
852
+ [O.X(), O.Y()],
853
+ circle.Radius(),
854
+ [A.X(), A.Y()],
855
+ [B.X(), B.Y()],
856
+ [N.X(), N.Y()]
857
+ );
858
+ return board.create("point", [() => am()[0], () => am()[1]], opts);
859
+ }
860
+ });
861
+
862
+ // src/core/scene/kinds/point-constraints/excenter.ts
863
+ var excenterConstraint = definePointConstraint({
864
+ kind: "excenter",
865
+ validate: (c) => {
866
+ if (!Array.isArray(c.vertices) || c.vertices.length !== 3) {
867
+ throw new Error("point.excenter: vertices ph\u1EA3i l\xE0 tuple 3 id");
868
+ }
869
+ if (!c.opposite) throw new Error("point.excenter: opposite b\u1EAFt bu\u1ED9c");
870
+ if (!c.vertices[0] || !c.vertices[1] || !c.vertices[2]) {
871
+ throw new Error("point.excenter: 3 vertex id ph\u1EA3i non-empty");
872
+ }
873
+ if (!c.vertices.includes(c.opposite)) {
874
+ throw new Error("point.excenter: opposite ph\u1EA3i l\xE0 m\u1ED9t trong vertices");
875
+ }
876
+ },
877
+ describe: (obj, state, c) => {
878
+ const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
879
+ const opp = state?.objects[c.opposite]?.label ?? c.opposite;
880
+ return `${obj.label} = t\xE2m b\xE0ng ti\u1EBFp \u0394${labels} \u0111\u1ED1i di\u1EC7n ${opp}`;
881
+ },
882
+ render: (obj, ctx, c, opts) => {
883
+ const board = ctx.jxg;
884
+ const a = ctx.resolveRef(c.vertices[0]);
885
+ const b = ctx.resolveRef(c.vertices[1]);
886
+ const c3 = ctx.resolveRef(c.vertices[2]);
887
+ const oppIdx = c.vertices.indexOf(c.opposite);
888
+ const idx = oppIdx < 0 ? 0 : oppIdx;
889
+ const ex = () => excenter(
890
+ [[a.X(), a.Y()], [b.X(), b.Y()], [c3.X(), c3.Y()]],
891
+ idx
892
+ );
893
+ return board.create("point", [() => ex()[0], () => ex()[1]], opts);
894
+ }
895
+ });
896
+
897
+ // src/core/scene/kinds/point-constraints/shared.ts
556
898
  function buildJxgTransforms(board, ctx, t) {
557
899
  switch (t.kind) {
558
900
  case "translate":
@@ -579,6 +921,283 @@ function buildJxgTransforms(board, ctx, t) {
579
921
  }
580
922
  }
581
923
  }
924
+ function makeDistanceFn(ctx, d) {
925
+ const scale = d.scale ?? 1;
926
+ const offset = d.offset ?? 0;
927
+ if (d.kind === "literal") {
928
+ const v = d.value;
929
+ return () => scale * v + offset;
930
+ }
931
+ if (d.kind === "segmentLength") {
932
+ const p = ctx.resolveRef(d.p1);
933
+ const q = ctx.resolveRef(d.p2);
934
+ return () => scale * Math.hypot(p.X() - q.X(), p.Y() - q.Y()) + offset;
935
+ }
936
+ const circle = ctx.resolveRef(d.circle);
937
+ return () => scale * circle.Radius() + offset;
938
+ }
939
+ function buildPointOpts(obj) {
940
+ return {
941
+ name: obj.label,
942
+ withLabel: obj.attrs.showLabel ?? true,
943
+ visible: obj.visible,
944
+ fixed: obj.locked,
945
+ strokeColor: obj.attrs.color ?? "#1e40af",
946
+ fillColor: obj.attrs.color ?? "#1e40af",
947
+ face: obj.attrs.face ?? "o",
948
+ size: obj.attrs.size ?? 4
949
+ };
950
+ }
951
+
952
+ // src/core/scene/kinds/point-constraints/pointAtDistance.ts
953
+ var pointAtDistanceConstraint = definePointConstraint({
954
+ kind: "pointAtDistance",
955
+ describe: (obj, state, c) => {
956
+ const fromL = state?.objects[c.from]?.label ?? c.from;
957
+ const thrL = state?.objects[c.through]?.label ?? c.through;
958
+ const d = c.distance;
959
+ const dLabel = d.kind === "literal" ? `${d.value}` : d.kind === "segmentLength" ? `${state?.objects[d.p1]?.label ?? d.p1}${state?.objects[d.p2]?.label ?? d.p2}` : `b\xE1n k\xEDnh (${state?.objects[d.circle]?.label ?? d.circle})`;
960
+ return `${obj.label} = tr\xEAn tia ${fromL}${thrL} k\xE9o d\xE0i, c\xE1ch ${thrL} kho\u1EA3ng ${dLabel}`;
961
+ },
962
+ render: (obj, ctx, c, opts) => {
963
+ const board = ctx.jxg;
964
+ const A = ctx.resolveRef(c.from);
965
+ const B = ctx.resolveRef(c.through);
966
+ const dFn = makeDistanceFn(ctx, c.distance);
967
+ const pc = () => pointAtDistanceCoord([A.X(), A.Y()], [B.X(), B.Y()], dFn());
968
+ return board.create("point", [() => pc()[0], () => pc()[1]], opts);
969
+ }
970
+ });
971
+
972
+ // src/core/scene/kinds/point-constraints/circleIntersection.ts
973
+ var circleIntersectionConstraint = definePointConstraint({
974
+ kind: "circleIntersection",
975
+ // Không có describe-arm riêng trong point.ts → giữ fallback `Điểm ${label}`.
976
+ describe: (obj) => `\u0110i\u1EC3m ${obj.label}`,
977
+ render: (obj, ctx, c, opts) => {
978
+ const board = ctx.jxg;
979
+ const k1 = ctx.resolveRef(c.c1);
980
+ const k2 = ctx.resolveRef(c.c2);
981
+ return board.create("intersection", [k1, k2, c.which], opts);
982
+ }
983
+ });
984
+
985
+ // src/core/scene/kinds/point-constraints/circleSecondIntersection.ts
986
+ var circleSecondIntersectionConstraint = definePointConstraint({
987
+ kind: "circleSecondIntersection",
988
+ // Không có describe-arm riêng trong point.ts → giữ fallback `Điểm ${label}`.
989
+ describe: (obj) => `\u0110i\u1EC3m ${obj.label}`,
990
+ render: (obj, ctx, c, opts) => {
991
+ const board = ctx.jxg;
992
+ const k1 = ctx.resolveRef(c.c1);
993
+ const k2 = ctx.resolveRef(c.c2);
994
+ const ex = ctx.resolveRef(c.exclude);
995
+ return board.create("otherintersection", [k1, k2, ex], opts);
996
+ }
997
+ });
998
+
999
+ // src/core/scene/kinds/point-constraints/secondIntersection.ts
1000
+ var secondIntersectionConstraint = definePointConstraint({
1001
+ kind: "secondIntersection",
1002
+ // Không có describe-arm riêng trong point.ts → giữ fallback `Điểm ${label}`.
1003
+ describe: (obj) => `\u0110i\u1EC3m ${obj.label}`,
1004
+ render: (obj, ctx, c, opts) => {
1005
+ const board = ctx.jxg;
1006
+ const line = ctx.resolveRef(c.line);
1007
+ const circle = ctx.resolveRef(c.circle);
1008
+ const other = ctx.resolveRef(c.other);
1009
+ return board.create("otherintersection", [circle, line, other], opts);
1010
+ }
1011
+ });
1012
+
1013
+ // src/core/scene/kinds/point-constraints/tangencyPoint.ts
1014
+ var tangencyPointConstraint = definePointConstraint({
1015
+ kind: "tangencyPoint",
1016
+ // Không có describe-arm riêng trong point.ts → giữ fallback `Điểm ${label}`.
1017
+ describe: (obj) => `\u0110i\u1EC3m ${obj.label}`,
1018
+ render: (obj, ctx, c, opts) => {
1019
+ const board = ctx.jxg;
1020
+ const circle = ctx.resolveRef(c.circle);
1021
+ const line = ctx.resolveRef(c.onLine);
1022
+ const O = circle?.center ?? circle?.midpoint ?? circle;
1023
+ return board.create("perpendicularpoint", [line, O], opts);
1024
+ }
1025
+ });
1026
+
1027
+ // src/core/scene/kinds/point-constraints/transformed.ts
1028
+ var transformedConstraint = definePointConstraint({
1029
+ kind: "transformed",
1030
+ describe: (obj, state, c) => {
1031
+ const t = c.transform;
1032
+ const labelRef = (id) => state?.objects[id]?.label ?? id;
1033
+ const op = t.kind === "translate" ? `t\u1ECBnh ti\u1EBFn (${t.dx.toFixed(2)}, ${t.dy.toFixed(2)})` : t.kind === "rotate" ? `quay ${(t.angleRad * 180 / Math.PI).toFixed(0)}\xB0 quanh ${labelRef(t.center)}` : t.kind === "reflectLine" ? `\u0111\u1ED1i x\u1EE9ng qua ${labelRef(t.line)}` : t.kind === "reflectPoint" ? `\u0111\u1ED1i x\u1EE9ng qua \u0111i\u1EC3m ${labelRef(t.center)}` : t.kind === "dilate" ? `v\u1ECB t\u1EF1 k=${t.k} quanh ${labelRef(t.center)}` : "";
1034
+ return `${obj.label} = \u1EA3nh c\u1EE7a ${labelRef(c.source)} (${op})`;
1035
+ },
1036
+ render: (obj, ctx, c, opts) => {
1037
+ const board = ctx.jxg;
1038
+ const src = ctx.resolveRef(c.source);
1039
+ const transforms = buildJxgTransforms(board, ctx, c.transform);
1040
+ const parent = transforms.length === 1 ? transforms[0] : transforms;
1041
+ const pt = board.create("point", [src, parent], opts);
1042
+ pt._helpers = transforms;
1043
+ return pt;
1044
+ }
1045
+ });
1046
+
1047
+ // src/core/scene/kinds/point-constraints/orthocenter.ts
1048
+ var orthocenterConstraint = definePointConstraint({
1049
+ kind: "orthocenter",
1050
+ validate: (c) => {
1051
+ if (!Array.isArray(c.vertices) || c.vertices.length !== 3) {
1052
+ throw new Error("point.orthocenter: vertices ph\u1EA3i l\xE0 tuple 3 id");
1053
+ }
1054
+ if (!c.vertices[0] || !c.vertices[1] || !c.vertices[2]) {
1055
+ throw new Error("point.orthocenter: 3 vertex id ph\u1EA3i non-empty");
1056
+ }
1057
+ },
1058
+ describe: (obj, state, c) => {
1059
+ const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
1060
+ return `${obj.label} = tr\u1EF1c t\xE2m \u0394${labels}`;
1061
+ },
1062
+ render: (obj, ctx, c, opts) => {
1063
+ const board = ctx.jxg;
1064
+ const a = ctx.resolveRef(c.vertices[0]);
1065
+ const b = ctx.resolveRef(c.vertices[1]);
1066
+ const c3 = ctx.resolveRef(c.vertices[2]);
1067
+ const hide = { visible: false, withLabel: false, fixed: true, name: "" };
1068
+ const lineBC = board.create("line", [b, c3], hide);
1069
+ const altA = board.create("perpendicular", [lineBC, a], hide);
1070
+ const lineAC = board.create("line", [a, c3], hide);
1071
+ const altB = board.create("perpendicular", [lineAC, b], hide);
1072
+ const ortho = board.create("intersection", [altA, altB, 0], opts);
1073
+ ortho._helpers = [lineBC, altA, lineAC, altB];
1074
+ return ortho;
1075
+ }
1076
+ });
1077
+
1078
+ // src/core/scene/kinds/point-constraints/onPerpendicular.ts
1079
+ var onPerpendicularConstraint = definePointConstraint({
1080
+ kind: "onPerpendicular",
1081
+ describe: (obj) => `\u0110i\u1EC3m ${obj.label}`,
1082
+ render: (obj, ctx, c, opts) => {
1083
+ const board = ctx.jxg;
1084
+ const T = ctx.resolveRef(c.through);
1085
+ const A = ctx.resolveRef(c.perpToA);
1086
+ const B = ctx.resolveRef(c.perpToB);
1087
+ const hide = { visible: false, withLabel: false, fixed: true, name: "" };
1088
+ const refLine = board.create("line", [A, B], hide);
1089
+ const perpLine = board.create("perpendicular", [refLine, T], hide);
1090
+ const dx = B.X() - A.X();
1091
+ const dy = B.Y() - A.Y();
1092
+ const len = Math.hypot(dx, dy) || 1;
1093
+ const ux = -dy / len;
1094
+ const uy = dx / len;
1095
+ const x0 = T.X() + c.t * ux;
1096
+ const y0 = T.Y() + c.t * uy;
1097
+ const gl = board.create("glider", [x0, y0, perpLine], opts);
1098
+ gl._helpers = [refLine, perpLine];
1099
+ return gl;
1100
+ }
1101
+ });
1102
+
1103
+ // src/core/scene/kinds/point-constraints/onPerpBisector.ts
1104
+ var onPerpBisectorConstraint = definePointConstraint({
1105
+ kind: "onPerpBisector",
1106
+ describe: (obj) => `\u0110i\u1EC3m ${obj.label}`,
1107
+ render: (obj, ctx, c, opts) => {
1108
+ const board = ctx.jxg;
1109
+ const A = ctx.resolveRef(c.p1);
1110
+ const B = ctx.resolveRef(c.p2);
1111
+ const hide = { visible: false, withLabel: false, fixed: true, name: "" };
1112
+ const refLine = board.create("line", [A, B], hide);
1113
+ const mid = board.create("midpoint", [A, B], hide);
1114
+ const bisLine = board.create("perpendicular", [refLine, mid], hide);
1115
+ const Mx = (A.X() + B.X()) / 2;
1116
+ const My = (A.Y() + B.Y()) / 2;
1117
+ const dx = B.X() - A.X();
1118
+ const dy = B.Y() - A.Y();
1119
+ const len = Math.hypot(dx, dy) || 1;
1120
+ const ux = -dy / len;
1121
+ const uy = dx / len;
1122
+ const x0 = Mx + c.t * ux;
1123
+ const y0 = My + c.t * uy;
1124
+ const gl = board.create("glider", [x0, y0, bisLine], opts);
1125
+ gl._helpers = [refLine, mid, bisLine];
1126
+ return gl;
1127
+ }
1128
+ });
1129
+
1130
+ // src/core/scene/kinds/point-constraints/onCircleAroundPoint.ts
1131
+ var onCircleAroundPointConstraint = definePointConstraint({
1132
+ kind: "onCircleAroundPoint",
1133
+ describe: (obj) => `\u0110i\u1EC3m ${obj.label}`,
1134
+ render: (obj, ctx, c, opts) => {
1135
+ const board = ctx.jxg;
1136
+ const C = ctx.resolveRef(c.center);
1137
+ const R = ctx.resolveRef(c.radiusPoint);
1138
+ const hide = { visible: false, withLabel: false, fixed: true, name: "" };
1139
+ const auxCircle = board.create("circle", [C, R], hide);
1140
+ const r = Math.hypot(R.X() - C.X(), R.Y() - C.Y());
1141
+ const x0 = C.X() + r * Math.cos(c.theta);
1142
+ const y0 = C.Y() + r * Math.sin(c.theta);
1143
+ const gl = board.create("glider", [x0, y0, auxCircle], opts);
1144
+ gl._helpers = [auxCircle];
1145
+ return gl;
1146
+ }
1147
+ });
1148
+
1149
+ // src/core/scene/kinds/point-constraints/tangentPointExt.ts
1150
+ var tangentPointExtConstraint = definePointConstraint({
1151
+ kind: "tangentPointExt",
1152
+ describe: (obj, state, c) => {
1153
+ const fromLabel = state?.objects[c.from]?.label ?? c.from;
1154
+ const circleLabel = state?.objects[c.circle]?.label ?? c.circle;
1155
+ return `${obj.label} = ti\u1EBFp \u0111i\u1EC3m c\u1EE7a (${circleLabel}) v\u1EDBi ti\u1EBFp tuy\u1EBFn t\u1EEB ${fromLabel}`;
1156
+ },
1157
+ render: (obj, ctx, c, opts) => {
1158
+ const board = ctx.jxg;
1159
+ const P = ctx.resolveRef(c.from);
1160
+ const K = ctx.resolveRef(c.circle);
1161
+ const O = K.center ?? K.midpoint;
1162
+ const hide = { visible: false, withLabel: false, fixed: true, name: "" };
1163
+ const mid = board.create("midpoint", [P, O], hide);
1164
+ const thales = board.create("circle", [mid, P], hide);
1165
+ const inter = board.create("intersection", [thales, K, c.which], opts);
1166
+ inter._helpers = [mid, thales];
1167
+ return inter;
1168
+ }
1169
+ });
1170
+
1171
+ // src/core/scene/kinds/point-constraints/registry.ts
1172
+ var ALL = [
1173
+ freeConstraint,
1174
+ onAxisConstraint,
1175
+ midpointConstraint,
1176
+ perpFootConstraint,
1177
+ circumcenterConstraint,
1178
+ incenterConstraint,
1179
+ onLineConstraint,
1180
+ onSegmentConstraint,
1181
+ onCircleConstraint,
1182
+ onPolygonConstraint,
1183
+ centroidConstraint,
1184
+ arcMidpointConstraint,
1185
+ excenterConstraint,
1186
+ pointAtDistanceConstraint,
1187
+ circleIntersectionConstraint,
1188
+ circleSecondIntersectionConstraint,
1189
+ secondIntersectionConstraint,
1190
+ tangencyPointConstraint,
1191
+ transformedConstraint,
1192
+ orthocenterConstraint,
1193
+ onPerpendicularConstraint,
1194
+ onPerpBisectorConstraint,
1195
+ onCircleAroundPointConstraint,
1196
+ tangentPointExtConstraint
1197
+ ];
1198
+ var POINT_CONSTRAINTS = new Map(ALL.map((m) => [m.kind, m]));
1199
+
1200
+ // src/core/scene/kinds/point.ts
582
1201
  var def3 = {
583
1202
  type: "point",
584
1203
  schemaVersion: 1,
@@ -588,43 +1207,7 @@ var def3 = {
588
1207
  throw new Error("point: constraint required");
589
1208
  }
590
1209
  const c = a.constraint;
591
- if (c.kind === "perpFoot") {
592
- if (!c.from || !c.onLine) {
593
- throw new Error("point.perpFoot: from v\xE0 onLine b\u1EAFt bu\u1ED9c");
594
- }
595
- }
596
- if (c.kind === "circumcenter") {
597
- if (!Array.isArray(c.vertices) || c.vertices.length !== 3) {
598
- throw new Error("point.circumcenter: vertices ph\u1EA3i l\xE0 tuple 3 id");
599
- }
600
- if (!c.vertices[0] || !c.vertices[1] || !c.vertices[2]) {
601
- throw new Error("point.circumcenter: 3 vertex id ph\u1EA3i non-empty");
602
- }
603
- }
604
- if (c.kind === "incenter") {
605
- if (!Array.isArray(c.vertices) || c.vertices.length !== 3) {
606
- throw new Error("point.incenter: vertices ph\u1EA3i l\xE0 tuple 3 id");
607
- }
608
- if (!c.vertices[0] || !c.vertices[1] || !c.vertices[2]) {
609
- throw new Error("point.incenter: 3 vertex id ph\u1EA3i non-empty");
610
- }
611
- }
612
- if (c.kind === "centroid") {
613
- if (!Array.isArray(c.vertices) || c.vertices.length !== 3) {
614
- throw new Error("point.centroid: vertices ph\u1EA3i l\xE0 tuple 3 id");
615
- }
616
- if (!c.vertices[0] || !c.vertices[1] || !c.vertices[2]) {
617
- throw new Error("point.centroid: 3 vertex id ph\u1EA3i non-empty");
618
- }
619
- }
620
- if (c.kind === "orthocenter") {
621
- if (!Array.isArray(c.vertices) || c.vertices.length !== 3) {
622
- throw new Error("point.orthocenter: vertices ph\u1EA3i l\xE0 tuple 3 id");
623
- }
624
- if (!c.vertices[0] || !c.vertices[1] || !c.vertices[2]) {
625
- throw new Error("point.orthocenter: 3 vertex id ph\u1EA3i non-empty");
626
- }
627
- }
1210
+ POINT_CONSTRAINTS.get(c.kind)?.validate?.(c);
628
1211
  },
629
1212
  dependsOn: (a) => constraintRefs2D(a.constraint),
630
1213
  measure: (obj) => {
@@ -639,132 +1222,16 @@ var def3 = {
639
1222
  },
640
1223
  describe: (obj, state) => {
641
1224
  const c = obj.attrs.constraint;
642
- if (c.kind === "free") return `\u0110i\u1EC3m ${obj.label}`;
643
- if (c.kind === "onAxis") return `${obj.label} tr\xEAn tr\u1EE5c ${c.axis}`;
644
- if (c.kind === "onLine") return `${obj.label} tr\xEAn \u0111\u01B0\u1EDDng ${state?.objects[c.lineId]?.label ?? c.lineId}`;
645
- if (c.kind === "onSegment") return `${obj.label} tr\xEAn \u0111o\u1EA1n ${state?.objects[c.segmentId]?.label ?? c.segmentId}`;
646
- if (c.kind === "onCircle") return `${obj.label} tr\xEAn \u0111\u01B0\u1EDDng tr\xF2n ${state?.objects[c.circleId]?.label ?? c.circleId}`;
647
- if (c.kind === "onPolygon") return `${obj.label} tr\xEAn \u0111a gi\xE1c ${state?.objects[c.polygonId]?.label ?? c.polygonId}`;
648
- if (c.kind === "midpoint") {
649
- const l1 = state?.objects[c.p1]?.label ?? c.p1;
650
- const l2 = state?.objects[c.p2]?.label ?? c.p2;
651
- return `${obj.label} = trung \u0111i\u1EC3m ${l1}${l2}`;
652
- }
653
- if (c.kind === "transformed") {
654
- const t = c.transform;
655
- const labelRef = (id) => state?.objects[id]?.label ?? id;
656
- const op = t.kind === "translate" ? `t\u1ECBnh ti\u1EBFn (${t.dx.toFixed(2)}, ${t.dy.toFixed(2)})` : t.kind === "rotate" ? `quay ${(t.angleRad * 180 / Math.PI).toFixed(0)}\xB0 quanh ${labelRef(t.center)}` : t.kind === "reflectLine" ? `\u0111\u1ED1i x\u1EE9ng qua ${labelRef(t.line)}` : t.kind === "reflectPoint" ? `\u0111\u1ED1i x\u1EE9ng qua \u0111i\u1EC3m ${labelRef(t.center)}` : t.kind === "dilate" ? `v\u1ECB t\u1EF1 k=${t.k} quanh ${labelRef(t.center)}` : "";
657
- return `${obj.label} = \u1EA3nh c\u1EE7a ${labelRef(c.source)} (${op})`;
658
- }
659
- if (c.kind === "perpFoot") {
660
- const fromLabel = state?.objects[c.from]?.label ?? c.from;
661
- const lineLabel = state?.objects[c.onLine]?.label ?? c.onLine;
662
- return `${obj.label} = ch\xE2n \u27C2 t\u1EEB ${fromLabel} xu\u1ED1ng ${lineLabel}`;
663
- }
664
- if (c.kind === "circumcenter") {
665
- const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
666
- return `${obj.label} = t\xE2m ngo\u1EA1i ti\u1EBFp \u0394${labels}`;
667
- }
668
- if (c.kind === "incenter") {
669
- const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
670
- return `${obj.label} = t\xE2m n\u1ED9i ti\u1EBFp \u0394${labels}`;
671
- }
672
- if (c.kind === "centroid") {
673
- const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
674
- return `${obj.label} = tr\u1ECDng t\xE2m \u0394${labels}`;
675
- }
676
- if (c.kind === "orthocenter") {
677
- const labels = c.vertices.map((id) => state?.objects[id]?.label ?? id).join("");
678
- return `${obj.label} = tr\u1EF1c t\xE2m \u0394${labels}`;
679
- }
1225
+ const mod = POINT_CONSTRAINTS.get(c.kind);
1226
+ if (mod) return mod.describe(obj, state, c);
680
1227
  return `\u0110i\u1EC3m ${obj.label}`;
681
1228
  },
682
1229
  render: (obj, ctx) => {
683
1230
  const board = ctx.jxg;
684
1231
  const c = obj.attrs.constraint;
685
- const opts = {
686
- name: obj.label,
687
- withLabel: obj.attrs.showLabel ?? true,
688
- visible: obj.visible,
689
- fixed: obj.locked,
690
- strokeColor: obj.attrs.color ?? "#1e40af",
691
- fillColor: obj.attrs.color ?? "#1e40af",
692
- face: obj.attrs.face ?? "o",
693
- size: obj.attrs.size ?? 4
694
- };
695
- if (c.kind === "free") return board.create("point", [c.x, c.y], opts);
696
- if (c.kind === "onAxis") {
697
- const coords = c.axis === "x" ? [c.t, 0] : [0, c.t];
698
- return board.create("point", coords, opts);
699
- }
700
- if (c.kind === "onLine") {
701
- const line = ctx.resolveRef(c.lineId);
702
- return board.create("glider", [c.t, c.t, line], opts);
703
- }
704
- if (c.kind === "onSegment") {
705
- const seg = ctx.resolveRef(c.segmentId);
706
- return board.create("glider", [c.t, c.t, seg], opts);
707
- }
708
- if (c.kind === "onCircle") {
709
- const circle = ctx.resolveRef(c.circleId);
710
- return board.create("glider", [Math.cos(c.theta), Math.sin(c.theta), circle], opts);
711
- }
712
- if (c.kind === "onPolygon") {
713
- const poly = ctx.resolveRef(c.polygonId);
714
- return board.create("glider", [c.u, c.v, poly], opts);
715
- }
716
- if (c.kind === "midpoint") {
717
- const p1 = ctx.resolveRef(c.p1);
718
- const p2 = ctx.resolveRef(c.p2);
719
- return board.create("midpoint", [p1, p2], opts);
720
- }
721
- if (c.kind === "transformed") {
722
- const src = ctx.resolveRef(c.source);
723
- const transforms = buildJxgTransforms(board, ctx, c.transform);
724
- const parent = transforms.length === 1 ? transforms[0] : transforms;
725
- const pt = board.create("point", [src, parent], opts);
726
- pt._helpers = transforms;
727
- return pt;
728
- }
729
- if (c.kind === "perpFoot") {
730
- const from = ctx.resolveRef(c.from);
731
- const onLine = ctx.resolveRef(c.onLine);
732
- return board.create("perpendicularpoint", [onLine, from], opts);
733
- }
734
- if (c.kind === "circumcenter") {
735
- const a = ctx.resolveRef(c.vertices[0]);
736
- const b = ctx.resolveRef(c.vertices[1]);
737
- const c3 = ctx.resolveRef(c.vertices[2]);
738
- return board.create("circumcenter", [a, b, c3], opts);
739
- }
740
- if (c.kind === "incenter") {
741
- const a = ctx.resolveRef(c.vertices[0]);
742
- const b = ctx.resolveRef(c.vertices[1]);
743
- const c3 = ctx.resolveRef(c.vertices[2]);
744
- return board.create("incenter", [a, b, c3], opts);
745
- }
746
- if (c.kind === "centroid") {
747
- const a = ctx.resolveRef(c.vertices[0]);
748
- const b = ctx.resolveRef(c.vertices[1]);
749
- const c3 = ctx.resolveRef(c.vertices[2]);
750
- return board.create("point", [
751
- () => (a.X() + b.X() + c3.X()) / 3,
752
- () => (a.Y() + b.Y() + c3.Y()) / 3
753
- ], opts);
754
- }
755
- if (c.kind === "orthocenter") {
756
- const a = ctx.resolveRef(c.vertices[0]);
757
- const b = ctx.resolveRef(c.vertices[1]);
758
- const c3 = ctx.resolveRef(c.vertices[2]);
759
- const hide = { visible: false, withLabel: false, fixed: true, name: "" };
760
- const lineBC = board.create("line", [b, c3], hide);
761
- const altA = board.create("perpendicular", [lineBC, a], hide);
762
- const lineAC = board.create("line", [a, c3], hide);
763
- const altB = board.create("perpendicular", [lineAC, b], hide);
764
- const ortho = board.create("intersection", [altA, altB, 0], opts);
765
- ortho._helpers = [lineBC, altA, lineAC, altB];
766
- return ortho;
767
- }
1232
+ const opts = buildPointOpts(obj);
1233
+ const mod = POINT_CONSTRAINTS.get(c.kind);
1234
+ if (mod) return mod.render(obj, ctx, c, opts);
768
1235
  return board.create("point", [0, 0], opts);
769
1236
  },
770
1237
  /**
@@ -864,6 +1331,10 @@ function constructionRefs(c) {
864
1331
  return [c.p1, c.vertex, c.p2];
865
1332
  case "angleBisectorLines":
866
1333
  return [stripBorderSuffix(c.line1), stripBorderSuffix(c.line2)];
1334
+ case "lineThrough":
1335
+ return [...c.points];
1336
+ case "radicalAxis":
1337
+ return [c.circle1, c.circle2];
867
1338
  case "tangent":
868
1339
  return [c.throughPoint, c.toCircle];
869
1340
  }
@@ -892,6 +1363,10 @@ var def5 = {
892
1363
  return `${obj.label}: ph\xE2n gi\xE1c g\xF3c ${L(c.p1)}${L(c.vertex)}${L(c.p2)}`;
893
1364
  case "angleBisectorLines":
894
1365
  return `${obj.label}: ph\xE2n gi\xE1c ${L(c.line1)} & ${L(c.line2)} (${c.branch === 0 ? "1" : "2"})`;
1366
+ case "lineThrough":
1367
+ return `${obj.label}: \u0111\u01B0\u1EDDng qua ${c.points.map(L).join("")}`;
1368
+ case "radicalAxis":
1369
+ return `${obj.label}: tr\u1EE5c \u0111\u1EB3ng ph\u01B0\u01A1ng ${L(c.circle1)} & ${L(c.circle2)}`;
895
1370
  case "tangent":
896
1371
  return `${obj.label}: ti\u1EBFp tuy\u1EBFn ${L(c.toCircle)} qua ${L(c.throughPoint)}`;
897
1372
  }
@@ -972,6 +1447,47 @@ var def5 = {
972
1447
  selected._helpers = [other];
973
1448
  return selected;
974
1449
  }
1450
+ case "lineThrough": {
1451
+ const pts = c.points.map((id) => ctx.resolveRef(id));
1452
+ let bi = 0, bj = 1, best = -1;
1453
+ for (let i = 0; i < pts.length; i++) {
1454
+ for (let j = i + 1; j < pts.length; j++) {
1455
+ const dx = pts[i].X() - pts[j].X();
1456
+ const dy = pts[i].Y() - pts[j].Y();
1457
+ const d = dx * dx + dy * dy;
1458
+ if (d > best) {
1459
+ best = d;
1460
+ bi = i;
1461
+ bj = j;
1462
+ }
1463
+ }
1464
+ }
1465
+ return board.create("line", [pts[bi], pts[bj]], {
1466
+ ...baseOpts,
1467
+ straightFirst: true,
1468
+ straightLast: true
1469
+ });
1470
+ }
1471
+ case "radicalAxis": {
1472
+ const k1 = ctx.resolveRef(c.circle1);
1473
+ const k2 = ctx.resolveRef(c.circle2);
1474
+ const o1 = () => [k1.center.X(), k1.center.Y()];
1475
+ const o2 = () => [k2.center.X(), k2.center.Y()];
1476
+ const foot = () => radicalAxisFoot(o1(), k1.Radius(), o2(), k2.Radius());
1477
+ const hide = { visible: false, withLabel: false, fixed: true, name: "" };
1478
+ const f1 = board.create("point", [() => foot()[0], () => foot()[1]], hide);
1479
+ const f2 = board.create("point", [
1480
+ () => foot()[0] - (o2()[1] - o1()[1]),
1481
+ () => foot()[1] + (o2()[0] - o1()[0])
1482
+ ], hide);
1483
+ const line = board.create("line", [f1, f2], {
1484
+ ...baseOpts,
1485
+ straightFirst: true,
1486
+ straightLast: true
1487
+ });
1488
+ line._helpers = [f1, f2];
1489
+ return line;
1490
+ }
975
1491
  case "tangent": {
976
1492
  const through = ctx.resolveRef(c.throughPoint);
977
1493
  const toCircle = ctx.resolveRef(c.toCircle);
@@ -1074,10 +1590,29 @@ var def7 = {
1074
1590
  registerKind(def7);
1075
1591
 
1076
1592
  // src/core/scene/kinds/circle.ts
1593
+ function asConstruction(a) {
1594
+ if (a.construction) return a.construction;
1595
+ const raw = a;
1596
+ if (raw.kind === "incircle" && raw.vertices) {
1597
+ return {
1598
+ kind: "incircle",
1599
+ p1: raw.vertices[0],
1600
+ p2: raw.vertices[1],
1601
+ p3: raw.vertices[2]
1602
+ };
1603
+ }
1604
+ return void 0;
1605
+ }
1077
1606
  function constructionRefs2(c) {
1078
1607
  switch (c.kind) {
1079
1608
  case "circumscribed":
1080
1609
  return [c.p1, c.p2, c.p3];
1610
+ case "incircle":
1611
+ return [c.p1, c.p2, c.p3];
1612
+ case "excircle":
1613
+ return [c.p1, c.p2, c.p3];
1614
+ case "diameter":
1615
+ return [c.p1, c.p2];
1081
1616
  }
1082
1617
  }
1083
1618
  var def8 = {
@@ -1085,14 +1620,27 @@ var def8 = {
1085
1620
  schemaVersion: 1,
1086
1621
  migrate: {},
1087
1622
  validate: (a) => {
1088
- if (a?.construction) return;
1623
+ if (asConstruction(a)) return;
1624
+ if (typeof a?.radius === "number") {
1625
+ if (!a.center) throw new Error("circle: center b\u1EAFt bu\u1ED9c khi d\xF9ng radius");
1626
+ if (!(a.radius > 0)) throw new Error("circle: radius ph\u1EA3i > 0");
1627
+ return;
1628
+ }
1089
1629
  if (!a?.center || !a?.surfacePoint) {
1090
- throw new Error("circle: center v\xE0 surfacePoint b\u1EAFt bu\u1ED9c (ho\u1EB7c construction)");
1630
+ throw new Error("circle: center v\xE0 surfacePoint b\u1EAFt bu\u1ED9c (ho\u1EB7c construction / radius)");
1091
1631
  }
1092
1632
  },
1093
- dependsOn: (a) => a.construction ? constructionRefs2(a.construction) : [a.center, a.surfacePoint],
1633
+ dependsOn: (a) => {
1634
+ const c = asConstruction(a);
1635
+ if (c) return constructionRefs2(c);
1636
+ if (typeof a.radius === "number") return [a.center];
1637
+ return [a.center, a.surfacePoint];
1638
+ },
1094
1639
  measure: (obj, state) => {
1095
- if (obj.attrs.construction) return null;
1640
+ if (asConstruction(obj.attrs)) return null;
1641
+ if (typeof obj.attrs.radius === "number") {
1642
+ return [{ label: "r", value: obj.attrs.radius }];
1643
+ }
1096
1644
  const center = obj.attrs.center ? state.objects[obj.attrs.center] : void 0;
1097
1645
  const surface = obj.attrs.surfacePoint ? state.objects[obj.attrs.surfacePoint] : void 0;
1098
1646
  if (!center || !surface) return null;
@@ -1105,10 +1653,22 @@ var def8 = {
1105
1653
  },
1106
1654
  describe: (obj, state) => {
1107
1655
  const L = (id) => labelOf(id, state);
1108
- const c = obj.attrs.construction;
1656
+ const c = asConstruction(obj.attrs);
1109
1657
  if (c?.kind === "circumscribed") {
1110
1658
  return `\u0110\u01B0\u1EDDng tr\xF2n \u0111i qua ${L(c.p1)}${L(c.p2)}${L(c.p3)}`;
1111
1659
  }
1660
+ if (c?.kind === "incircle") {
1661
+ return `\u0110\u01B0\u1EDDng tr\xF2n n\u1ED9i ti\u1EBFp \u0394${L(c.p1)}${L(c.p2)}${L(c.p3)}`;
1662
+ }
1663
+ if (c?.kind === "excircle") {
1664
+ return `\u0110\u01B0\u1EDDng tr\xF2n b\xE0ng ti\u1EBFp \u0394${L(c.p1)}${L(c.p2)}${L(c.p3)} \u0111\u1ED1i di\u1EC7n ${L(c.opposite)}`;
1665
+ }
1666
+ if (c?.kind === "diameter") {
1667
+ return `\u0110\u01B0\u1EDDng tr\xF2n \u0111\u01B0\u1EDDng k\xEDnh ${L(c.p1)}${L(c.p2)}`;
1668
+ }
1669
+ if (typeof obj.attrs.radius === "number") {
1670
+ return `\u0110\u01B0\u1EDDng tr\xF2n t\xE2m ${L(obj.attrs.center)} b\xE1n k\xEDnh ${obj.attrs.radius}`;
1671
+ }
1112
1672
  return `\u0110\u01B0\u1EDDng tr\xF2n t\xE2m ${L(obj.attrs.center)} b\xE1n k\xEDnh ${L(obj.attrs.center)}${L(obj.attrs.surfacePoint)}`;
1113
1673
  },
1114
1674
  render: (obj, ctx) => {
@@ -1123,13 +1683,66 @@ var def8 = {
1123
1683
  visible: obj.visible,
1124
1684
  fixed: obj.locked
1125
1685
  };
1126
- const c = obj.attrs.construction;
1686
+ const c = asConstruction(obj.attrs);
1127
1687
  if (c?.kind === "circumscribed") {
1128
1688
  const p1 = ctx.resolveRef(c.p1);
1129
1689
  const p2 = ctx.resolveRef(c.p2);
1130
1690
  const p3 = ctx.resolveRef(c.p3);
1131
1691
  return board.create("circumcircle", [p1, p2, p3], baseOpts);
1132
1692
  }
1693
+ if (c?.kind === "incircle") {
1694
+ const p1 = ctx.resolveRef(c.p1);
1695
+ const p2 = ctx.resolveRef(c.p2);
1696
+ const p3 = ctx.resolveRef(c.p3);
1697
+ const center2 = board.create("incenter", [p1, p2, p3], {
1698
+ visible: obj.visible,
1699
+ withLabel: true,
1700
+ fixed: true,
1701
+ name: obj.label
1702
+ });
1703
+ const circ = board.create("incircle", [p1, p2, p3], baseOpts);
1704
+ circ.center = circ.center ?? center2;
1705
+ circ._helpers = [center2];
1706
+ return circ;
1707
+ }
1708
+ if (c?.kind === "excircle") {
1709
+ const P = [ctx.resolveRef(c.p1), ctx.resolveRef(c.p2), ctx.resolveRef(c.p3)];
1710
+ const ids = [c.p1, c.p2, c.p3];
1711
+ const oppIdx = Math.max(0, ids.indexOf(c.opposite));
1712
+ const verts = () => [
1713
+ [P[0].X(), P[0].Y()],
1714
+ [P[1].X(), P[1].Y()],
1715
+ [P[2].X(), P[2].Y()]
1716
+ ];
1717
+ const ctr = () => excenter(verts(), oppIdx);
1718
+ const radius = () => {
1719
+ const I = ctr();
1720
+ const others = [0, 1, 2].filter((i) => i !== oppIdx);
1721
+ const v = verts();
1722
+ const a = v[others[0]];
1723
+ const b = v[others[1]];
1724
+ const dx = b[0] - a[0];
1725
+ const dy = b[1] - a[1];
1726
+ const len = Math.hypot(dx, dy) || 1;
1727
+ return Math.abs((I[0] - a[0]) * dy - (I[1] - a[1]) * dx) / len;
1728
+ };
1729
+ const center2 = board.create("point", [() => ctr()[0], () => ctr()[1]], { visible: false, withLabel: false, fixed: true, name: "" });
1730
+ const circ = board.create("circle", [center2, () => radius()], baseOpts);
1731
+ circ._helpers = [center2];
1732
+ return circ;
1733
+ }
1734
+ if (c?.kind === "diameter") {
1735
+ const p1 = ctx.resolveRef(c.p1);
1736
+ const p2 = ctx.resolveRef(c.p2);
1737
+ const center2 = board.create("midpoint", [p1, p2], { visible: false, withLabel: false, fixed: true, name: "" });
1738
+ const circ = board.create("circle", [center2, p2], baseOpts);
1739
+ circ._helpers = [center2];
1740
+ return circ;
1741
+ }
1742
+ if (typeof obj.attrs.radius === "number") {
1743
+ const center2 = ctx.resolveRef(obj.attrs.center);
1744
+ return board.create("circle", [center2, obj.attrs.radius], baseOpts);
1745
+ }
1133
1746
  const center = ctx.resolveRef(obj.attrs.center);
1134
1747
  const surface = ctx.resolveRef(obj.attrs.surfacePoint);
1135
1748
  return board.create("circle", [center, surface], baseOpts);
@@ -1273,19 +1886,53 @@ function regularVertexLabels(p1Label, p2Label, n) {
1273
1886
  }
1274
1887
  return `${p1Label}${p2Label}\u2026`;
1275
1888
  }
1889
+ function specialShapeName(kind) {
1890
+ switch (kind) {
1891
+ case "square":
1892
+ return "H\xECnh vu\xF4ng";
1893
+ case "rectangle":
1894
+ return "H\xECnh ch\u1EEF nh\u1EADt";
1895
+ case "rhombus":
1896
+ return "H\xECnh thoi";
1897
+ case "parallelogram":
1898
+ return "H\xECnh b\xECnh h\xE0nh";
1899
+ case "isoTrapezoid":
1900
+ return "H\xECnh thang c\xE2n";
1901
+ case "isoTriangle":
1902
+ return "Tam gi\xE1c c\xE2n";
1903
+ case "rightTriangle":
1904
+ return "Tam gi\xE1c vu\xF4ng";
1905
+ case "regular":
1906
+ return "";
1907
+ }
1908
+ }
1276
1909
  var def11 = {
1277
1910
  type: "polygon",
1278
1911
  schemaVersion: 1,
1279
1912
  migrate: {},
1280
1913
  validate: (a) => {
1281
1914
  if (a?.construction) {
1282
- if (a.construction.kind === "regular") {
1283
- if (!a.construction.p1 || !a.construction.p2) {
1284
- throw new Error("polygon (regular): p1 v\xE0 p2 b\u1EAFt bu\u1ED9c");
1285
- }
1286
- if (!Number.isFinite(a.construction.n) || a.construction.n < 3) {
1287
- throw new Error("polygon (regular): n \u2265 3");
1288
- }
1915
+ const c = a.construction;
1916
+ if (c.kind === "regular") {
1917
+ if (!c.p1 || !c.p2) throw new Error("polygon (regular): p1 v\xE0 p2 b\u1EAFt bu\u1ED9c");
1918
+ if (!Number.isFinite(c.n) || c.n < 3) throw new Error("polygon (regular): n \u2265 3");
1919
+ return;
1920
+ }
1921
+ if (c.kind === "square") {
1922
+ if (!c.p1 || !c.p2) throw new Error("polygon (square): p1 v\xE0 p2 b\u1EAFt bu\u1ED9c");
1923
+ return;
1924
+ }
1925
+ if (c.kind === "rectangle" || c.kind === "rhombus" || c.kind === "parallelogram" || c.kind === "isoTrapezoid") {
1926
+ if (!c.p1 || !c.p2 || !c.p3) throw new Error(`polygon (${c.kind}): p1, p2, p3 b\u1EAFt bu\u1ED9c`);
1927
+ return;
1928
+ }
1929
+ if (c.kind === "isoTriangle") {
1930
+ if (!c.base1 || !c.base2 || !c.apex) throw new Error("polygon (isoTriangle): base1, base2, apex b\u1EAFt bu\u1ED9c");
1931
+ return;
1932
+ }
1933
+ if (c.kind === "rightTriangle") {
1934
+ if (!c.rightAngle || !c.leg1End || !c.leg2End) throw new Error("polygon (rightTriangle): rightAngle, leg1End, leg2End b\u1EAFt bu\u1ED9c");
1935
+ return;
1289
1936
  }
1290
1937
  return;
1291
1938
  }
@@ -1294,14 +1941,51 @@ var def11 = {
1294
1941
  }
1295
1942
  },
1296
1943
  dependsOn: (a) => {
1297
- if (a.construction?.kind === "regular") return [a.construction.p1, a.construction.p2];
1298
- return [...a.vertices ?? []];
1944
+ const c = a.construction;
1945
+ if (!c) return [...a.vertices ?? []];
1946
+ switch (c.kind) {
1947
+ case "regular":
1948
+ return [c.p1, c.p2];
1949
+ case "square":
1950
+ return [c.p1, c.p2];
1951
+ case "rectangle":
1952
+ case "rhombus":
1953
+ case "parallelogram":
1954
+ case "isoTrapezoid":
1955
+ return [c.p1, c.p2, c.p3];
1956
+ case "isoTriangle":
1957
+ return [c.base1, c.base2, c.apex];
1958
+ case "rightTriangle":
1959
+ return [c.rightAngle, c.leg1End, c.leg2End];
1960
+ }
1299
1961
  },
1300
1962
  describe: (obj, state) => {
1301
- if (obj.attrs.construction?.kind === "regular") {
1302
- const c = obj.attrs.construction;
1303
- const labels = regularVertexLabels(labelOf(c.p1, state), labelOf(c.p2, state), c.n);
1304
- return `${regularPolygonName(c.n)} ${labels}`;
1963
+ const c = obj.attrs.construction;
1964
+ if (c) {
1965
+ if (c.kind === "regular") {
1966
+ const labels2 = regularVertexLabels(labelOf(c.p1, state), labelOf(c.p2, state), c.n);
1967
+ return `${regularPolygonName(c.n)} ${labels2}`;
1968
+ }
1969
+ const name = specialShapeName(c.kind);
1970
+ let labels = [];
1971
+ switch (c.kind) {
1972
+ case "square":
1973
+ labels = [labelOf(c.p1, state), labelOf(c.p2, state)];
1974
+ break;
1975
+ case "rectangle":
1976
+ case "rhombus":
1977
+ case "parallelogram":
1978
+ case "isoTrapezoid":
1979
+ labels = [labelOf(c.p1, state), labelOf(c.p2, state), labelOf(c.p3, state)];
1980
+ break;
1981
+ case "isoTriangle":
1982
+ labels = [labelOf(c.apex, state), labelOf(c.base1, state), labelOf(c.base2, state)];
1983
+ break;
1984
+ case "rightTriangle":
1985
+ labels = [labelOf(c.rightAngle, state), labelOf(c.leg1End, state), labelOf(c.leg2End, state)];
1986
+ break;
1987
+ }
1988
+ return `${name} ${labels.join("")}`;
1305
1989
  }
1306
1990
  return `\u0110a gi\xE1c ${(obj.attrs.vertices ?? []).map((id) => labelOf(id, state)).join("")}`;
1307
1991
  },
@@ -1309,22 +1993,85 @@ var def11 = {
1309
1993
  const board = ctx.jxg;
1310
1994
  const label = obj.label;
1311
1995
  const showValue = obj.attrs.showValue ?? false;
1312
- if (obj.attrs.construction?.kind === "regular") {
1313
- const c = obj.attrs.construction;
1314
- const p1 = ctx.resolveRef(c.p1);
1315
- const p2 = ctx.resolveRef(c.p2);
1316
- return board.create("regularpolygon", [p1, p2, c.n], {
1317
- name: label,
1318
- withLabel: obj.attrs.showLabel ?? false,
1319
- borders: {
1320
- strokeColor: obj.attrs.color ?? "#0f172a",
1321
- strokeWidth: obj.attrs.width ?? 2
1322
- },
1323
- fillColor: obj.attrs.color ?? "#60a5fa",
1324
- fillOpacity: obj.attrs.fillOpacity ?? 0.15,
1325
- visible: obj.visible,
1326
- fixed: obj.locked
1996
+ const cons = obj.attrs.construction;
1997
+ const commonAttrs = {
1998
+ name: label,
1999
+ withLabel: obj.attrs.showLabel ?? false,
2000
+ borders: {
2001
+ strokeColor: obj.attrs.color ?? "#0f172a",
2002
+ strokeWidth: obj.attrs.width ?? 2
2003
+ },
2004
+ fillColor: obj.attrs.color ?? "#60a5fa",
2005
+ fillOpacity: obj.attrs.fillOpacity ?? 0.15,
2006
+ visible: obj.visible,
2007
+ fixed: obj.locked
2008
+ };
2009
+ if (cons?.kind === "regular") {
2010
+ const p1 = ctx.resolveRef(cons.p1);
2011
+ const p2 = ctx.resolveRef(cons.p2);
2012
+ return board.create("regularpolygon", [p1, p2, cons.n], commonAttrs);
2013
+ }
2014
+ if (cons?.kind === "square") {
2015
+ const p1 = ctx.resolveRef(cons.p1);
2016
+ const p2 = ctx.resolveRef(cons.p2);
2017
+ return board.create("regularpolygon", [p1, p2, 4], commonAttrs);
2018
+ }
2019
+ if (cons?.kind === "rectangle" || cons?.kind === "rhombus" || cons?.kind === "parallelogram") {
2020
+ const A = ctx.resolveRef(cons.p1);
2021
+ const B = ctx.resolveRef(cons.p2);
2022
+ const C = ctx.resolveRef(cons.p3);
2023
+ const D = board.create(
2024
+ "point",
2025
+ [() => A.X() + C.X() - B.X(), () => A.Y() + C.Y() - B.Y()],
2026
+ { visible: false, withLabel: false, fixed: true, name: "" }
2027
+ );
2028
+ const poly2 = board.create("polygon", [A, B, C, D], commonAttrs);
2029
+ poly2._helpers = [D];
2030
+ return poly2;
2031
+ }
2032
+ if (cons?.kind === "isoTrapezoid") {
2033
+ const A = ctx.resolveRef(cons.p1);
2034
+ const B = ctx.resolveRef(cons.p2);
2035
+ const C = ctx.resolveRef(cons.p3);
2036
+ const Dx = () => {
2037
+ const Mx = (A.X() + B.X()) / 2;
2038
+ const My = (A.Y() + B.Y()) / 2;
2039
+ const ux = B.X() - A.X();
2040
+ const uy = B.Y() - A.Y();
2041
+ const len2 = ux * ux + uy * uy || 1;
2042
+ const proj = ((C.X() - Mx) * ux + (C.Y() - My) * uy) / len2;
2043
+ return C.X() - 2 * proj * ux;
2044
+ };
2045
+ const Dy = () => {
2046
+ const Mx = (A.X() + B.X()) / 2;
2047
+ const My = (A.Y() + B.Y()) / 2;
2048
+ const ux = B.X() - A.X();
2049
+ const uy = B.Y() - A.Y();
2050
+ const len2 = ux * ux + uy * uy || 1;
2051
+ const proj = ((C.X() - Mx) * ux + (C.Y() - My) * uy) / len2;
2052
+ return C.Y() - 2 * proj * uy;
2053
+ };
2054
+ const D = board.create("point", [Dx, Dy], {
2055
+ visible: false,
2056
+ withLabel: false,
2057
+ fixed: true,
2058
+ name: ""
1327
2059
  });
2060
+ const poly2 = board.create("polygon", [A, B, C, D], commonAttrs);
2061
+ poly2._helpers = [D];
2062
+ return poly2;
2063
+ }
2064
+ if (cons?.kind === "isoTriangle") {
2065
+ const Apex = ctx.resolveRef(cons.apex);
2066
+ const B1 = ctx.resolveRef(cons.base1);
2067
+ const B2 = ctx.resolveRef(cons.base2);
2068
+ return board.create("polygon", [Apex, B1, B2], commonAttrs);
2069
+ }
2070
+ if (cons?.kind === "rightTriangle") {
2071
+ const R = ctx.resolveRef(cons.rightAngle);
2072
+ const P = ctx.resolveRef(cons.leg1End);
2073
+ const Q = ctx.resolveRef(cons.leg2End);
2074
+ return board.create("polygon", [R, P, Q], commonAttrs);
1328
2075
  }
1329
2076
  const verts = (obj.attrs.vertices ?? []).map((id) => ctx.resolveRef(id));
1330
2077
  const poly = board.create("polygon", verts, {
@@ -1768,5 +2515,5 @@ function deserializeScene(domain, raw) {
1768
2515
  }
1769
2516
 
1770
2517
  export { deserializeScene, listObjects, nextLabel, serializeScene, useEditorState };
1771
- //# sourceMappingURL=chunk-REIJZDVZ.mjs.map
1772
- //# sourceMappingURL=chunk-REIJZDVZ.mjs.map
2518
+ //# sourceMappingURL=chunk-D5LWSN2Y.mjs.map
2519
+ //# sourceMappingURL=chunk-D5LWSN2Y.mjs.map