Skip to content

Commit

Permalink
feat: parabola length 7d49991
Browse files Browse the repository at this point in the history
  • Loading branch information
plantain-00 committed Aug 13, 2024
1 parent 683e196 commit 39e296a
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 11 deletions.
17 changes: 15 additions & 2 deletions dev/cad-editor/plugins/parabola.plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export function getModel(ctx: PluginContext): model.Model<ParabolaContent> {
}
})
}
const React = ctx.React
return {
type: 'parabola',
...ctx.strokeModel,
Expand All @@ -43,6 +44,19 @@ export function getModel(ctx: PluginContext): model.Model<ParabolaContent> {
return target.renderPolyline(points, options)
},
getGeometries: getParabolaGeometries,
propertyPanel(content, update, contents, { acquirePoint }) {
return {
from: <ctx.Button onClick={() => acquirePoint(p => update(c => { if (isParabolaContent(c)) { c.x = p.x, c.y = p.y } }))}>canvas</ctx.Button>,
x: <ctx.NumberEditor value={content.x} setValue={(v) => update(c => { if (isParabolaContent(c)) { c.x = v } })} />,
y: <ctx.NumberEditor value={content.y} setValue={(v) => update(c => { if (isParabolaContent(c)) { c.y = v } })} />,
p: <ctx.NumberEditor value={content.p} setValue={(v) => update(c => { if (isParabolaContent(c) && v > 0) { c.p = v } })} />,
t1: <ctx.NumberEditor value={content.t1} setValue={(v) => update(c => { if (isParabolaContent(c)) { c.t1 = v } })} />,
t2: <ctx.NumberEditor value={content.t2} setValue={(v) => update(c => { if (isParabolaContent(c)) { c.t2 = v } })} />,
angle: <ctx.NumberEditor value={content.angle} setValue={(v) => update(c => { if (isParabolaContent(c)) { c.angle = v } })} />,
...ctx.getStrokeContentPropertyPanel(content, update, contents),
...ctx.getSegmentCountContentPropertyPanel(content, update),
}
},
isValid: (c, p) => ctx.validate(c, ParabolaContent, p),
getRefIds: ctx.getStrokeRefIds,
updateRefId: ctx.updateStrokeRefIds,
Expand All @@ -55,11 +69,10 @@ export function isParabolaContent(content: model.BaseContent): content is Parabo
}

