Skip to content

Commit

Permalink
feat: get circle tangent to 2 geometry lines near param b953f6e
Browse files Browse the repository at this point in the history
  • Loading branch information
plantain-00 committed Nov 13, 2024
1 parent c6ef1d1 commit 66eac43
Show file tree
Hide file tree
Showing 5 changed files with 291 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { PluginContext } from './types'
import type * as core from '../../../src'
import type { Command } from '../command'
import type * as model from '../model'
import { isLineContent } from './line-polyline.plugin'
import { isLineContent, LineContent } from './line-polyline.plugin'
import { CircleContent, isArcContent, isCircleContent } from './circle-arc.plugin'

export function getCommand(ctx: PluginContext): Command[] {
Expand Down Expand Up @@ -38,6 +38,15 @@ export function getCommand(ctx: PluginContext): Command[] {
</svg>
)
const contentSelectable = (c: model.BaseContent) => isCircleContent(c) || isArcContent(c) || isLineContent(c)
const icon2 = (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<polyline points="10,87 89,87" strokeWidth="5" strokeMiterlimit="10" strokeLinejoin="miter" strokeLinecap="butt" fill="none" stroke="currentColor"></polyline>
<circle cx="17" cy="40" r="16" strokeWidth="5" strokeMiterlimit="10" strokeLinejoin="miter" strokeLinecap="butt" fill="none" stroke="currentColor"></circle>
<circle cx="60" cy="57" r="30" strokeWidth="5" strokeMiterlimit="10" strokeLinejoin="miter" strokeLinecap="butt" fill="none" stroke="currentColor"></circle>
<circle cx="33" cy="46" r="8" fillOpacity="1" strokeOpacity="1" fill="currentColor" stroke="none"></circle>
<circle cx="60" cy="87" r="8" fillOpacity="1" strokeOpacity="1" fill="currentColor" stroke="none"></circle>
</svg>
)
const command: Command = {
name: 'create tangent tangent radius circle',
useCommand({ onEnd, type, selected, scale }) {
Expand Down Expand Up @@ -213,6 +222,105 @@ export function getCommand(ctx: PluginContext): Command[] {
}
},
selectCount: 0,
},
{
name: 'create tangent tangent radius circle at points',
useCommand({ onEnd, type, scale, contents }) {
const [start, setStart] = React.useState<{ point: core.Position, param: number, line: core.GeometryLine }>()
const [cursor, setCursor] = React.useState<core.Position>()
const [radius, setRadius] = React.useState<number>()
const [result, setResult] = React.useState<core.Circle>()
const assistentContents: (LineContent | CircleContent)[] = []
if (start && cursor && type) {
assistentContents.push({
points: [start.point, cursor],
type: 'line',
dashArray: [4 / scale],
})
}
if (result) {
assistentContents.push({ ...result, type: 'circle' } as CircleContent)
}
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 }
}
let message = ''
if (type && !radius) {
message = 'input radius'
}
const { input, setInputPosition, setCursorPosition, clearText, resetInput } = ctx.useCursorInput(message, type ? (e, text) => {
if (e.key === 'Enter') {
const r = +text
if (!isNaN(r)) {
setRadius(r)
clearText()
resetInput()
}
}
} : undefined)
const reset = () => {
setStart(undefined)
setResult(undefined)
setCursor(undefined)
setRadius(undefined)
clearText()
resetInput()
}
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 (result) {
onEnd({
updateContents: (contents) => {
contents.push({ ...result, type: 'circle' } as CircleContent)
}
})
reset()
}
},
onMove(p, viewportPosition, target) {
if (!type) return
setCursor(p)
setResult(undefined)
setCursorPosition(p)
setInputPosition(viewportPosition || p)
if (!radius) return
if (!target) return
if (target.param === undefined) return
if (!start) return
const end = getTarget(p, target.id, target.param)
if (!end) return
const center = ctx.getTwoGeneralFormLinesIntersectionPoint(
ctx.pointAndDirectionToGeneralFormLine(
start.point,
ctx.getGeometryLineTangentRadianAtParam(start.param, start.line),
),
ctx.pointAndDirectionToGeneralFormLine(
end.point,
ctx.getGeometryLineTangentRadianAtParam(end.param, end.line),
),
)
if (!center) return
const circle = ctx.getCircleTangentTo2GeometryLinesNearParam(start.line, end.line, radius, start.param, end.param, center)
if (!circle) return
setResult({ ...circle, r: radius })
},
input,
assistentContents,
reset,
}
},
selectCount: 0,
icon: icon2,
}
]
}
101 changes: 101 additions & 0 deletions dev/cad-editor/plugins/variables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2733,6 +2733,7 @@ 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: "10,87 89,87", strokeWidth: "5", strokeMiterlimit: "10", strokeLinejoin: "miter", strokeLinecap: "butt", fill: "none", stroke: "currentColor" }), /* @__PURE__ */ React.createElement("circle", { cx: "17", cy: "40", r: "16", strokeWidth: "5", strokeMiterlimit: "10", strokeLinejoin: "miter", strokeLinecap: "butt", fill: "none", stroke: "currentColor" }), /* @__PURE__ */ React.createElement("circle", { cx: "60", cy: "57", r: "30", strokeWidth: "5", strokeMiterlimit: "10", strokeLinejoin: "miter", strokeLinecap: "butt", fill: "none", stroke: "currentColor" }));
const contentSelectable = (c) => isCircleContent(c) || isArcContent(c) || isLineContent(c);
const icon2 = /* @__PURE__ */ React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 100 100" }, /* @__PURE__ */ React.createElement("polyline", { points: "10,87 89,87", strokeWidth: "5", strokeMiterlimit: "10", strokeLinejoin: "miter", strokeLinecap: "butt", fill: "none", stroke: "currentColor" }), /* @__PURE__ */ React.createElement("circle", { cx: "17", cy: "40", r: "16", strokeWidth: "5", strokeMiterlimit: "10", strokeLinejoin: "miter", strokeLinecap: "butt", fill: "none", stroke: "currentColor" }), /* @__PURE__ */ React.createElement("circle", { cx: "60", cy: "57", r: "30", strokeWidth: "5", strokeMiterlimit: "10", strokeLinejoin: "miter", strokeLinecap: "butt", fill: "none", stroke: "currentColor" }), /* @__PURE__ */ React.createElement("circle", { cx: "33", cy: "46", r: "8", fillOpacity: "1", strokeOpacity: "1", fill: "currentColor", stroke: "none" }), /* @__PURE__ */ React.createElement("circle", { cx: "60", cy: "87", r: "8", fillOpacity: "1", strokeOpacity: "1", fill: "currentColor", stroke: "none" }));
const command = {
name: "create tangent tangent radius circle",
useCommand({ onEnd, type, selected, scale }) {
Expand Down Expand Up @@ -2907,6 +2908,106 @@ function getCommand(ctx) {
};
},
selectCount: 0
},
{
name: "create tangent tangent radius circle at points",
useCommand({ onEnd, type, scale, contents }) {
const [start, setStart] = React.useState();
const [cursor, setCursor] = React.useState();
const [radius, setRadius] = React.useState();
const [result, setResult] = React.useState();
const assistentContents = [];
if (start && cursor && type) {
assistentContents.push({
points: [start.point, cursor],
type: "line",
dashArray: [4 / scale]
});
}
if (result) {
assistentContents.push({ ...result, type: "circle" });
}
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 };
};
let message = "";
if (type && !radius) {
message = "input radius";
}
const { input, setInputPosition, setCursorPosition, clearText, resetInput } = ctx.useCursorInput(message, type ? (e, text) => {
if (e.key === "Enter") {
const r = +text;
if (!isNaN(r)) {
setRadius(r);
clearText();
resetInput();
}
}
} : void 0);
const reset = () => {
setStart(void 0);
setResult(void 0);
setCursor(void 0);
setRadius(void 0);
clearText();
resetInput();
};
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 (result) {
onEnd({
updateContents: (contents2) => {
contents2.push({ ...result, type: "circle" });
}
});
reset();
}
},
onMove(p, viewportPosition, target) {
if (!type) return;
setCursor(p);
setResult(void 0);
setCursorPosition(p);
setInputPosition(viewportPosition || p);
if (!radius) return;
if (!target) return;
if (target.param === void 0) return;
if (!start) return;
const end = getTarget(p, target.id, target.param);
if (!end) return;
const center = ctx.getTwoGeneralFormLinesIntersectionPoint(
ctx.pointAndDirectionToGeneralFormLine(
start.point,
ctx.getGeometryLineTangentRadianAtParam(start.param, start.line)
),
ctx.pointAndDirectionToGeneralFormLine(
end.point,
ctx.getGeometryLineTangentRadianAtParam(end.param, end.line)
)
);
if (!center) return;
const circle = ctx.getCircleTangentTo2GeometryLinesNearParam(start.line, end.line, radius, start.param, end.param, center);
if (!circle) return;
setResult({ ...circle, r: radius });
},
input,
assistentContents,
reset
};
},
selectCount: 0,
icon: icon2
}
];
}
Expand Down
2 changes: 1 addition & 1 deletion main.bundle.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,4 @@ export * from './cone'
export * from './parabola'
export * from './hyperbola'
export * from './tangent-line'
export * from './tangent-circle'
79 changes: 79 additions & 0 deletions src/utils/tangent-circle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { newtonIterates } from "./equation-calculater";
import { GeometryLine, getGeometryLineDerivatives } from "./geometry-line";
import { delta2 } from "./math";
import { Position } from "./position";

