Skip to content

Commit

Permalink
feat: length 824355a
Browse files Browse the repository at this point in the history
  • Loading branch information
plantain-00 committed Nov 8, 2023
1 parent 111a446 commit c77f530
Show file tree
Hide file tree
Showing 16 changed files with 141 additions and 36 deletions.
4 changes: 2 additions & 2 deletions dev/cad-editor/plugins/circle-arc.plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ export function getModel(ctx: PluginContext) {
const x = ctx.getTimeExpressionValue(content.xExpression, time, content.x)
const y = ctx.getTimeExpressionValue(content.yExpression, time, content.y)
const r = ctx.getTimeExpressionValue(content.rExpression, time, content.r)
return { quadrantPoints, ...getArcGeometries({ ...content, x, y, r, startAngle: 0, endAngle: 360 }) }
return { quadrantPoints, ...getArcGeometries(ctx.circleToArc({ ...content, x, y, r })) }
}
return geometriesCache.get(content, () => {
return { quadrantPoints, ...getArcGeometries({ ...content, startAngle: 0, endAngle: 360 }) }
return { quadrantPoints, ...getArcGeometries(ctx.circleToArc(content)) }
})
}
function getArcGeometries(content: Omit<ArcContent, "type">) {
Expand Down
6 changes: 1 addition & 5 deletions dev/cad-editor/plugins/ellipse.plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,7 @@ export function getModel(ctx: PluginContext) {
return {
lines: [{
type: 'ellipse arc' as const,
curve: {
...content,
startAngle: 0,
endAngle: 360,
},
curve: ctx.ellipseToEllipseArc(content),
}],
points,
center, left, right, top, bottom,
Expand Down
4 changes: 2 additions & 2 deletions dev/cad-editor/plugins/ring.plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ export function getModel(ctx: PluginContext): model.Model<RingContent> {
function getRingGeometriesFromCache(content: Omit<RingContent, "type">) {
return ctx.getGeometriesFromCache(content, () => {
const angleDelta = content.angleDelta ?? ctx.defaultAngleDelta
const arc1 = { ...content, r: content.outerRadius, startAngle: 0, endAngle: 360 }
const arc2 = { ...content, r: content.innerRadius, startAngle: 0, endAngle: 360 }
const arc1 = ctx.circleToArc({ ...content, r: content.outerRadius })
const arc2 = ctx.circleToArc({ ...content, r: content.innerRadius })
const points1 = ctx.arcToPolyline(arc1, angleDelta)
const points2 = ctx.arcToPolyline(arc2, angleDelta)
const points = [...points1, ...points2]
Expand Down
2 changes: 1 addition & 1 deletion dev/cad-editor/plugins/time-axis.plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export function getModel(ctx: PluginContext): model.Model<TimeAxisContent> {
renderingLines: ctx.dashedPolylineToLines(points, content.dashArray),
}
if (time) {
const timePoints = ctx.arcToPolyline({ x: content.x + time / 10, y: content.y, r: 5, startAngle: 0, endAngle: 360 }, ctx.defaultAngleDelta)
const timePoints = ctx.arcToPolyline(ctx.circleToArc({ x: content.x + time / 10, y: content.y, r: 5 }), ctx.defaultAngleDelta)
result.regions.push({
points: timePoints,
lines: Array.from(ctx.iteratePolygonLines(timePoints)),
Expand Down
16 changes: 6 additions & 10 deletions dev/cad-editor/plugins/variables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -894,10 +894,10 @@ function getModel(ctx) {
const x = ctx.getTimeExpressionValue(content.xExpression, time, content.x);
const y = ctx.getTimeExpressionValue(content.yExpression, time, content.y);
const r = ctx.getTimeExpressionValue(content.rExpression, time, content.r);
return { quadrantPoints, ...getArcGeometries({ ...content, x, y, r, startAngle: 0, endAngle: 360 }) };
return { quadrantPoints, ...getArcGeometries(ctx.circleToArc({ ...content, x, y, r })) };
}
return geometriesCache.get(content, () => {
return { quadrantPoints, ...getArcGeometries({ ...content, startAngle: 0, endAngle: 360 }) };
return { quadrantPoints, ...getArcGeometries(ctx.circleToArc(content)) };
});
}
function getArcGeometries(content) {
Expand Down Expand Up @@ -2528,11 +2528,7 @@ function getModel(ctx) {
return {
lines: [{
type: "ellipse arc",
curve: {
...content,
startAngle: 0,
endAngle: 360
}
curve: ctx.ellipseToEllipseArc(content)
}],
points,
center,
Expand Down Expand Up @@ -7856,8 +7852,8 @@ function getModel(ctx) {
return ctx.getGeometriesFromCache(content, () => {
var _a;
const angleDelta = (_a = content.angleDelta) != null ? _a : ctx.defaultAngleDelta;
const arc1 = { ...content, r: content.outerRadius, startAngle: 0, endAngle: 360 };
const arc2 = { ...content, r: content.innerRadius, startAngle: 0, endAngle: 360 };
const arc1 = ctx.circleToArc({ ...content, r: content.outerRadius });
const arc2 = ctx.circleToArc({ ...content, r: content.innerRadius });
const points1 = ctx.arcToPolyline(arc1, angleDelta);
const points2 = ctx.arcToPolyline(arc2, angleDelta);
const points = [...points1, ...points2];
Expand Down Expand Up @@ -10117,7 +10113,7 @@ function getModel(ctx) {
renderingLines: ctx.dashedPolylineToLines(points, content.dashArray)
};
if (time) {
const timePoints = ctx.arcToPolyline({ x: content.x + time / 10, y: content.y, r: 5, startAngle: 0, endAngle: 360 }, ctx.defaultAngleDelta);
const timePoints = ctx.arcToPolyline(ctx.circleToArc({ x: content.x + time / 10, y: content.y, r: 5 }), ctx.defaultAngleDelta);
result.regions.push({
points: timePoints,
lines: Array.from(ctx.iteratePolygonLines(timePoints))
Expand Down
4 changes: 2 additions & 2 deletions dev/circuit-graph-editor/plugins/switch.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from "react"
import { arcToPolyline, BooleanEditor, getPointByLengthAndDirection, getTwoPointCenter, getTwoPointsDistance, iteratePolylineLines, Nullable, Position, rotatePositionByCenter } from "../../../src"
import { arcToPolyline, BooleanEditor, circleToArc, getPointByLengthAndDirection, getTwoPointCenter, getTwoPointsDistance, iteratePolylineLines, Nullable, Position, rotatePositionByCenter } from "../../../src"
import { BaseContent, BaseDevice, deviceGeometryCache, deviceModel, Geometries, getDeviceText, getReference, isJunctionContent, Model } from "../model"

export type SwitchDevice = BaseDevice<'switch'> & {
Expand Down Expand Up @@ -81,7 +81,7 @@ function getSwitchGeometriesFromCache(content: Omit<SwitchDevice, "type">, conte
const radius = 3
const lines: [Position, Position][] = [
[start.position, getPointByLengthAndDirection(p1, radius, start.position)],
...iteratePolylineLines(arcToPolyline({ x: p1.x, y: p1.y, r: radius, startAngle: 0, endAngle: 360 }, 5)),
...iteratePolylineLines(arcToPolyline(circleToArc({ x: p1.x, y: p1.y, r: radius }), 5)),
]
if (content.on) {
lines.push([getPointByLengthAndDirection(p1, radius, end.position), end.position])
Expand Down
6 changes: 3 additions & 3 deletions dev/point-snap.story.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from "react"
import { allSnapTypes, Circle, iterateIntersectionPoints, Position, usePointSnap, WeakmapCache2 } from "../src"
import { allSnapTypes, Circle, circleToArc, iterateIntersectionPoints, Position, usePointSnap, WeakmapCache2 } from "../src"

const intersectionPointsCache = new WeakmapCache2<Circle, Circle, Position[]>()
const contents: Circle[] = [{ x: 300, y: 200, r: 100 }, { x: 450, y: 350, r: 130 }]
Expand All @@ -9,7 +9,7 @@ export default () => {
const startPosition = { x: 500, y: 100 }
const { getSnapAssistentContents, getSnapPoint } = usePointSnap<Circle>(
true,
(c1, c2) => intersectionPointsCache.get(c1, c2, () => Array.from(iterateIntersectionPoints(c1, c2, contents, () => ({ getGeometries: (c) => ({ lines: [{ type: 'arc', curve: { ...c, startAngle: 0, endAngle: 360 } }] }) })))),
(c1, c2) => intersectionPointsCache.get(c1, c2, () => Array.from(iterateIntersectionPoints(c1, c2, contents, () => ({ getGeometries: (c) => ({ lines: [{ type: 'arc', curve: circleToArc(c) }] }) })))),
allSnapTypes,
() => ({
getSnapPoints(c) {
Expand All @@ -23,7 +23,7 @@ export default () => {
},
getGeometries(c) {
return {
lines: [{ type: 'arc', curve: { ...c, startAngle: 0, endAngle: 360 } }],
lines: [{ type: 'arc', curve: circleToArc(c) }],
bounding: {
start: { x: c.x - c.r, y: c.y - c.r },
end: { x: c.x + c.r, y: c.y + c.r },
Expand Down
2 changes: 1 addition & 1 deletion main.bundle.js

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions spec/length.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import test from 'ava'
import { rombergIntegral } from '../src'

test('rombergIntegral', (t) => {
t.snapshot(rombergIntegral(0.8478111820247928, 2.90107925830033, t => Math.sqrt(10000 * Math.sin(t) ** 2 + 22500 * Math.cos(t) ** 2)))
})
11 changes: 11 additions & 0 deletions spec/length.ts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Snapshot report for `spec/length.ts`

The actual snapshot is saved in `length.ts.snap`.

Generated by [AVA](https://avajs.dev).

## rombergIntegral

> Snapshot 1
241.31511665318624
Binary file added spec/length.ts.snap
Binary file not shown.
6 changes: 3 additions & 3 deletions src/components/react-render-target/create-webgl-renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { getImageFromCache } from './image-loader'
import { Filter, LinearGradient, PathLineStyleOptions, RadialGradient } from './react-render-target'
import { colorNumberToRec, getColorString, mergeOpacities, mergeOpacityToColor } from '../../utils/color'
import { m3, Matrix } from '../../utils/matrix'
import { arcToPolyline, Bounding, dashedPolylineToLines, equals, getParallelLinesByDistance, getPointSideOfLine, getTwoPointsDistance, isZero, polygonToPolyline, Position, Size, twoPointLineToGeneralFormLine } from '../../utils/geometry'
import { arcToPolyline, Bounding, circleToArc, dashedPolylineToLines, equals, getParallelLinesByDistance, getPointSideOfLine, getTwoPointsDistance, isZero, polygonToPolyline, Position, Size, twoPointLineToGeneralFormLine } from '../../utils/geometry'
import { WeakmapCache, WeakmapMap3Cache, WeakmapMapCache } from '../../utils/weakmap-cache'
import { Vec2, Vec4 } from '../../utils/types'
import { angleToRadian } from '../../utils/radian'
Expand Down Expand Up @@ -1069,14 +1069,14 @@ function getRadialGradientGraphic(radialGradient: RadialGradient, points: Positi
const circle = { x: start.x + offset.x * s.offset, y: start.y + offset.y * s.offset, r: start.r + offset.r * s.offset }
return {
color: colorNumberToRec(s.color, s.opacity),
points: arcToPolyline({ x: circle.x, y: circle.y, r: circle.r, startAngle: 0, endAngle: 360 }, 5),
points: arcToPolyline(circleToArc(circle), 5),
}
})
const maxDistance = Math.max(...points.map(p => getTwoPointsDistance(p, end)))
if (maxDistance > end.r) {
stopPoints.push({
color: stopPoints[stopPoints.length - 1].color,
points: arcToPolyline({ x: end.x, y: end.y, r: maxDistance, startAngle: 0, endAngle: 360 }, 5),
points: arcToPolyline(circleToArc({ x: end.x, y: end.y, r: maxDistance }), 5),
})
}
const fillTriangles: number[][] = []
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from "react"
import { arcToPolyline, dashedPolylineToLines, ellipseArcToPolyline, ellipseToPolygon, polygonToPolyline, rotatePosition } from "../../utils/geometry"
import { arcToPolyline, circleToArc, dashedPolylineToLines, ellipseArcToPolyline, ellipseToPolygon, polygonToPolyline, rotatePosition } from "../../utils/geometry"
import { getPathCommandsPoints, pathCommandPointsToPath, ReactRenderTarget, renderPartStyledPolyline, RenderTransform } from "./react-render-target"
import { createWebglRenderer, getGroupGraphics, getImageGraphic, getPathGraphics, getTextGraphic, Graphic, PatternGraphic } from "./create-webgl-renderer"
import { Matrix } from "../../utils/matrix"
Expand Down Expand Up @@ -71,7 +71,7 @@ export const reactWebglRenderTarget: ReactRenderTarget<WebglDraw> = {
return this.renderPolyline(points, { ...options, closed: true })
},
renderCircle(cx, cy, r, options) {
const points = arcToPolyline({ x: cx, y: cy, r, startAngle: 0, endAngle: 360 }, 5)
const points = arcToPolyline(circleToArc({ x: cx, y: cy, r }), 5)
return this.renderPolyline(points, options)
},
renderEllipse(cx, cy, rx, ry, options) {
Expand Down
21 changes: 16 additions & 5 deletions src/utils/geometry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -968,11 +968,6 @@ function getAngleRange(range: AngleRange, angleDelta: number) {
return angles
}

export function getArcLength(arc: Arc) {
const endAngle = getFormattedEndAngle(arc)
return arc.r * angleToRadian(Math.abs(endAngle - arc.startAngle))
}

export function getPolygonArea(points: Position[]) {
let result = 0
for (let i = 0; i < points.length; i++) {
Expand Down Expand Up @@ -1006,6 +1001,22 @@ export function getEllipseArcPointAtAngle(content: EllipseArc, angle: number) {
return getEllipsePointAtRadian(content, angleToRadian(angle))
}

export function circleToArc(circle: Circle): Arc {
return {
...circle,
startAngle: 0,
endAngle: 360,
}
}

export function ellipseToEllipseArc(ellipse: Ellipse): EllipseArc {
return {
...ellipse,
startAngle: 0,
endAngle: 360,
}
}

/**
* @public
*/
Expand Down
1 change: 1 addition & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ export * from './triangles'
export * from './nurbs'
export * from './parallel'
export * from './factorial'
export * from './length'
84 changes: 84 additions & 0 deletions src/utils/length.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { Arc, Circle, Ellipse, EllipseArc, ellipseToEllipseArc, getFormattedEndAngle } from "./geometry"
import { BezierCurve, QuadraticCurve } from "./intersection"
import { angleToRadian } from "./radian"

export function getCircleLength(circle: Circle) {
return 2 * Math.PI * circle.r
}

export function getArcLength(arc: Arc) {
const endAngle = getFormattedEndAngle(arc)
return arc.r * angleToRadian(Math.abs(endAngle - arc.startAngle))
}

export function getEllipseLength(ellipse: Ellipse, delta?: number) {
return getEllipseArcLength(ellipseToEllipseArc(ellipse), delta)
}

export function getEllipseArcLength(ellipse: EllipseArc, delta?: number) {
// let d1 = Math.sin(radian), d2 = Math.cos(radian)
// x = d2 rx cos(t) - d1 ry sin(t) + cx
// y = d1 rx cos(t) + d2 ry sin(t) + cy
// x' = -d2 rx sin(t) - d1 ry cos(t) = dx/dt
// y' = -d1 rx sin(t) + d2 ry cos(t) = dy/dt
// dz = sqrt(dx^2 + dy^2)
// dz/dt = sqrt(cos(t) cos(t) d1 d1 ry ry + cos(t) cos(t) d2 d2 ry ry + d1 d1 rx rx sin(t) sin(t) + d2 d2 rx rx sin(t) sin(t))
// dz/dt = sqrt(cos(t) cos(t)ry ry + rx rx sin(t) sin(t))
const e1 = ellipse.rx ** 2, e2 = ellipse.ry ** 2
// dz/dt = sqrt(e1 sin(t) sin(t) + e2 cos(t) cos(t))
const radian1 = angleToRadian(ellipse.startAngle), radian2 = angleToRadian(getFormattedEndAngle(ellipse))
return rombergIntegral(Math.min(radian1, radian2), Math.max(radian1, radian2), t => Math.sqrt(e1 * Math.sin(t) ** 2 + e2 * Math.cos(t) ** 2), delta)
}

export function getQuadraticCurveLength({ from: { x: a1, y: b1 }, cp: { x: a2, y: b2 }, to: { x: a3, y: b3 } }: QuadraticCurve, delta?: number) {
const c1 = a2 - a1, c2 = a3 - a2 - c1, c3 = b2 - b1, c4 = b3 - b2 - c3
// x = c2 t t + 2 c1 t + a1
// y = c4 t t + 2 c3 t + b1
// x' = 2 c2 t + 2 c1 = dx/dt
// y' = 2 c4 t + 2 c3 = dy/dt
// dz = sqrt(dx^2 + dy^2)
// dz/dt = 2 sqrt((c2 t + c1)^2 + (c4 t + c3)^2)
// dz/dt = 2 sqrt((c2 c2 + c4 c4) t t + (2 c1 c2 + 2 c3 c4) t + c1 c1 + c3 c3)
const e1 = c2 * c2 + c4 * c4, e2 = 2 * c1 * c2 + 2 * c3 * c4, e3 = c1 * c1 + c3 * c3
// dz/dt = 2 sqrt(e1 t t + e2 t + e3)
return rombergIntegral(0, 1, t => 2 * Math.sqrt(e1 * t * t + e2 * t + e3), delta)
}

export function getBezierCurveLength({ from: { x: a1, y: b1 }, cp1: { x: a2, y: b2 }, cp2: { x: a3, y: b3 }, to: { x: a4, y: b4 } }: BezierCurve) {
const c1 = -a1 + 3 * a2 + -3 * a3 + a4, c2 = 3 * (a1 - 2 * a2 + a3), c3 = 3 * (a2 - a1)
const d1 = -b1 + 3 * b2 + -3 * b3 + b4, d2 = 3 * (b1 - 2 * b2 + b3), d3 = 3 * (b2 - b1)
// x = c1 t t t + c2 t t + c3 t + a1
// y = d1 t t t + d2 t t + d3 t + b1
// x' = 3 c1 t t + 2 c2 t + c3 = dx/dt
// y' = 3 d1 t t + 2 d2 t + d3 = dy/dt
// dz = sqrt(dx^2 + dy^2)
// dz/dt = sqrt((3 c1 t t + 2 c2 t + c3)^2 + (3 d1 t t + 2 d2 t + d3)^2)
// dz/dt = sqrt((9 c1 c1 + 9 d1 d1) t t t t + (12 c1 c2 + 12 d1 d2) t t t + (4 c2 c2 + 6 c1 c3 + 4 d2 d2 + 6 d1 d3) t t + (4 c2 c3 + 4 d2 d3) t + c3 c3 + d3 d3)
const e1 = 9 * c1 * c1 + 9 * d1 * d1, e2 = 12 * c1 * c2 + 12 * d1 * d2, e3 = 4 * c2 * c2 + 6 * c1 * c3 + 4 * d2 * d2 + 6 * d1 * d3, e4 = 4 * c2 * c3 + 4 * d2 * d3, e5 = c3 * c3 + d3 * d3
// dz/dt = sqrt(e1 t t t t + e2 t t t + e3 t t + e4 t + e5)
return rombergIntegral(0, 1, t => Math.sqrt(e1 * t * t * t * t + e2 * t * t * t + e3 * t * t + e4 * t + e5))
}

export function cotesIntegral(a: number, b: number, f: (t: number) => number, count?: number): number {
if (count && Number.isInteger(count) && count > 1) {
let result = 0
const d = (b - a) / count
for (let i = 0; i < count; i++) {
result += cotesIntegral(a + i * d, a + (i + 1) * d, f)
}
return result
}
const h = (b - a) / 4
return (b - a) * (7 * f(a) + 32 * f(a + h) + 12 * f(a + 2 * h) + 32 * f(a + 3 * h) + 7 * f(b)) / 90
}

export function rombergIntegral(a: number, b: number, f: (t: number) => number, delta = 1e-2): number {
let p: number | undefined
for (let count = 1; ; count *= 2) {
const c = cotesIntegral(a, b, f, count)
if (p && Math.abs(p - c) < delta) {
return c
}
p = c
}
}

0 comments on commit c77f530

Please sign in to comment.