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