export function getCommand(ctx: PluginContext): Command {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const React = ctx.React
const icon = (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<polygon points="52,5 97,50 52,96 6,50" strokeWidth="5" strokeMiterlimit="10" strokeLinejoin="miter" strokeLinecap="butt" fill="none" stroke="currentColor"></polygon>
<polyline points="99,3 98,7 97,10 96,14 95,18 94,21 93,25 92,28 91,31 90,34 89,38 88,41 87,44 86,46 85,49 84,52 83,55 82,57 81,60 80,62 79,64 78,67 77,69 76,71 75,73 75,75 74,77 73,79 72,80 71,82 70,84 69,85 68,87 67,88 66,89 65,90 64,91 63,93 62,93 61,94 60,95 59,96 58,97 57,97 56,98 55,98 54,98 53,99 52,99 51,99 50,99 49,99 48,99 47,99 46,98 45,98 44,98 43,97 42,97 41,96 40,95 39,94 38,93 37,93 36,91 35,90 34,89 33,88 32,87 31,85 30,84 29,82 28,80 27,79 26,77 26,75 25,73 24,71 23,69 22,67 21,64 20,62 19,60 18,57 17,55 16,52 15,49 14,46 13,44 12,41 11,38 10,34 9,31 8,28 7,25 6,21 5,18 4,14 3,10 2,7 1,3" strokeWidth="5" strokeMiterlimit="10" strokeLinejoin="miter" strokeLinecap="butt" strokeOpacity="1" fill="none" stroke="currentColor"></polyline>
</svg>
)
return {
Expand Down
44 changes: 43 additions & 1 deletion dev/cad-editor/plugins/variables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7511,6 +7511,7 @@ function getModel(ctx) {
};
});
}
const React = ctx.React;
return {
type: "parabola",
...ctx.strokeModel,
Expand All @@ -7523,6 +7524,47 @@ function getModel(ctx) {
return target.renderPolyline(points, options);
},
getGeometries: getParabolaGeometries,
propertyPanel(content, update, contents, { acquirePoint }) {
return {
from: /* @__PURE__ */ React.createElement(ctx.Button, { onClick: () => acquirePoint((p) => update((c) => {
if (isParabolaContent(c)) {
c.x = p.x, c.y = p.y;
}
})) }, "canvas"),
x: /* @__PURE__ */ React.createElement(ctx.NumberEditor, { value: content.x, setValue: (v) => update((c) => {
if (isParabolaContent(c)) {
c.x = v;
}
}) }),
y: /* @__PURE__ */ React.createElement(ctx.NumberEditor, { value: content.y, setValue: (v) => update((c) => {
if (isParabolaContent(c)) {
c.y = v;
}
}) }),
p: /* @__PURE__ */ React.createElement(ctx.NumberEditor, { value: content.p, setValue: (v) => update((c) => {
if (isParabolaContent(c) && v > 0) {
c.p = v;
}
}) }),
t1: /* @__PURE__ */ React.createElement(ctx.NumberEditor, { value: content.t1, setValue: (v) => update((c) => {
if (isParabolaContent(c)) {
c.t1 = v;
}
}) }),
t2: /* @__PURE__ */ React.createElement(ctx.NumberEditor, { value: content.t2, setValue: (v) => update((c) => {
if (isParabolaContent(c)) {
c.t2 = v;
}
}) }),
angle: /* @__PURE__ */ React.createElement(ctx.NumberEditor, { value: content.angle, setValue: (v) => update((c) => {
if (isParabolaContent(c)) {
c.angle = v;
}
}) }),
...ctx.getStrokeContentPropertyPanel(content, update, contents),
...ctx.getSegmentCountContentPropertyPanel(content, update)
};
},
isValid: (c, p) => ctx.validate(c, ParabolaContent, p),
getRefIds: ctx.getStrokeRefIds,
updateRefId: ctx.updateStrokeRefIds,
Expand All @@ -7534,7 +7576,7 @@ function isParabolaContent(content) {
}
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("polygon", { points: "52,5 97,50 52,96 6,50", strokeWidth: "5", strokeMiterlimit: "10", strokeLinejoin: "miter", strokeLinecap: "butt", fill: "none", stroke: "currentColor" }));
const icon = /* @__PURE__ */ React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 100 100" }, /* @__PURE__ */ React.createElement("polyline", { points: "99,3 98,7 97,10 96,14 95,18 94,21 93,25 92,28 91,31 90,34 89,38 88,41 87,44 86,46 85,49 84,52 83,55 82,57 81,60 80,62 79,64 78,67 77,69 76,71 75,73 75,75 74,77 73,79 72,80 71,82 70,84 69,85 68,87 67,88 66,89 65,90 64,91 63,93 62,93 61,94 60,95 59,96 58,97 57,97 56,98 55,98 54,98 53,99 52,99 51,99 50,99 49,99 48,99 47,99 46,98 45,98 44,98 43,97 42,97 41,96 40,95 39,94 38,93 37,93 36,91 35,90 34,89 33,88 32,87 31,85 30,84 29,82 28,80 27,79 26,77 26,75 25,73 24,71 23,69 22,67 21,64 20,62 19,60 18,57 17,55 16,52 15,49 14,46 13,44 12,41 11,38 10,34 9,31 8,28 7,25 6,21 5,18 4,14 3,10 2,7 1,3", strokeWidth: "5", strokeMiterlimit: "10", strokeLinejoin: "miter", strokeLinecap: "butt", strokeOpacity: "1", fill: "none", stroke: "currentColor" }));
return {
name: "create parabola",
icon,
Expand Down
2 changes: 1 addition & 1 deletion main.bundle.js

Large diffs are not rendered by default.

14 changes: 13 additions & 1 deletion src/utils/geometry-line.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,19 @@ export function getGeometryLinesPointAtParam(param: number, lines: GeometryLine[
}

function getAngleAtParam(param: number, range: AngleRange) {
return param * (range.endAngle - range.startAngle) + range.startAngle
return getParamAtPercent(param, range.startAngle, range.endAngle)
}

export function getParamAtPercent(percent: number, start: number, end: number) {
return percent * (end - start) + start
}

export function getPercentAtParam(param: number, start: number, end: number) {
return (param - start) / (end - start)
}

export function getPercentAtAngle(angle: number, range: AngleRange) {
return getPercentAtParam(angle, range.startAngle, range.endAngle)
}

export function getPartOfGeometryLine(param1: number, param2: number, line: GeometryLine, closed = false): GeometryLine {
Expand Down
8 changes: 4 additions & 4 deletions src/utils/length.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { ellipseToEllipseArc } from "./ellipse"
import { EllipseArc } from "./ellipse"
import { Arc, Circle } from "./circle"
import { Ellipse } from "./ellipse"
import { GeometryLine, getGeometryLinePointAndTangentRadianAtParam } from "./geometry-line"
import { GeometryLine, getGeometryLinePointAndTangentRadianAtParam, getPercentAtAngle } from "./geometry-line"
import { QuadraticCurve } from "./bezier"
import { BezierCurve } from "./bezier"
import { getNurbsCurveLength, getNurbsCurveParamByLength, getNurbsMaxParam } from "./nurbs"
Expand Down Expand Up @@ -161,13 +161,13 @@ export function getGeometryLineParamByLength(line: GeometryLine, length: number)
}
if (line.type === 'arc') {
const angle = radianToAngle(getArcRadianByLength(line.curve, length))
return (angle - line.curve.startAngle) / (line.curve.endAngle - line.curve.startAngle)
return getPercentAtAngle(angle, line.curve)
}
if (line.type === 'ellipse arc') {
const radian = getEllipseArcRadianByLength(line.curve, length)
if (radian === undefined) return
const angle = radianToAngle()
return (angle - line.curve.startAngle) / (line.curve.endAngle - line.curve.startAngle)
const angle = radianToAngle(radian)
return getPercentAtAngle(angle, line.curve)
}
if (line.type === 'quadratic curve') {
return getQuadraticCurvePercentByLength(line.curve, length)
Expand Down
74 changes: 72 additions & 2 deletions src/utils/parabola.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { getPointsBoundingUnsafe } from "./bounding"
import { calculateEquation3 } from "./equation-calculater"
import { delta2, isBetween, isZero, minimumBy } from "./math"
import { calculateEquation2, calculateEquation3, newtonIterate } from "./equation-calculater"
import { rombergIntegral } from "./length"
import { delta2, delta3, ExtendType, getTwoNumberCenter, isBetween, isSameNumber, isZero, minimumBy } from "./math"
import { getTwoPointsDistance, Position } from "./position"
import { angleToRadian } from "./radian"
import { TwoPointsFormRegion } from "./region"
Expand Down Expand Up @@ -55,6 +56,18 @@ export function getParabolaPointAtParam(parabola: Parabola, param: number): Posi
return transformPointFromCoordinate2D(getParabolaCoordinatePointAtParam(parabola, param), parabola, getParabolaXAxisRadian(parabola))
}

export function getParabolaParamAtPoint({ angle, x: x1, y: y1 }: Parabola, point: Position): number {
const xAxisRadian = getParabolaXAxisRadian({ angle })
const e1 = Math.sin(xAxisRadian), e2 = Math.cos(xAxisRadian)
// x = x1 + e2 t - 2 e1 p t^2
// y = y1 + e1 t + 2 e2 p t^2
// e2 x = e2 x1 + e2 e2 t - 2 e1 e2 p t^2
// e1 y = e1 y1 + e1 e1 t + 2 e1 e2 p t^2
// e2 x + e1 y = e2 x1 + e1 y1 + t
// t = e2(x - x1) + e1(y - y1)
return e2 * (point.x - x1) + e1 * (point.y - y1)
}

export function getParabolaCoordinatePointAtParam(parabola: Parabola, param: number): Position {
return { x: param, y: 2 * parabola.p * param ** 2 }
}
Expand Down Expand Up @@ -107,3 +120,60 @@ export function getParabolaBounding(curve: ParabolaSegment): TwoPointsFormRegion
}
return getPointsBoundingUnsafe(points)
}

