@xom11/whiteboard 0.25.0 → 0.28.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/{ExcalidrawWithMenus-WENZRYYE.mjs → ExcalidrawWithMenus-2QPPTXJM.mjs} +3 -2
- package/dist/ExcalidrawWithMenus-2QPPTXJM.mjs.map +1 -0
- package/dist/ai.d.mts +3035 -108
- package/dist/ai.d.ts +3035 -108
- package/dist/ai.js +6780 -788
- package/dist/ai.js.map +1 -1
- package/dist/ai.mjs +5140 -577
- package/dist/ai.mjs.map +1 -1
- package/dist/catalog.json +5 -5
- package/dist/{chunk-NDEZJKNY.mjs → chunk-5JM35CXV.mjs} +4 -4
- package/dist/{chunk-NDEZJKNY.mjs.map → chunk-5JM35CXV.mjs.map} +1 -1
- package/dist/{chunk-VNCCIV6O.mjs → chunk-AJAHD35N.mjs} +779 -9
- package/dist/chunk-AJAHD35N.mjs.map +1 -0
- package/dist/{chunk-M42TGYT6.mjs → chunk-BNBOIDO5.mjs} +3 -3
- package/dist/{chunk-M42TGYT6.mjs.map → chunk-BNBOIDO5.mjs.map} +1 -1
- package/dist/{chunk-ONBCUWVI.mjs → chunk-BU5KLO3P.mjs} +3 -3
- package/dist/{chunk-ONBCUWVI.mjs.map → chunk-BU5KLO3P.mjs.map} +1 -1
- package/dist/{chunk-CJBLJUWG.mjs → chunk-CXHNVYMD.mjs} +4 -4
- package/dist/{chunk-CJBLJUWG.mjs.map → chunk-CXHNVYMD.mjs.map} +1 -1
- package/dist/chunk-H22OZYTW.mjs +265 -0
- package/dist/chunk-H22OZYTW.mjs.map +1 -0
- package/dist/chunk-J5LGTIGS.mjs +10 -0
- package/dist/chunk-J5LGTIGS.mjs.map +1 -0
- package/dist/{chunk-TB4CL25L.mjs → chunk-OQIQNKPQ.mjs} +206 -66
- package/dist/chunk-OQIQNKPQ.mjs.map +1 -0
- package/dist/{chunk-SGFJLHHG.mjs → chunk-PPKHCRRE.mjs} +3 -3
- package/dist/{chunk-SGFJLHHG.mjs.map → chunk-PPKHCRRE.mjs.map} +1 -1
- package/dist/{chunk-YSJOVBCD.mjs → chunk-QCZVFEN4.mjs} +4 -4
- package/dist/{chunk-YSJOVBCD.mjs.map → chunk-QCZVFEN4.mjs.map} +1 -1
- package/dist/{chunk-ESVPQWHX.mjs → chunk-QRUAEXLR.mjs} +5 -5
- package/dist/{chunk-ESVPQWHX.mjs.map → chunk-QRUAEXLR.mjs.map} +1 -1
- package/dist/{chunk-AYSFWUPK.mjs → chunk-SZDAS7LK.mjs} +79 -2
- package/dist/chunk-SZDAS7LK.mjs.map +1 -0
- package/dist/chunk-T3SOHYB2.mjs +851 -0
- package/dist/chunk-T3SOHYB2.mjs.map +1 -0
- package/dist/{chunk-I24QOHPU.mjs → chunk-V3YJ6JFL.mjs} +3 -3
- package/dist/{chunk-I24QOHPU.mjs.map → chunk-V3YJ6JFL.mjs.map} +1 -1
- package/dist/{chunk-REIJZDVZ.mjs → chunk-ZTQBUKLJ.mjs} +960 -196
- package/dist/chunk-ZTQBUKLJ.mjs.map +1 -0
- package/dist/geometry-2d.d.mts +1 -1
- package/dist/geometry-2d.d.ts +1 -1
- package/dist/geometry-2d.js +5521 -1384
- package/dist/geometry-2d.js.map +1 -1
- package/dist/geometry-2d.mjs +5 -4
- package/dist/geometry-3d.d.mts +1 -1
- package/dist/geometry-3d.d.ts +1 -1
- package/dist/geometry-3d.js +1351 -252
- package/dist/geometry-3d.js.map +1 -1
- package/dist/geometry-3d.mjs +4 -3
- package/dist/graph-2d.d.mts +1 -1
- package/dist/graph-2d.d.ts +1 -1
- package/dist/graph-2d.js +1517 -341
- package/dist/graph-2d.js.map +1 -1
- package/dist/graph-2d.mjs +7 -6
- package/dist/handleExtractProblem-C-U5KluK.d.mts +158 -0
- package/dist/handleExtractProblem-C-U5KluK.d.ts +158 -0
- package/dist/{host-A64ITWVX.mjs → host-2ISGVO7O.mjs} +6 -5
- package/dist/host-2ISGVO7O.mjs.map +1 -0
- package/dist/{host-L7FMFZUW.mjs → host-4P766V4J.mjs} +1363 -463
- package/dist/host-4P766V4J.mjs.map +1 -0
- package/dist/{host-QK53UYMD.mjs → host-HOSJHQ5H.mjs} +10 -9
- package/dist/host-HOSJHQ5H.mjs.map +1 -0
- package/dist/{host-QS2EOTRJ.mjs → host-ZQCDAT6O.mjs} +3 -2
- package/dist/host-ZQCDAT6O.mjs.map +1 -0
- package/dist/index.d.mts +10 -4
- package/dist/index.d.ts +10 -4
- package/dist/index.js +5746 -1603
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +26 -22
- package/dist/index.mjs.map +1 -1
- package/dist/latex.d.mts +1 -1
- package/dist/latex.d.ts +1 -1
- package/dist/latex.mjs +2 -1
- package/dist/render-ZX2O2IK7.mjs +10 -0
- package/dist/{render-3WTY7NZB.mjs.map → render-ZX2O2IK7.mjs.map} +1 -1
- package/dist/serialize-N4G6RFBB.mjs +9 -0
- package/dist/{serialize-SRJVKYUG.mjs.map → serialize-N4G6RFBB.mjs.map} +1 -1
- package/dist/{types-DWRyCa2m.d.ts → types-BHYC2Fiw.d.mts} +130 -1
- package/dist/{types-DWRyCa2m.d.mts → types-BHYC2Fiw.d.ts} +130 -1
- package/package.json +10 -1
- package/dist/ExcalidrawWithMenus-WENZRYYE.mjs.map +0 -1
- package/dist/chunk-AYSFWUPK.mjs.map +0 -1
- package/dist/chunk-REIJZDVZ.mjs.map +0 -1
- package/dist/chunk-TB4CL25L.mjs.map +0 -1
- package/dist/chunk-VNCCIV6O.mjs.map +0 -1
- package/dist/chunk-VRHWDZ66.mjs +0 -96
- package/dist/chunk-VRHWDZ66.mjs.map +0 -1
- package/dist/host-A64ITWVX.mjs.map +0 -1
- package/dist/host-L7FMFZUW.mjs.map +0 -1
- package/dist/host-QK53UYMD.mjs.map +0 -1
- package/dist/host-QS2EOTRJ.mjs.map +0 -1
- package/dist/render-3WTY7NZB.mjs +0 -9
- 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
|
-
|
|
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
|
-
|
|
643
|
-
if (
|
|
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
|
-
|
|
687
|
-
|
|
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
|
|
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) =>
|
|
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
|
|
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,17 +1653,31 @@ var def8 = {
|
|
|
1105
1653
|
},
|
|
1106
1654
|
describe: (obj, state) => {
|
|
1107
1655
|
const L = (id) => labelOf(id, state);
|
|
1108
|
-
const c = obj.attrs
|
|
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) => {
|
|
1115
1675
|
const board = ctx.jxg;
|
|
1676
|
+
const isCenterLabel = (l) => /^[A-Z]['′]?\d*$/u.test(l);
|
|
1677
|
+
const isCenter = isCenterLabel(obj.label);
|
|
1116
1678
|
const baseOpts = {
|
|
1117
1679
|
name: obj.label,
|
|
1118
|
-
withLabel: obj.attrs.showLabel ?? false,
|
|
1680
|
+
withLabel: isCenter ? obj.attrs.showLabel ?? false : true,
|
|
1119
1681
|
strokeColor: obj.attrs.color ?? "#0f172a",
|
|
1120
1682
|
strokeWidth: obj.attrs.width ?? 2,
|
|
1121
1683
|
dash: obj.attrs.dash ?? 0,
|
|
@@ -1123,13 +1685,81 @@ var def8 = {
|
|
|
1123
1685
|
visible: obj.visible,
|
|
1124
1686
|
fixed: obj.locked
|
|
1125
1687
|
};
|
|
1126
|
-
const c = obj.attrs
|
|
1688
|
+
const c = asConstruction(obj.attrs);
|
|
1127
1689
|
if (c?.kind === "circumscribed") {
|
|
1128
1690
|
const p1 = ctx.resolveRef(c.p1);
|
|
1129
1691
|
const p2 = ctx.resolveRef(c.p2);
|
|
1130
1692
|
const p3 = ctx.resolveRef(c.p3);
|
|
1693
|
+
if (isCenter) {
|
|
1694
|
+
const center2 = board.create("circumcenter", [p1, p2, p3], {
|
|
1695
|
+
visible: obj.visible,
|
|
1696
|
+
withLabel: true,
|
|
1697
|
+
fixed: true,
|
|
1698
|
+
name: obj.label
|
|
1699
|
+
});
|
|
1700
|
+
const circ = board.create("circumcircle", [p1, p2, p3], { ...baseOpts, withLabel: false });
|
|
1701
|
+
circ.center = circ.center ?? center2;
|
|
1702
|
+
circ._helpers = [center2];
|
|
1703
|
+
return circ;
|
|
1704
|
+
}
|
|
1131
1705
|
return board.create("circumcircle", [p1, p2, p3], baseOpts);
|
|
1132
1706
|
}
|
|
1707
|
+
if (c?.kind === "incircle") {
|
|
1708
|
+
const p1 = ctx.resolveRef(c.p1);
|
|
1709
|
+
const p2 = ctx.resolveRef(c.p2);
|
|
1710
|
+
const p3 = ctx.resolveRef(c.p3);
|
|
1711
|
+
if (isCenter) {
|
|
1712
|
+
const center2 = board.create("incenter", [p1, p2, p3], {
|
|
1713
|
+
visible: obj.visible,
|
|
1714
|
+
withLabel: true,
|
|
1715
|
+
fixed: true,
|
|
1716
|
+
name: obj.label
|
|
1717
|
+
});
|
|
1718
|
+
const circ = board.create("incircle", [p1, p2, p3], { ...baseOpts, withLabel: false });
|
|
1719
|
+
circ.center = circ.center ?? center2;
|
|
1720
|
+
circ._helpers = [center2];
|
|
1721
|
+
return circ;
|
|
1722
|
+
}
|
|
1723
|
+
return board.create("incircle", [p1, p2, p3], baseOpts);
|
|
1724
|
+
}
|
|
1725
|
+
if (c?.kind === "excircle") {
|
|
1726
|
+
const P = [ctx.resolveRef(c.p1), ctx.resolveRef(c.p2), ctx.resolveRef(c.p3)];
|
|
1727
|
+
const ids = [c.p1, c.p2, c.p3];
|
|
1728
|
+
const oppIdx = Math.max(0, ids.indexOf(c.opposite));
|
|
1729
|
+
const verts = () => [
|
|
1730
|
+
[P[0].X(), P[0].Y()],
|
|
1731
|
+
[P[1].X(), P[1].Y()],
|
|
1732
|
+
[P[2].X(), P[2].Y()]
|
|
1733
|
+
];
|
|
1734
|
+
const ctr = () => excenter(verts(), oppIdx);
|
|
1735
|
+
const radius = () => {
|
|
1736
|
+
const I = ctr();
|
|
1737
|
+
const others = [0, 1, 2].filter((i) => i !== oppIdx);
|
|
1738
|
+
const v = verts();
|
|
1739
|
+
const a = v[others[0]];
|
|
1740
|
+
const b = v[others[1]];
|
|
1741
|
+
const dx = b[0] - a[0];
|
|
1742
|
+
const dy = b[1] - a[1];
|
|
1743
|
+
const len = Math.hypot(dx, dy) || 1;
|
|
1744
|
+
return Math.abs((I[0] - a[0]) * dy - (I[1] - a[1]) * dx) / len;
|
|
1745
|
+
};
|
|
1746
|
+
const center2 = board.create("point", [() => ctr()[0], () => ctr()[1]], { visible: false, withLabel: false, fixed: true, name: "" });
|
|
1747
|
+
const circ = board.create("circle", [center2, () => radius()], baseOpts);
|
|
1748
|
+
circ._helpers = [center2];
|
|
1749
|
+
return circ;
|
|
1750
|
+
}
|
|
1751
|
+
if (c?.kind === "diameter") {
|
|
1752
|
+
const p1 = ctx.resolveRef(c.p1);
|
|
1753
|
+
const p2 = ctx.resolveRef(c.p2);
|
|
1754
|
+
const center2 = board.create("midpoint", [p1, p2], { visible: false, withLabel: false, fixed: true, name: "" });
|
|
1755
|
+
const circ = board.create("circle", [center2, p2], baseOpts);
|
|
1756
|
+
circ._helpers = [center2];
|
|
1757
|
+
return circ;
|
|
1758
|
+
}
|
|
1759
|
+
if (typeof obj.attrs.radius === "number") {
|
|
1760
|
+
const center2 = ctx.resolveRef(obj.attrs.center);
|
|
1761
|
+
return board.create("circle", [center2, obj.attrs.radius], baseOpts);
|
|
1762
|
+
}
|
|
1133
1763
|
const center = ctx.resolveRef(obj.attrs.center);
|
|
1134
1764
|
const surface = ctx.resolveRef(obj.attrs.surfacePoint);
|
|
1135
1765
|
return board.create("circle", [center, surface], baseOpts);
|
|
@@ -1273,19 +1903,53 @@ function regularVertexLabels(p1Label, p2Label, n) {
|
|
|
1273
1903
|
}
|
|
1274
1904
|
return `${p1Label}${p2Label}\u2026`;
|
|
1275
1905
|
}
|
|
1906
|
+
function specialShapeName(kind) {
|
|
1907
|
+
switch (kind) {
|
|
1908
|
+
case "square":
|
|
1909
|
+
return "H\xECnh vu\xF4ng";
|
|
1910
|
+
case "rectangle":
|
|
1911
|
+
return "H\xECnh ch\u1EEF nh\u1EADt";
|
|
1912
|
+
case "rhombus":
|
|
1913
|
+
return "H\xECnh thoi";
|
|
1914
|
+
case "parallelogram":
|
|
1915
|
+
return "H\xECnh b\xECnh h\xE0nh";
|
|
1916
|
+
case "isoTrapezoid":
|
|
1917
|
+
return "H\xECnh thang c\xE2n";
|
|
1918
|
+
case "isoTriangle":
|
|
1919
|
+
return "Tam gi\xE1c c\xE2n";
|
|
1920
|
+
case "rightTriangle":
|
|
1921
|
+
return "Tam gi\xE1c vu\xF4ng";
|
|
1922
|
+
case "regular":
|
|
1923
|
+
return "";
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1276
1926
|
var def11 = {
|
|
1277
1927
|
type: "polygon",
|
|
1278
1928
|
schemaVersion: 1,
|
|
1279
1929
|
migrate: {},
|
|
1280
1930
|
validate: (a) => {
|
|
1281
1931
|
if (a?.construction) {
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1932
|
+
const c = a.construction;
|
|
1933
|
+
if (c.kind === "regular") {
|
|
1934
|
+
if (!c.p1 || !c.p2) throw new Error("polygon (regular): p1 v\xE0 p2 b\u1EAFt bu\u1ED9c");
|
|
1935
|
+
if (!Number.isFinite(c.n) || c.n < 3) throw new Error("polygon (regular): n \u2265 3");
|
|
1936
|
+
return;
|
|
1937
|
+
}
|
|
1938
|
+
if (c.kind === "square") {
|
|
1939
|
+
if (!c.p1 || !c.p2) throw new Error("polygon (square): p1 v\xE0 p2 b\u1EAFt bu\u1ED9c");
|
|
1940
|
+
return;
|
|
1941
|
+
}
|
|
1942
|
+
if (c.kind === "rectangle" || c.kind === "rhombus" || c.kind === "parallelogram" || c.kind === "isoTrapezoid") {
|
|
1943
|
+
if (!c.p1 || !c.p2 || !c.p3) throw new Error(`polygon (${c.kind}): p1, p2, p3 b\u1EAFt bu\u1ED9c`);
|
|
1944
|
+
return;
|
|
1945
|
+
}
|
|
1946
|
+
if (c.kind === "isoTriangle") {
|
|
1947
|
+
if (!c.base1 || !c.base2 || !c.apex) throw new Error("polygon (isoTriangle): base1, base2, apex b\u1EAFt bu\u1ED9c");
|
|
1948
|
+
return;
|
|
1949
|
+
}
|
|
1950
|
+
if (c.kind === "rightTriangle") {
|
|
1951
|
+
if (!c.rightAngle || !c.leg1End || !c.leg2End) throw new Error("polygon (rightTriangle): rightAngle, leg1End, leg2End b\u1EAFt bu\u1ED9c");
|
|
1952
|
+
return;
|
|
1289
1953
|
}
|
|
1290
1954
|
return;
|
|
1291
1955
|
}
|
|
@@ -1294,14 +1958,51 @@ var def11 = {
|
|
|
1294
1958
|
}
|
|
1295
1959
|
},
|
|
1296
1960
|
dependsOn: (a) => {
|
|
1297
|
-
|
|
1298
|
-
return [...a.vertices ?? []];
|
|
1961
|
+
const c = a.construction;
|
|
1962
|
+
if (!c) return [...a.vertices ?? []];
|
|
1963
|
+
switch (c.kind) {
|
|
1964
|
+
case "regular":
|
|
1965
|
+
return [c.p1, c.p2];
|
|
1966
|
+
case "square":
|
|
1967
|
+
return [c.p1, c.p2];
|
|
1968
|
+
case "rectangle":
|
|
1969
|
+
case "rhombus":
|
|
1970
|
+
case "parallelogram":
|
|
1971
|
+
case "isoTrapezoid":
|
|
1972
|
+
return [c.p1, c.p2, c.p3];
|
|
1973
|
+
case "isoTriangle":
|
|
1974
|
+
return [c.base1, c.base2, c.apex];
|
|
1975
|
+
case "rightTriangle":
|
|
1976
|
+
return [c.rightAngle, c.leg1End, c.leg2End];
|
|
1977
|
+
}
|
|
1299
1978
|
},
|
|
1300
1979
|
describe: (obj, state) => {
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1980
|
+
const c = obj.attrs.construction;
|
|
1981
|
+
if (c) {
|
|
1982
|
+
if (c.kind === "regular") {
|
|
1983
|
+
const labels2 = regularVertexLabels(labelOf(c.p1, state), labelOf(c.p2, state), c.n);
|
|
1984
|
+
return `${regularPolygonName(c.n)} ${labels2}`;
|
|
1985
|
+
}
|
|
1986
|
+
const name = specialShapeName(c.kind);
|
|
1987
|
+
let labels = [];
|
|
1988
|
+
switch (c.kind) {
|
|
1989
|
+
case "square":
|
|
1990
|
+
labels = [labelOf(c.p1, state), labelOf(c.p2, state)];
|
|
1991
|
+
break;
|
|
1992
|
+
case "rectangle":
|
|
1993
|
+
case "rhombus":
|
|
1994
|
+
case "parallelogram":
|
|
1995
|
+
case "isoTrapezoid":
|
|
1996
|
+
labels = [labelOf(c.p1, state), labelOf(c.p2, state), labelOf(c.p3, state)];
|
|
1997
|
+
break;
|
|
1998
|
+
case "isoTriangle":
|
|
1999
|
+
labels = [labelOf(c.apex, state), labelOf(c.base1, state), labelOf(c.base2, state)];
|
|
2000
|
+
break;
|
|
2001
|
+
case "rightTriangle":
|
|
2002
|
+
labels = [labelOf(c.rightAngle, state), labelOf(c.leg1End, state), labelOf(c.leg2End, state)];
|
|
2003
|
+
break;
|
|
2004
|
+
}
|
|
2005
|
+
return `${name} ${labels.join("")}`;
|
|
1305
2006
|
}
|
|
1306
2007
|
return `\u0110a gi\xE1c ${(obj.attrs.vertices ?? []).map((id) => labelOf(id, state)).join("")}`;
|
|
1307
2008
|
},
|
|
@@ -1309,22 +2010,85 @@ var def11 = {
|
|
|
1309
2010
|
const board = ctx.jxg;
|
|
1310
2011
|
const label = obj.label;
|
|
1311
2012
|
const showValue = obj.attrs.showValue ?? false;
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
2013
|
+
const cons = obj.attrs.construction;
|
|
2014
|
+
const commonAttrs = {
|
|
2015
|
+
name: label,
|
|
2016
|
+
withLabel: obj.attrs.showLabel ?? false,
|
|
2017
|
+
borders: {
|
|
2018
|
+
strokeColor: obj.attrs.color ?? "#0f172a",
|
|
2019
|
+
strokeWidth: obj.attrs.width ?? 2
|
|
2020
|
+
},
|
|
2021
|
+
fillColor: obj.attrs.color ?? "#60a5fa",
|
|
2022
|
+
fillOpacity: obj.attrs.fillOpacity ?? 0.15,
|
|
2023
|
+
visible: obj.visible,
|
|
2024
|
+
fixed: obj.locked
|
|
2025
|
+
};
|
|
2026
|
+
if (cons?.kind === "regular") {
|
|
2027
|
+
const p1 = ctx.resolveRef(cons.p1);
|
|
2028
|
+
const p2 = ctx.resolveRef(cons.p2);
|
|
2029
|
+
return board.create("regularpolygon", [p1, p2, cons.n], commonAttrs);
|
|
2030
|
+
}
|
|
2031
|
+
if (cons?.kind === "square") {
|
|
2032
|
+
const p1 = ctx.resolveRef(cons.p1);
|
|
2033
|
+
const p2 = ctx.resolveRef(cons.p2);
|
|
2034
|
+
return board.create("regularpolygon", [p1, p2, 4], commonAttrs);
|
|
2035
|
+
}
|
|
2036
|
+
if (cons?.kind === "rectangle" || cons?.kind === "rhombus" || cons?.kind === "parallelogram") {
|
|
2037
|
+
const A = ctx.resolveRef(cons.p1);
|
|
2038
|
+
const B = ctx.resolveRef(cons.p2);
|
|
2039
|
+
const C = ctx.resolveRef(cons.p3);
|
|
2040
|
+
const D = board.create(
|
|
2041
|
+
"point",
|
|
2042
|
+
[() => A.X() + C.X() - B.X(), () => A.Y() + C.Y() - B.Y()],
|
|
2043
|
+
{ visible: false, withLabel: false, fixed: true, name: "" }
|
|
2044
|
+
);
|
|
2045
|
+
const poly2 = board.create("polygon", [A, B, C, D], commonAttrs);
|
|
2046
|
+
poly2._helpers = [D];
|
|
2047
|
+
return poly2;
|
|
2048
|
+
}
|
|
2049
|
+
if (cons?.kind === "isoTrapezoid") {
|
|
2050
|
+
const A = ctx.resolveRef(cons.p1);
|
|
2051
|
+
const B = ctx.resolveRef(cons.p2);
|
|
2052
|
+
const C = ctx.resolveRef(cons.p3);
|
|
2053
|
+
const Dx = () => {
|
|
2054
|
+
const Mx = (A.X() + B.X()) / 2;
|
|
2055
|
+
const My = (A.Y() + B.Y()) / 2;
|
|
2056
|
+
const ux = B.X() - A.X();
|
|
2057
|
+
const uy = B.Y() - A.Y();
|
|
2058
|
+
const len2 = ux * ux + uy * uy || 1;
|
|
2059
|
+
const proj = ((C.X() - Mx) * ux + (C.Y() - My) * uy) / len2;
|
|
2060
|
+
return C.X() - 2 * proj * ux;
|
|
2061
|
+
};
|
|
2062
|
+
const Dy = () => {
|
|
2063
|
+
const Mx = (A.X() + B.X()) / 2;
|
|
2064
|
+
const My = (A.Y() + B.Y()) / 2;
|
|
2065
|
+
const ux = B.X() - A.X();
|
|
2066
|
+
const uy = B.Y() - A.Y();
|
|
2067
|
+
const len2 = ux * ux + uy * uy || 1;
|
|
2068
|
+
const proj = ((C.X() - Mx) * ux + (C.Y() - My) * uy) / len2;
|
|
2069
|
+
return C.Y() - 2 * proj * uy;
|
|
2070
|
+
};
|
|
2071
|
+
const D = board.create("point", [Dx, Dy], {
|
|
2072
|
+
visible: false,
|
|
2073
|
+
withLabel: false,
|
|
2074
|
+
fixed: true,
|
|
2075
|
+
name: ""
|
|
1327
2076
|
});
|
|
2077
|
+
const poly2 = board.create("polygon", [A, B, C, D], commonAttrs);
|
|
2078
|
+
poly2._helpers = [D];
|
|
2079
|
+
return poly2;
|
|
2080
|
+
}
|
|
2081
|
+
if (cons?.kind === "isoTriangle") {
|
|
2082
|
+
const Apex = ctx.resolveRef(cons.apex);
|
|
2083
|
+
const B1 = ctx.resolveRef(cons.base1);
|
|
2084
|
+
const B2 = ctx.resolveRef(cons.base2);
|
|
2085
|
+
return board.create("polygon", [Apex, B1, B2], commonAttrs);
|
|
2086
|
+
}
|
|
2087
|
+
if (cons?.kind === "rightTriangle") {
|
|
2088
|
+
const R = ctx.resolveRef(cons.rightAngle);
|
|
2089
|
+
const P = ctx.resolveRef(cons.leg1End);
|
|
2090
|
+
const Q = ctx.resolveRef(cons.leg2End);
|
|
2091
|
+
return board.create("polygon", [R, P, Q], commonAttrs);
|
|
1328
2092
|
}
|
|
1329
2093
|
const verts = (obj.attrs.vertices ?? []).map((id) => ctx.resolveRef(id));
|
|
1330
2094
|
const poly = board.create("polygon", verts, {
|
|
@@ -1768,5 +2532,5 @@ function deserializeScene(domain, raw) {
|
|
|
1768
2532
|
}
|
|
1769
2533
|
|
|
1770
2534
|
export { deserializeScene, listObjects, nextLabel, serializeScene, useEditorState };
|
|
1771
|
-
//# sourceMappingURL=chunk-
|
|
1772
|
-
//# sourceMappingURL=chunk-
|
|
2535
|
+
//# sourceMappingURL=chunk-ZTQBUKLJ.mjs.map
|
|
2536
|
+
//# sourceMappingURL=chunk-ZTQBUKLJ.mjs.map
|