From 70aeed9933804097302382512905655ddb93c398 Mon Sep 17 00:00:00 2001 From: york Date: Sat, 23 Nov 2024 09:21:14 +0800 Subject: [PATCH] feat: get circle tangent to three geometry lines near param --- ...angent-tangent-circle-at-points.plugin.tsx | 103 +++++++++++++ dev/cad-editor/plugins/variables.ts | 93 ++++++++++++ src/utils/tangent-circle.ts | 139 +++++++++++++++++- 3 files changed, 334 insertions(+), 1 deletion(-) create mode 100644 dev/cad-editor/plugins/create-tangent-tangent-tangent-circle-at-points.plugin.tsx diff --git a/dev/cad-editor/plugins/create-tangent-tangent-tangent-circle-at-points.plugin.tsx b/dev/cad-editor/plugins/create-tangent-tangent-tangent-circle-at-points.plugin.tsx new file mode 100644 index 0000000..54b72c9 --- /dev/null +++ b/dev/cad-editor/plugins/create-tangent-tangent-tangent-circle-at-points.plugin.tsx @@ -0,0 +1,103 @@ +import type { PluginContext } from './types' +import type * as core from '../../../src' +import type { Command } from '../command' +import type { LineContent } from './line-polyline.plugin' +import type { CircleContent } from './circle-arc.plugin' + +export function getCommand(ctx: PluginContext): Command { + const React = ctx.React + const icon = ( + + + + + + + + + + ) + return { + name: 'create tangent tangent tangent circle at points', + useCommand({ onEnd, type, scale, contents }) { + const [start, setStart] = React.useState<{ point: core.Position, param: number, line: core.GeometryLine }>() + const [second, setSecond] = React.useState<{ point: core.Position, param: number, line: core.GeometryLine }>() + const [cursor, setCursor] = React.useState() + const [result, setResult] = React.useState() + const assistentContents: (LineContent | CircleContent)[] = [] + if (start && cursor && type) { + if (second) { + assistentContents.push({ + points: [start.point, second.point, cursor], + type: 'polyline', + dashArray: [4 / scale], + }) + } else { + assistentContents.push({ + points: [start.point, cursor], + type: 'line', + dashArray: [4 / scale], + }) + } + } + if (result) { + assistentContents.push({ + ...result, + type: 'circle', + }) + } + const reset = () => { + setStart(undefined) + setSecond(undefined) + setResult(undefined) + setCursor(undefined) + } + const getTarget = (point: core.Position, id: number, param: number) => { + const content = contents[id] + if (!content) return + const lines = ctx.getContentModel(content)?.getGeometries?.(content, contents)?.lines + if (!lines) return + const index = Math.floor(param) + return { point, line: lines[index], param: param - index } + } + return { + onStart(p, target) { + if (!type) return + if (!target) return + if (target.param === undefined) return + if (!start) { + setStart(getTarget(p, target.id, target.param)) + } else if (!second) { + setSecond(getTarget(p, target.id, target.param)) + } else if (result) { + onEnd({ + updateContents: (contents) => { + contents.push({ type: 'circle', ...result } as CircleContent) + } + }) + reset() + } + }, + onMove(p, _, target) { + if (!type) return + setCursor(p) + setResult(undefined) + if (!target) return + if (target.param === undefined) return + if (!start) return + if (!second) return + const end = getTarget(p, target.id, target.param) + if (!end) return + const circle = ctx.getCircleTangentToThreeGeometryLinesNearParam(start.line, second.line, end.line, start.param, second.param, end.param) + if (circle) { + setResult(circle) + } + }, + assistentContents, + reset, + } + }, + selectCount: 0, + icon, + } +} diff --git a/dev/cad-editor/plugins/variables.ts b/dev/cad-editor/plugins/variables.ts index c417356..7dc6a96 100644 --- a/dev/cad-editor/plugins/variables.ts +++ b/dev/cad-editor/plugins/variables.ts @@ -3004,6 +3004,99 @@ export { getCommand }; `, +`// dev/cad-editor/plugins/create-tangent-tangent-tangent-circle-at-points.plugin.tsx +function getCommand(ctx) { + const React = ctx.React; + const icon = /* @__PURE__ */ React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 100 100" }, /* @__PURE__ */ React.createElement("polyline", { points: "0,8 100,8", strokeWidth: "5", strokeMiterlimit: "10", strokeLinejoin: "miter", strokeLinecap: "butt", strokeOpacity: "1", fill: "none", stroke: "currentColor" }), /* @__PURE__ */ React.createElement("polyline", { points: "99,19 60,100", strokeWidth: "5", strokeMiterlimit: "10", strokeLinejoin: "miter", strokeLinecap: "butt", strokeOpacity: "1", fill: "none", stroke: "currentColor" }), /* @__PURE__ */ React.createElement("polyline", { points: "0,22 44,98", strokeWidth: "5", strokeMiterlimit: "10", strokeLinejoin: "miter", strokeLinecap: "butt", strokeOpacity: "1", fill: "none", stroke: "currentColor" }), /* @__PURE__ */ React.createElement("circle", { cx: "50", cy: "42", r: "34", strokeWidth: "5", strokeMiterlimit: "10", strokeLinejoin: "miter", strokeLinecap: "butt", fillOpacity: "1", strokeOpacity: "1", fill: "none", stroke: "currentColor" }), /* @__PURE__ */ React.createElement("circle", { cx: "82", cy: "60", r: "8", fillOpacity: "1", strokeOpacity: "1", fill: "currentColor", stroke: "none" }), /* @__PURE__ */ React.createElement("circle", { cx: "48", cy: "8", r: "8", fillOpacity: "1", strokeOpacity: "1", fill: "currentColor", stroke: "none" }), /* @__PURE__ */ React.createElement("circle", { cx: "22", cy: "60", r: "8", fillOpacity: "1", strokeOpacity: "1", fill: "currentColor", stroke: "none" })); + return { + name: "create tangent tangent tangent circle at points", + useCommand({ onEnd, type, scale, contents }) { + const [start, setStart] = React.useState(); + const [second, setSecond] = React.useState(); + const [cursor, setCursor] = React.useState(); + const [result, setResult] = React.useState(); + const assistentContents = []; + if (start && cursor && type) { + if (second) { + assistentContents.push({ + points: [start.point, second.point, cursor], + type: "polyline", + dashArray: [4 / scale] + }); + } else { + assistentContents.push({ + points: [start.point, cursor], + type: "line", + dashArray: [4 / scale] + }); + } + } + if (result) { + assistentContents.push({ + ...result, + type: "circle" + }); + } + const reset = () => { + setStart(void 0); + setSecond(void 0); + setResult(void 0); + setCursor(void 0); + }; + const getTarget = (point, id, param) => { + var _a, _b, _c; + const content = contents[id]; + if (!content) return; + const lines = (_c = (_b = (_a = ctx.getContentModel(content)) == null ? void 0 : _a.getGeometries) == null ? void 0 : _b.call(_a, content, contents)) == null ? void 0 : _c.lines; + if (!lines) return; + const index = Math.floor(param); + return { point, line: lines[index], param: param - index }; + }; + return { + onStart(p, target) { + if (!type) return; + if (!target) return; + if (target.param === void 0) return; + if (!start) { + setStart(getTarget(p, target.id, target.param)); + } else if (!second) { + setSecond(getTarget(p, target.id, target.param)); + } else if (result) { + onEnd({ + updateContents: (contents2) => { + contents2.push({ type: "circle", ...result }); + } + }); + reset(); + } + }, + onMove(p, _, target) { + if (!type) return; + setCursor(p); + setResult(void 0); + if (!target) return; + if (target.param === void 0) return; + if (!start) return; + if (!second) return; + const end = getTarget(p, target.id, target.param); + if (!end) return; + const circle = ctx.getCircleTangentToThreeGeometryLinesNearParam(start.line, second.line, end.line, start.param, second.param, end.param); + if (circle) { + setResult(circle); + } + }, + assistentContents, + reset + }; + }, + selectCount: 0, + icon + }; +} +export { + getCommand +}; +`, `// dev/cad-editor/plugins/circle-arc.plugin.tsx function isCircleContent(content) { return content.type === "circle"; diff --git a/src/utils/tangent-circle.ts b/src/utils/tangent-circle.ts index 5edd503..4e93e65 100644 --- a/src/utils/tangent-circle.ts +++ b/src/utils/tangent-circle.ts @@ -1,8 +1,9 @@ +import { Circle } from "./circle"; import { newtonIterate2, newtonIterates } from "./equation-calculater"; import { GeometryLine, getGeometryLineDerivatives } from "./geometry-line"; import { delta2 } from "./math"; import { Matrix2 } from "./matrix"; -import { Position } from "./position"; +import { getTwoPointsDistance, Position } from "./position"; import { Tuple2, Vec2 } from "./types"; /** @@ -190,3 +191,139 @@ export function getCircleTangentToTwoGeometryLinesNearParam(curve1: GeometryLine const p2 = derivative2[0](result[1]) return getVariables(p1, p2) } + +export function getCircleTangentToThreeGeometryLinesNearParam(curve1: GeometryLine, curve2: GeometryLine, curve3: GeometryLine, t1: number, t2: number, t3: number): Circle | undefined { + const derivative1 = getGeometryLineDerivatives(curve1) + const derivative2 = getGeometryLineDerivatives(curve2) + const derivative3 = getGeometryLineDerivatives(curve3) + // (y - y1)/(x - x1)y1'/x1' = -1 + // (y - y2)/(x - x2)y2'/x2' = -1 + // (x - x1)x1' + (y - y1)y1' = 0 + // (x - x2)x2' + (y - y2)y2' = 0 + // x x1' - x1 x1' + y y1' - y1 y1' = 0 + // x x2' - x2 x2' + y y2' - y2 y2' = 0 + // let a1 = x1 x1' + y1 y1', a2 = x2 x2' + y2 y2' + // x x1' + y y1' = a1 + // x x2' + y y2' = a2 + // x x1' x2' + y y1' x2' = a1 x2' + // x x1' x2' + y y2' x1' = a2 x1' + // y(y1' x2' - y2' x1') = a1 x2' - a2 x1' + // let a5 = a1 x2' - a2 x1', a6 = y1' x2' - y2' x1' + // y = a5 / a6 + // x x1' y2' + y y1' y2' = a1 y2' + // x x2' y1' + y y1' y2' = a2 y1' + // x(x1' y2' - x2' y1') = a1 y2' - a2 y1' + // let a3 = a1 y2' - a2 y1', a4 = x1' y2' - x2' y1' + // x = a3 / a4 + + // (x - x1)^2 + (y - y1)^2 = (x - x2)^2 + (y - y2)^2 = (x - x3)^2 + (y - y3)^2 + // z1 = (x - x1)^2 + (y - y1)^2 - (x - x3)^2 - (y - y3)^2 + // z2 = (x - x2)^2 + (y - y2)^2 - (x - x3)^2 - (y - y3)^2 + // (y - y3)/(x - x3)y3'/x3' = -1 + // (x - x3)x3' + (y - y3)y3' = 0 + // z3 = (x - x3)x3' + (y - y3)y3' + + const getVariables = (p1: Tuple2, p2: Tuple2, p3: Tuple2) => { + const [{ x: x1, y: y1 }, { x: x11, y: y11 }] = p1 + const [{ x: x2, y: y2 }, { x: x21, y: y21 }] = p2 + const [{ x: x3, y: y3 }, { x: x31, y: y31 }] = p3 + const a1 = x1 * x11 + y1 * y11, a2 = x2 * x21 + y2 * y21 + const a3 = a1 * y21 - a2 * y11, a4 = x11 * y21 - x21 * y11 + const a5 = a1 * x21 - a2 * x11, a6 = y11 * x21 - y21 * x11 + const x = a3 / a4 + const y = a5 / a6 + return { x, y, a1, a2, a3, a4, a5, a6, x1, y1, x2, y2, x11, y11, x21, y21, x3, y3, x31, y31 } + } + + const f1 = (t: number[]): number[] => { + const p1 = derivative1[0](t[0]) + const p2 = derivative2[0](t[1]) + const p3 = derivative3[0](t[2]) + const { x, y, x1, y1, x2, y2, x3, y3, x31, y31 } = getVariables(p1, p2, p3) + return [ + (x - x1) ** 2 + (y - y1) ** 2 - (x - x3) ** 2 - (y - y3) ** 2, + (x - x2) ** 2 + (y - y2) ** 2 - (x - x3) ** 2 - (y - y3) ** 2, + (x - x3) * x31 + (y - y3) * y31, + ] + } + + const f2 = (t: number[]): number[][] => { + const [p10, p11, { x: x12, y: y12 }] = derivative1[1](t[0]) + const [p20, p21, { x: x22, y: y22 }] = derivative2[1](t[1]) + const [p30, p31, { x: x32, y: y32 }] = derivative3[1](t[2]) + const { x, y, a1, a2, a3, a4, a5, a6, x1, y1, x2, y2, x11, y11, x21, y21, x3, y3, x31, y31 } = getVariables([p10, p11], [p20, p21], [p30, p31]) + + // a1' = (x1 x1' + y1 y1')' + // a1' = x1'^2 + x1 x1'' + y1'^2 + y1 y1'' + // a2' = (x2 x2' + y2 y2')' + // a2' = x2'^2 + x2 x2'' + y2'^2 + y2 y2'' + const a11 = x11 ** 2 + x1 * x12 + y11 ** 2 + y1 * y12 + const a21 = x21 ** 2 + x2 * x22 + y21 ** 2 + y2 * y22 + + // da3/dt1 = a1' y2' - a2 y1'' + // da3/dt2 = a1 y2'' - a2' y1' + // da4/dt1 = x1'' y2' - x2' y1'' + // da4/dt2 = x1' y2'' - x2'' y1' + const a31 = a11 * y21 - a2 * y12 + const a32 = a1 * y22 - a21 * y11 + const a41 = x12 * y21 - x21 * y12 + const a42 = x11 * y22 - x22 * y11 + + // da5/dt1 = a1' x2' - a2 x1'' + // da5/dt2 = a1 x2'' - a2' x1' + // da6/dt1 = y1'' x2' - y2' x1'' + // da6/dt2 = y1' x2'' - y2'' x1' + const a51 = a11 * x21 - a2 * x12 + const a52 = a1 * x22 - a21 * x11 + const a61 = y12 * x21 - y21 * x12 + const a62 = y11 * x22 - y22 * x11 + + // x' = (a3/a4)' + // dx/dt1 = (a4 da3/dt1 - a3 da4/dt1)/a4/a4 + // dx/dt2 = (a4 da3/dt2 - a3 da4/dt2)/a4/a4 + // y' = (a5/a6)' + // dy/dt1 = (a6 da5/dt1 - a5 da6/dt1)/a6/a6 + // dy/dt2 = (a6 da5/dt2 - a5 da6/dt2)/a6/a6 + const xt1 = (a4 * a31 - a3 * a41) / a4 / a4 + const xt2 = (a4 * a32 - a3 * a42) / a4 / a4 + const yt1 = (a6 * a51 - a5 * a61) / a6 / a6 + const yt2 = (a6 * a52 - a5 * a62) / a6 / a6 + + // dz1/dt1 = 2(x - x1)(dx/dt1 - x1') + 2(y - y1)dy/dt1 - 2(x - x3)dx/dt1 - 2(y - y3)dy/dt1 + // dz1/dt2 = 2(x - x1)dx/dt2 + 2(y - y1)(dy/dt2 - y1') - 2(x - x3)dx/dt2 - 2(y - y3)dy/dt2 + // dz1/dt3 = 2(x - x3)x3' + 2(y - y3)y3' + // dz2/dt1 = 2(x - x2)dx/dt1 + 2(y - y2)dy/dt1 - 2(x - x3)dx/dt1 - 2(y - y3)dy/dt1 + // dz2/dt2 = 2(x - x2)(dx/dt2 - x2') + 2(y - y2)(dy/dt2 - y2') - 2(x - x3)dx/dt2 - 2(y - y3)dy/dt2 + // dz2/dt3 = 2(x - x3)x3' + 2(y - y3)y3' + // dz3/dt1 = dx/dt1 x3' + dy/dt1 y3' + // dz3/dt2 = dx/dt2 x3' + dy/dt2 y3' + // dz3/dt3 = -x3' x3' + (x - x3)x3'' - y3' y3' + (y - y3)y3'' + return [ + [ + 2 * (x - x1) * (xt1 - x11) + 2 * (y - y1) * yt1 - 2 * (x - x3) * xt1 - 2 * (y - y3) * yt1, + 2 * (x - x1) * xt2 + 2 * (y - y1) * (yt2 - y11) - 2 * (x - x3) * xt2 - 2 * (y - y3) * yt2, + 2 * (x - x3) * x31 + 2 * (y - y3) * y31, + ], + [ + 2 * (x - x2) * xt1 + 2 * (y - y2) * yt1 - 2 * (x - x3) * xt1 - 2 * (y - y3) * yt1, + 2 * (x - x2) * (xt2 - x21) + 2 * (y - y2) * (yt2 - y21) - 2 * (x - x3) * xt2 - 2 * (y - y3) * yt2, + 2 * (x - x3) * x31 + 2 * (y - y3) * y31, + ], + [ + xt1 * x31 + yt1 * y31, + xt2 * x31 + yt2 * y31, + -x31 * x31 + (x - x3) * x32 - y31 * y31 + (y - y3) * y32, + ], + ] + } + const result = newtonIterates([t1, t2, t3], f1, f2, delta2) + if (!result) return + const p1 = derivative1[0](result[0]) + const p2 = derivative2[0](result[1]) + const p3 = derivative3[0](result[2]) + const v = getVariables(p1, p2, p3) + return { + ...v, + r: getTwoPointsDistance(v, { x: v.x1, y: v.y1 }), + } +}