export function getParabolaLength(curve: ParabolaSegment, delta?: number): number {
const { p, t1, t2 } = curve
// let xAxisRadian = getParabolaXAxisRadian({ curve.angle })
// let e1 = Math.sin(xAxisRadian), e2 = Math.cos(xAxisRadian)
// x = x1 + e2 t - 2 e1 p t^2
// y = y1 + e1 t + 2 e2 p t^2
// x' = e2 - 4 e1 p t = dx/dt
// y' = 4 e2 p t + e1 = dy/dt
// dz = sqrt(dx^2 + dy^2)
// dz/dt = sqrt((e2 - 4 e1 p t)^2 + (4 e2 p t + e1)^2)
// dz/dt = sqrt((16 e1 e1 p p + 16 e2 e2 p p) t t + e2 e2 + e1 e1)
// dz/dt = sqrt(16 p p t t + 1)
const a = 16 * p * p
return rombergIntegral(t1, t2, t => Math.sqrt(a * t * t + 1), delta)
}

export function getParabolaParamByLength(curve: ParabolaSegment, length: number, delta = delta2): number | undefined {
const f1 = (t: number) => getParabolaLength({ ...curve, t2: t }) - length
// dz/dt = sqrt(16 p p t t + 1)
const a = 16 * curve.p * curve.p
const f2 = (t: number) => Math.sqrt(a * t * t + 1)
return newtonIterate(getTwoNumberCenter(curve.t1, curve.t2), f1, f2, delta)
}

