Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/angle utils #189

Merged
merged 2 commits into from
May 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"comment": "feat: add `calculateMaxRadius()` and `findBoundaryAngles()`\n\n",
"type": "none",
"packageName": "@visactor/vutils"
}
],
"packageName": "@visactor/vutils",
"email": "dingling112@gmail.com"
}
32 changes: 31 additions & 1 deletion packages/vutils/__tests__/angle.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { normalizeAngle } from '../src';
import { findBoundaryAngles, normalizeAngle, polarToCartesian, calculateMaxRadius } from '../src';

describe('angle utils', () => {
it('normalizeAngle', () => {
Expand All @@ -11,4 +11,34 @@ describe('angle utils', () => {
expect(normalizeAngle(-2 * Math.PI)).toBe(0);
expect(normalizeAngle(-Math.PI)).toBe(Math.PI);
});

it('polarToCartesian', () => {
expect(polarToCartesian({ x: 0, y: 0 }, undefined, undefined)).toEqual({ x: 0, y: 0 });
});

it('findBoundaryAngles', () => {
expect(findBoundaryAngles(Math.PI / 4, Math.PI)).toEqual([Math.PI / 4, Math.PI, Math.PI / 2]);
expect(findBoundaryAngles(Math.PI / 4, 1.5 * Math.PI)).toEqual([Math.PI / 4, 1.5 * Math.PI, Math.PI / 2, Math.PI]);
expect(findBoundaryAngles(Math.PI / 4, 3 * Math.PI)).toEqual([0, Math.PI / 2, Math.PI, 1.5 * Math.PI]);

expect(findBoundaryAngles(-Math.PI, -Math.PI / 4)).toEqual([Math.PI, 1.75 * Math.PI, 1.5 * Math.PI]);

expect(findBoundaryAngles(-0.2 * Math.PI, -0.8 * Math.PI)).toEqual([1.2 * Math.PI, 1.8 * Math.PI, 1.5 * Math.PI]);
});

it('calculateMaxRadius', () => {
expect(
calculateMaxRadius({ width: 400, height: 300 }, { x: 200, y: 120 }, -1.25 * Math.PI, 0.25 * Math.PI)
).toBeCloseTo(120);
expect(
calculateMaxRadius({ width: 400, height: 300 }, { x: 200, y: 200 }, -1.25 * Math.PI, 0.25 * Math.PI)
).toBeCloseTo(141.42135623730948);

expect(
calculateMaxRadius({ width: 400, height: 500 }, { x: 200, y: 150 }, -0.8 * Math.PI, 0.1 * Math.PI)
).toBeCloseTo(150);
expect(
calculateMaxRadius({ width: 400, height: 500 }, { x: 200, y: 350 }, -0.8 * Math.PI, 0.1 * Math.PI)
).toBeCloseTo(157.71933363574007);
});
});
83 changes: 83 additions & 0 deletions packages/vutils/src/angle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ export const clampAngleByDegree = clampDegree;
* @returns 返回笛卡尔坐标点
*/
export function polarToCartesian(center: IPointLike, radius: number, angleInRadian: number): { x: number; y: number } {
if (!radius) {
return { x: center.x, y: center.y };
}

return {
x: center.x + radius * Math.cos(angleInRadian),
y: center.y + radius * Math.sin(angleInRadian)
Expand Down Expand Up @@ -98,3 +102,82 @@ export function normalizeAngle(angle: number): number {
}
return angle;
}

/**
* 指定开始角度和结束角度,计算这个范围内的边界角度,
* 即起始角度以及经过的东、南、西、北四个方向的角度
* 计算这个角度,可以用于计算一个弧度的bounding box 等
* @param startAngle 起始角度的弧度值
* @param endAngle 结束角度的弧度值
* @returns 边界角度数组
*/
export function findBoundaryAngles(startAngle: number, endAngle: number) {
const deltaAngle = Math.abs(endAngle - startAngle);

if (deltaAngle >= 2 * Math.PI || 2 * Math.PI - deltaAngle < 1e-6) {
return [0, Math.PI / 2, Math.PI, 1.5 * Math.PI];
}
const min = Math.min(startAngle, endAngle);
const normalMin = normalizeAngle(min);
const normalMax = normalMin + deltaAngle;
const steps = [normalMin, normalMax];
let directionAngle = (Math.floor(normalMin / Math.PI) * Math.PI) / 2;

while (directionAngle < normalMax) {
if (directionAngle > normalMin) {
steps.push(directionAngle);
}
directionAngle += Math.PI / 2;
}

return steps;
}
/**
* 计算指定范围内,指定中心的情况下,不超出边界的最大可用半径
* @param rect 矩形的大小
* @param center 中心点
* @param startAngle 起始角度的弧度值
* @param endAngle 结束角度的弧度值
* @returns 最大半径
*/
export function calculateMaxRadius(
rect: { width: number; height: number },
center: { x: number; y: number },
startAngle: number,
endAngle: number
) {
const { x, y } = center;
const steps = findBoundaryAngles(startAngle, endAngle);
const { width, height } = rect;

const radiusList: number[] = [];

steps.forEach(step => {
const sin = Math.sin(step);
const cos = Math.cos(step);

if (sin === 1) {
radiusList.push(height - y);
} else if (sin === -1) {
radiusList.push(y);
} else if (cos === 1) {
radiusList.push(width - x);
} else if (cos === -1) {
radiusList.push(x);
} else {
if (sin > 0) {
radiusList.push(Math.abs((height - y) / cos));
} else {
radiusList.push(Math.abs(y / cos));
}

if (cos > 0) {
radiusList.push(Math.abs((width - x) / sin));
} else {
radiusList.push(Math.abs(x / sin));
}
}
});

return Math.min.apply(null, radiusList);
}
Loading