export function getCircleTangentTo2GeometryLinesNearParam(curve1: GeometryLine, curve2: GeometryLine, radius: number, t1: number, t2: number, center: Position): Position | undefined {
const derivative1 = getGeometryLineDerivatives(curve1)
const derivative2 = getGeometryLineDerivatives(curve2)
const r = radius ** 2
const f1 = (t: number[]): number[] => {
const x = t[2], y = t[3]
// (y - y1)/(x - x1)y1'/x1' = -1
// (y - y2)/(x - x2)y2'/x2' = -1
// (x - x1)^2 + (y - y1)^2 = r
// (x - x2)^2 + (y - y2)^2 = r
// z1 = (x - x1)x1' + (y - y1)y1'
// z2 = (x - x2)x2' + (y - y2)y2'
// z3 = (x - x1)^2 + (y - y1)^2 - r
// z4 = (x - x2)^2 + (y - y2)^2 - r
const [{ x: x1, y: y1 }, { x: x11, y: y11 }] = derivative1[0](t[0])
const [{ x: x2, y: y2 }, { x: x21, y: y21 }] = derivative2[0](t[1])
return [
(x - x1) * x11 + (y - y1) * y11,
(x - x2) * x21 + (y - y2) * y21,
(x - x1) ** 2 + (y - y1) ** 2 - r,
(x - x2) ** 2 + (y - y2) ** 2 - r,
]
}
const f2 = (t: number[]): number[][] => {
const x = t[2], y = t[3]
const [{ x: x1, y: y1 }, { x: x11, y: y11 }, { x: x12, y: y12 }] = derivative1[1](t[0])
const [{ x: x2, y: y2 }, { x: x21, y: y21 }, { x: x22, y: y22 }] = derivative2[1](t[1])
// dz1/dt1 = -x1' x1' + (x - x1)x1'' - y1'y1' + (y - y1)y1''
// dz1/dt2 = 0
// dz1/dx = x1'
// dz1/dy = y1'
// dz2/dt1 = 0
// dz2/dt2 = -x2'x2' + (x - x2)x2'' + - y2'y2' + (y - y2)y2''
// dz2/dx = x2'
// dz2/dy = y2'
// dz3/dt1 = 2 x1'(x1 - x) + 2 y1'(y1 - y)
// dz3/dt2 = 0
// dz3/dx = 2(x - x1)
// dz3/dy = 2(y - y1)
// dz4/dt1 = 0
// dz4/dt2 = 2 x2'(x2 - x) + 2 y2'(y2 - y)
// dz4/dx = 2(x - x2)
// dz4/dy = 2(y - y2)
return [
[
-x11 * x11 + (x - x1) * x12 - y11 * y11 + (y - y1) * y12,
0,
x11,
y11,
],
[
0,
-x21 * x21 + (x - x2) * x22 + - y21 * y21 + (y - y2) * y22,
x21,
y21,
],
[
2 * x11 * (x1 - x) + 2 * y11 * (y1 - y),
0,
2 * (x - x1),
2 * (y - y1),
],
[
0,
2 * x21 * (x2 - x) + 2 * y21 * (y2 - y),
2 * (x - x2),
2 * (y - y2),
],
]
}
const result = newtonIterates([t1, t2, center.x, center.y], f1, f2, delta2)
if (!result) return
return { x: result[2], y: result[3] }
}

0 comments on commit 66eac43

Please sign in to comment.