export function getParabolaSegmentStartAndEnd(curve: ParabolaSegment) {
return {
start: getParabolaPointAtParam(curve, curve.t1),
end: getParabolaPointAtParam(curve, curve.t2),
}
}

export function pointIsOnParabola(point: Position, { angle, p, x: x1, y: y1 }: Parabola): boolean {
const xAxisRadian = getParabolaXAxisRadian({ angle })
const e1 = Math.sin(xAxisRadian), e2 = Math.cos(xAxisRadian)
// x = x1 + e2 t - 2 e1 p t^2
// y = y1 + e1 t + 2 e2 p t^2
return calculateEquation2(- 2 * e1 * p, e2, x1 - point.x)
.filter(t => isSameNumber(2 * e2 * p * t * t + e1 * t + y1, point.y, delta3)).length > 0
}

export function pointIsOnParabolaSegment(point: Position, curve: ParabolaSegment, extend: ExtendType = { body: true }): boolean {
if (extend.head && extend.body && extend.tail) return true
if (!extend.head && !extend.body && !extend.tail) return false
const t = getParabolaParamAtPoint(curve, point)
return isBetween(t, curve.t1, curve.t2, extend)
}

export function getParabolaTangentRadianAtParam({ angle, p }: ParabolaSegment, t: number): number {
const xAxisRadian = getParabolaXAxisRadian({ angle })
const e1 = Math.sin(xAxisRadian), e2 = Math.cos(xAxisRadian)
// x = x1 + e2 t - 2 e1 p t^2
// y = y1 + e1 t + 2 e2 p t^2
// x' = e2 - 4 e1 p t
// y' = 4 e2 p t + e1
return Math.atan2(4 * e2 * p * t + e1, e2 - 4 * e1 * p * t)
}

0 comments on commit 39e296a

Please sign in to comment.