Skip to content

Commit

Permalink
Merge pull request #189 from VisActor/feat/angle-utils
Browse files Browse the repository at this point in the history
Feat/angle utils
  • Loading branch information
xile611 authored May 24, 2024
2 parents dc69149 + bd5c38b commit 5659559
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 1 deletion.
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);
}

0 comments on commit 5659559

Please sign in to comment.