【初始需求】:给出一组信息(
中心圆坐标
、中心圆半径
、环绕圆半径
、环绕圆距离中心圆距离
),是否能够求助 最大 可以有多少个环绕圆。【延展需求】:基于以上需求,能否实现可设置 最大数量 环绕圆 均匀分布 ,以及能否 自定义 环绕圆之间的 间隔 。
一、完整代码及样式示例
1.1. 不同情况下效果图
① 紧凑排布,显示最大数量环绕圆
② 均匀分布,显示最大数量环绕圆
③ 自定义环绕圆之间的间隔,显示配置间隔后最大环绕圆数量
1.2. 完整代码示例
二、实现方法及逻辑解析
2.1. 计算逻辑图解
2.2. 计算环绕圆不同情况图解
三、核心代码(TS及JS)
TS版本封装的SurroundingCircle
类
interface SurroundingCircleInter {
centerCircle: {
position: {
x: number;
y: number;
};
radius: number;
};
surroundCircleRadius: number;
distance: number;
surroundManner?: string; // 环绕方式,默认为 "default"
surroundGap?: number | null; // 环绕圆之间的间距,默认为 null
}
interface CircleDataInter {
circlePoints: Array<{ x: number; y: number }>;
surroundingCircleGap: number;
pointsGather: Array<{ x: number; y: number }>;
}
interface CirclePointInter {
position: { x: number; y: number };
radius: number;
}
interface PointsGatherInter {
circlePoints: Array<{ x: number; y: number }>;
points: Array<{ x: number; y: number }>;
}
/**
* 环绕圆类
* @class surroundingCircle
* @param {Object} options - 配置选项
* @param {Object} options.centerCircle - 中心圆的配置
* @param {Object} options.centerCircle.position - 中心圆的中心点坐标
* @param {number} options.centerCircle.radius - 中心圆的半径
* @param {number} options.surroundCircleRadius - 环绕圆的半径
* @param {number} options.distance - 环绕圆与中心圆之间的距离
* @param {string} [options.surroundManner="default"] - 环绕方式,"default" 或 "average"
* @param {number|null} [options.surroundGap=null] - 环绕圆之间的间距,默认为 null
* @description 计算环绕圆的点数据
*/
export class SurroundingCircle {
centerPointX: number;
centerPointY: number;
centerCircleRadius: number;
surroundCircleRadius: number;
distance: number;
surroundManner: string;
surroundGap: number | null;
maxSurroundGap: number;
constructor({
centerCircle,
surroundCircleRadius,
distance,
surroundManner = "default",
surroundGap = null,
}: SurroundingCircleInter) {
this.centerPointX = centerCircle.position.x;
this.centerPointY = centerCircle.position.y;
this.centerCircleRadius = centerCircle.radius;
this.surroundCircleRadius = surroundCircleRadius;
this.distance = distance;
this.surroundManner = surroundManner;
this.surroundGap = surroundGap;
this.maxSurroundGap = 0;
}
/**
* 更新环绕圆配置参数
* @param {Object} options - 配置选项
* @param {Object} options.centerCircle - 中心圆的配置
* @param {Object} options.centerCircle.position - 中心圆的中心点坐标
* @param {number} options.centerCircle.radius - 中心圆的半径
* @param {number} options.surroundCircleRadius - 环绕圆的半径
* @param {number} options.distance - 环绕圆与中心圆之间的距离
* @param {string} [options.surroundManner="default"] - 环绕方式,"default" 或 "average"
* @param {number|null} [options.surroundGap=null] - 环绕圆之间的间距,默认为 null
*/
setSurroundingCircleOptions(options: SurroundingCircleInter): void {
this.centerPointX = options.centerCircle.position.x;
this.centerPointY = options.centerCircle.position.y;
this.centerCircleRadius = options.centerCircle.radius;
this.surroundCircleRadius = options.surroundCircleRadius;
this.distance = options.distance;
this.surroundManner = options.surroundManner ?? "default";
this.surroundGap = options.surroundGap ?? null;
this.maxSurroundGap = 0;
}
/**
* 获取环绕圆的数据
* @returns {Object} 返回包含环绕圆点和距离的对象
*/
getCircleData(): CircleDataInter {
if (this.surroundManner === "default") {
return this.getCloseCircleData();
} else if (this.surroundManner === "average") {
return this.getAverageCircleData();
} else {
throw new Error("Invalid surround manner specified.");
}
}
/**
* 获取环绕圆的点数据--紧凑型
* @returns {Object} 返回包含环绕圆点和距离的对象
*/
getCloseCircleData(): CircleDataInter {
return this.handleCalculatePoints(
this.centerPointX,
this.centerPointY,
this.centerCircleRadius,
this.surroundCircleRadius,
this.distance
);
}
/**
* 获取平均环绕圆的数据-平均型
* @returns {Object} 返回包含平均环绕圆点和距离的对象
*/
getAverageCircleData(): CircleDataInter {
const circlePointGather: CircleDataInter = this.getCloseCircleData();
const averageGap: number =
this.surroundGap ??
circlePointGather.surroundingCircleGap /
circlePointGather.circlePoints.length;
return this.handleCalculatePoints(
this.centerPointX,
this.centerPointY,
this.centerCircleRadius,
this.surroundCircleRadius,
this.distance,
averageGap
);
}
/**
* 获取最大间隔值
* @returns {number} 返回可设置间隔的最大值
*/
getMaxSurroundGap(): number {
return this.maxSurroundGap;
}
/**
* 设置最大间距
* @param {object} virtualCircle - 虚拟圆对象 { position: { x, y }, radius }
* @param {object} originPoints - 原始圆对象 { position: { x, y }, radius }
* @param {number} x - 虚拟圆心的 x 坐标
* @param {number} y - 虚拟圆心的 y 坐标
* @param {number} R - 虚拟圆的半径
* @param {number} d - 偏移量
* @param {number} r - 原始圆的半径
*/
setMaxSurroundGap(
virtualCircle: CirclePointInter,
originPoints: Array<{ x: number; y: number }> | null,
x: number,
y: number,
R: number,
d: number,
r: number
) {
// 对称于originCircle的点,用于计算最大gap,即this.maxSurroundGap
const symmetryOriginCircle: CirclePointInter = {
position: { x: x, y: y - R - d - r },
radius: r,
};
// 开始计算最大gap,即this.maxSurroundGap
this.findCircleIntersection(
virtualCircle.position.x,
virtualCircle.position.y,
virtualCircle.radius,
symmetryOriginCircle.position.x,
symmetryOriginCircle.position.y,
symmetryOriginCircle.radius
)?.forEach((item) => {
if (item.x === (originPoints ? originPoints[0] : { x: 0, y: 0 }).x) {
this.maxSurroundGap =
(originPoints ? originPoints[0] : { x: 0, y: 0 }).y - item.y;
}
});
}
/**
* 处理计算点的逻辑 --- 主要核心函数
* @param {number} x - 虚拟圆心的 x 坐标
* @param {number} y - 虚拟圆心的 y 坐标
* @param {number} R - 虚拟圆的半径
* @param {number} r - 原始圆的半径
* @param {number} d - 偏移量
* @param {number} gap - 原始小圆之间的间隔
* @returns {Object} 返回包含交点和距离的对象
*/
handleCalculatePoints(
x: number,
y: number,
R: number,
r: number,
d: number,
gap: number = 0
): CircleDataInter {
const virtualCircle: CirclePointInter = {
position: { x, y },
radius: R + d + r,
};
const originCircle: CirclePointInter = {
position: { x: x, y: y + R + d + r },
radius: r,
};
const originPoints: Array<{ x: number; y: number }> | null =
this.findCircleIntersection(
virtualCircle.position.x,
virtualCircle.position.y,
virtualCircle.radius,
originCircle.position.x,
originCircle.position.y,
originCircle.radius
);
// 如果设置了平均间隔且配置了间隔值,则需要执行以下逻辑
if (this.surroundManner !== "default" && this.surroundGap) {
// 计算出最大可配置间隔值
this.setMaxSurroundGap(virtualCircle, originPoints, x, y, R, d, r);
// 如果设置间隔平均分配且间隔超出最大间隔时,仅返回originCircle圆数据
if (this.surroundManner !== "default" && gap >= this.maxSurroundGap) {
return {
circlePoints: [originCircle.position],
surroundingCircleGap: gap,
pointsGather: [originCircle.position, ...(originPoints ?? [])],
};
}
}
// 开始进行过滤后的正常计算
const rightCirclePoints: PointsGatherInter = this.calculatePoints(
originPoints ? originPoints[0] : { x: 0, y: 0 },
virtualCircle,
originCircle.radius,
R,
gap,
"right"
);
const leftCirclePoints: PointsGatherInter = this.calculatePoints(
originPoints ? originPoints[1] : { x: 0, y: 0 },
virtualCircle,
originCircle.radius,
R,
gap,
"left"
);
// 计算:如果左右两部分最后一个点集合之间的距离小于gap时,去除掉其中一边的圆数据即对应的点集合数据
if (
rightCirclePoints.circlePoints.length !== 0 &&
leftCirclePoints.circlePoints.length !== 0
) {
const leftDistanceRight: number = this.calculateDistance(
rightCirclePoints.points[rightCirclePoints.points.length - 1].x,
rightCirclePoints.points[rightCirclePoints.points.length - 1].y,
leftCirclePoints.points[leftCirclePoints.points.length - 1].x,
leftCirclePoints.points[leftCirclePoints.points.length - 1].y
);
if (leftDistanceRight < gap && this.surroundGap) {
rightCirclePoints.circlePoints.pop();
rightCirclePoints.points.slice(0, rightCirclePoints.points.length - 3);
}
}
// 初步整理圆心点集合、点集合、默认紧凑时的间距
const circlePoints: Array<{ x: number; y: number }> = [
originCircle.position,
...rightCirclePoints.circlePoints,
...leftCirclePoints.circlePoints,
];
const pointsGather: Array<{ x: number; y: number }> = [
...rightCirclePoints.points,
...leftCirclePoints.points,
];
let distance: number = this.calculateDistance(
leftCirclePoints.points[leftCirclePoints.points.length - 1].x,
leftCirclePoints.points[leftCirclePoints.points.length - 1].y,
rightCirclePoints.points[rightCirclePoints.points.length - 1].x,
rightCirclePoints.points[rightCirclePoints.points.length - 1].y
);
// 如果左右两部分都没有适合间隔的圆数据,则将originCircle右侧点开始,计算gap距离符合需求的圆数据
if (
rightCirclePoints.circlePoints.length === 0 &&
leftCirclePoints.circlePoints.length === 0 &&
gap <= R * 2 + d * 2
) {
this.findCircleIntersection(
virtualCircle.position.x,
virtualCircle.position.y,
virtualCircle.radius,
(originPoints ? originPoints[1] : { x: 0, y: 0 }).x,
(originPoints ? originPoints[1] : { x: 0, y: 0 }).y,
gap
)?.forEach((item) => {
if (item.x < virtualCircle.position.x) {
pointsGather.push(item);
this.findCircleIntersection(
virtualCircle.position.x,
virtualCircle.position.y,
virtualCircle.radius,
item.x,
item.y,
r
)?.forEach((circle) => {
if (circle.x > item.x) {
circlePoints.push(circle);
this.findCircleIntersection(
virtualCircle.position.x,
virtualCircle.position.y,
virtualCircle.radius,
circle.x,
circle.y,
r
)?.forEach((point) => {
if (point.x > circle.x) {
pointsGather.push(point);
distance = this.calculateDistance(
(originPoints ? originPoints[0] : { x: 0, y: 0 }).x,
(originPoints ? originPoints[0] : { x: 0, y: 0 }).y,
point.x,
point.y
);
}
});
}
});
}
});
}
// 当左右两边的距离还能容纳一个圆时(分是否有gap,以及是否是默认自动均匀分布)
if (
(distance >= r * 2 && gap === 0) ||
(gap !== 0 && (distance >= r * 2 + gap * 2 || this.surroundGap === null))
) {
// 默认 gap 为 0 时,右侧开始补圆的起点
let x2:number = rightCirclePoints.points[rightCirclePoints.points.length - 1].x;
let y2:number = rightCirclePoints.points[rightCirclePoints.points.length - 1].y;
const x1:number = virtualCircle.position.x;
const y1:number = virtualCircle.position.y;
const R:number = virtualCircle.radius;
// 当 gap 不为 0 时,重新设置间隔后,右侧开始补圆的起点
if (gap !== 0) {
this.findCircleIntersection(x1, y1, R, x2, y2, gap)?.forEach((item) => {
if (item.x < x2) {
pointsGather.push(item);
x2 = item.x;
y2 = item.y;
}
});
}
this.findCircleIntersection(x1, y1, R, x2, y2, r)?.forEach((item) => {
if (item.x < x2) {
circlePoints.push(item);
pointsGather.push(item);
this.findCircleIntersection(x1, y1, R, item.x, item.y, r)?.forEach(
(point) => {
if (point.x < item.x) {
pointsGather.push(point);
distance = this.calculateDistance(
leftCirclePoints.points[leftCirclePoints.points.length - 1].x,
leftCirclePoints.points[leftCirclePoints.points.length - 1].y,
point.x,
point.y
);
}
}
);
}
});
}
return { circlePoints, surroundingCircleGap: distance, pointsGather };
}
/**
* 计算两个圆的交点
* @param {number} x1 - 第一个圆心的 x 坐标
* @param {number} y1 - 第一个圆心的 y 坐标
* @param {number} R - 第一个圆的半径
* @param {number} x2 - 第二个圆心的 x 坐标
* @param {number} y2 - 第二个圆心的 y 坐标
* @param {number} r - 第二个圆的半径
* @returns {Array|null} 返回交点坐标数组或 null(如果没有交点)
*/
findCircleIntersection(
x1: number,
y1: number,
R: number,
x2: number,
y2: number,
r: number
): Array<{ x: number; y: number }> | null {
const d:number = Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2); // 圆心距离
if (d > R + r || d < Math.abs(R - r)) {
return null; // 两圆没有交点
}
const a: number = (R ** 2 - r ** 2 + d ** 2) / (2 * d);
const h: number = Math.sqrt(R ** 2 - a ** 2);
const px: number = x1 + (a * (x2 - x1)) / d;
const py: number = y1 + (a * (y2 - y1)) / d;
const offsetX: number = (h * (y2 - y1)) / d;
const offsetY: number = (h * (x2 - x1)) / d;
const intersection1: { x: number; y: number } = {
x: px + offsetX,
y: py - offsetY,
};
const intersection2: { x: number; y: number } = {
x: px - offsetX,
y: py + offsetY,
};
return [intersection1, intersection2];
}
/**
* 计算两点之间的距离
* @param {number} x1 - 第一个点的 x 坐标
* @param {number} y1 - 第一个点的 y 坐标
* @param {number} x2 - 第二个点的 x 坐标
* @param {number} y2 - 第二个点的 y 坐标
* @returns {number} 返回两点之间的距离
*/
calculateDistance(x1: number, y1: number, x2: number, y2: number): number {
return Math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2);
}
/**
* 判断两个点是否相同
* @param {Object} point1 - 第一个点坐标对象 { x, y }
* @param {Object} point2 - 第二个点坐标对象 { x, y }
* @returns {boolean} 返回 true 如果两个点相同,否则返回 false
*/
calculatePointsIsSame(
point1: { x: number; y: number },
point2: { x: number; y: number }
): boolean {
return point1.x === point2.x && point1.y === point2.y;
}
/**
* 计算从一个点开始,沿着虚拟圆的边界向下找到所有交点
* @param {Object} points - 起始点坐标对象 { x, y }
* @param {Object} virtualCircle - 虚拟圆对象 { position: { x, y }, radius }
* @param {number} r - 原始圆的半径
* @param {number} R - 虚拟圆的半径
* @param {number} gap - 原始小圆之间的间隔
* @param {string} direction - 虚拟圆的左侧或是右侧
* @returns {Array} 返回所有找到的交点坐标数组
*/
calculatePoints(
points: { x: number; y: number },
virtualCircle: CirclePointInter,
r: number,
R: number,
gap: number | null = 0,
direction: "right" | "left" = "right"
): PointsGatherInter {
const newPoints = new Map();
newPoints.set(0, points);
let index: number = 0;
while (
newPoints.has(index) &&
this.calculateDistance(
newPoints.get(index).x,
newPoints.get(index).y,
virtualCircle.position.x,
virtualCircle.position.y - R
) >= r
) {
const x1: number = virtualCircle.position.x;
const y1: number = virtualCircle.position.y;
const x2: number = newPoints.get(index).x;
const y2: number = newPoints.get(index).y;
const R: number = virtualCircle.radius;
const dblPoints: Array<{ x: number; y: number }> | null =
this.findCircleIntersection(
x1,
y1,
R,
x2,
y2,
gap !== null && gap !== 0 && index % 3 === 0 ? gap : r
);
dblPoints?.forEach((item, i) => {
if (
item.y < y2 &&
(direction === "right" ? item.x > x1 : item.x < x1)
) {
newPoints.set(index + 1, item);
}
});
index++;
}
let circleGather: Array<{ x: number; y: number }> = [];
let allPoints: Array<{ x: number; y: number }> = [];
for (let key of newPoints) {
if (gap === 0) {
if (key[0] % 2 !== 0) {
circleGather.push(newPoints.get(key[0]));
}
} else {
if ((key[0] + 1) % 3 === 0) {
circleGather.push(newPoints.get(key[0]));
}
}
allPoints.push(newPoints.get(key[0]));
}
// 处理最后一个点的情况,以防止多余的点被添加到结果中 --- 无间隔
if (allPoints.length - 1 !== circleGather.length * 2 && gap === 0) {
circleGather = circleGather.slice(0, circleGather.length - 1);
allPoints = allPoints.slice(0, circleGather.length * 2 + 1);
}
// 处理最后一个点的情况,以防止多余的点被添加到结果中 --- 有间隔
if (allPoints.length - 1 !== circleGather.length * 3 && gap !== 0) {
// 处理当最后一个圆心点是最后一个点集合数据一样时的问题,此时需要删去最后一个圆点数据,并更新所有点集合数据
if (
this.calculatePointsIsSame(
circleGather[circleGather.length - 1],
allPoints[allPoints.length - 1]
)
) {
circleGather = circleGather.slice(0, circleGather.length - 1);
}
allPoints = allPoints.slice(0, circleGather.length * 3 + 1);
}
return { circlePoints: circleGather, points: allPoints };
}
}
JS版本封装的SurroundingCircle
类
/**
* 环绕圆类
* @class surroundingCircle
* @param {Object} options - 配置选项
* @param {Object} options.centerCircle - 中心圆的配置
* @param {Object} options.centerCircle.position - 中心圆的中心点坐标
* @param {number} options.centerCircle.radius - 中心圆的半径
* @param {number} options.surroundCircleRadius - 环绕圆的半径
* @param {number} options.distance - 环绕圆与中心圆之间的距离
* @param {string} [options.surroundManner="default"] - 环绕方式,"default" 或 "average"
* @param {number|null} [options.surroundGap=null] - 环绕圆之间的间距,默认为 null
* @description 计算环绕圆的点数据
*/
export class SurroundingCircle {
constructor({
centerCircle,
surroundCircleRadius,
distance,
surroundManner = "default",
surroundGap = null,
}) {
this.centerPointX = centerCircle.position.x;
this.centerPointY = centerCircle.position.y;
this.centerCircleRadius = centerCircle.radius;
this.surroundCircleRadius = surroundCircleRadius;
this.distance = distance;
this.surroundManner = surroundManner;
this.surroundGap = surroundGap;
this.maxSurroundGap = 0;
}
/**
* 更新环绕圆配置参数
* @param {Object} options - 配置选项
* @param {Object} options.centerCircle - 中心圆的配置
* @param {Object} options.centerCircle.position - 中心圆的中心点坐标
* @param {number} options.centerCircle.radius - 中心圆的半径
* @param {number} options.surroundCircleRadius - 环绕圆的半径
* @param {number} options.distance - 环绕圆与中心圆之间的距离
* @param {string} [options.surroundManner="default"] - 环绕方式,"default" 或 "average"
* @param {number|null} [options.surroundGap=null] - 环绕圆之间的间距,默认为 null
*/
setSurroundingCircleOptions(options) {
this.centerPointX = options.centerCircle.position.x;
this.centerPointY = options.centerCircle.position.y;
this.centerCircleRadius = options.centerCircle.radius;
this.surroundCircleRadius = options.surroundCircleRadius;
this.distance = options.distance;
this.surroundManner = options.surroundManner ?? "default";
this.surroundGap = options.surroundGap ?? null;
this.maxSurroundGap = 0;
}
/**
* 获取环绕圆的数据
* @returns {Object} 返回包含环绕圆点和距离的对象
*/
getCircleData() {
if (this.surroundManner === "default") {
return this.getCloseCircleData();
} else if (this.surroundManner === "average") {
return this.getAverageCircleData();
} else {
throw new Error("Invalid surround manner specified.");
}
}
/**
* 获取环绕圆的点数据--紧凑型
* @returns {Object} 返回包含环绕圆点和距离的对象
*/
getCloseCircleData() {
return this.handleCalculatePoints(
this.centerPointX,
this.centerPointY,
this.centerCircleRadius,
this.surroundCircleRadius,
this.distance
);
}
/**
* 获取平均环绕圆的数据-平均型
* @returns {Object} 返回包含平均环绕圆点和距离的对象
*/
getAverageCircleData() {
const circlePointGather = this.getCloseCircleData();
const averageGap =
this.surroundGap ??
circlePointGather.surroundingCircleGap /
circlePointGather.circlePoints.length;
return this.handleCalculatePoints(
this.centerPointX,
this.centerPointY,
this.centerCircleRadius,
this.surroundCircleRadius,
this.distance,
averageGap
);
}
/**
* 获取最大间隔值
* @returns {number} 返回可设置间隔的最大值
*/
getMaxSurroundGap() {
return this.maxSurroundGap;
}
/**
* 设置最大间距
* @param {object} virtualCircle - 虚拟圆对象 { position: { x, y }, radius }
* @param {object} originPoints - 原始圆对象 { position: { x, y }, radius }
* @param {number} x - 虚拟圆心的 x 坐标
* @param {number} y - 虚拟圆心的 y 坐标
* @param {number} R - 虚拟圆的半径
* @param {number} d - 偏移量
* @param {number} r - 原始圆的半径
*/
setMaxSurroundGap(virtualCircle, originPoints, x, y, R, d, r) {
// 对称于originCircle的点,用于计算最大gap,即this.maxSurroundGap
const symmetryOriginCircle = {
position: { x: x, y: y - R - d - r },
radius: r,
};
// 开始计算最大gap,即this.maxSurroundGap
this.findCircleIntersection(
virtualCircle.position.x,
virtualCircle.position.y,
virtualCircle.radius,
symmetryOriginCircle.position.x,
symmetryOriginCircle.position.y,
symmetryOriginCircle.radius
)?.forEach((item) => {
if (item.x === (originPoints ? originPoints[0] : { x: 0, y: 0 }).x) {
this.maxSurroundGap =
(originPoints ? originPoints[0] : { x: 0, y: 0 }).y - item.y;
}
});
}
/**
* 处理计算点的逻辑 --- 主要核心函数
* @param {number} x - 虚拟圆心的 x 坐标
* @param {number} y - 虚拟圆心的 y 坐标
* @param {number} R - 虚拟圆的半径
* @param {number} r - 原始圆的半径
* @param {number} d - 偏移量
* @param {number} gap - 原始小圆之间的间隔
* @returns {Object} 返回包含交点和距离的对象
*/
handleCalculatePoints(x, y, R, r, d, gap = 0) {
const virtualCircle = {
position: { x, y },
radius: R + d + r,
};
const originCircle = {
position: { x: x, y: y + R + d + r },
radius: r,
};
const originPoints = this.findCircleIntersection(
virtualCircle.position.x,
virtualCircle.position.y,
virtualCircle.radius,
originCircle.position.x,
originCircle.position.y,
originCircle.radius
);
// 如果设置了平均间隔且配置了间隔值,则需要执行以下逻辑
if (this.surroundManner !== "default" && this.surroundGap) {
// 计算出最大可配置间隔值
this.setMaxSurroundGap(virtualCircle, originPoints, x, y, R, d, r);
// 如果设置间隔平均分配且间隔超出最大间隔时,仅返回originCircle圆数据
if (this.surroundManner !== "default" && gap >= this.maxSurroundGap) {
return {
circlePoints: [originCircle.position],
surroundingCircleGap: gap,
pointsGather: [originCircle.position, ...(originPoints ?? [])],
};
}
}
// 开始进行过滤后的正常计算
const rightCirclePoints = this.calculatePoints(
originPoints ? originPoints[0] : { x: 0, y: 0 },
virtualCircle,
originCircle.radius,
R,
gap,
"right"
);
const leftCirclePoints = this.calculatePoints(
originPoints ? originPoints[1] : { x: 0, y: 0 },
virtualCircle,
originCircle.radius,
R,
gap,
"left"
);
// 计算:如果左右两部分最后一个点集合之间的距离小于gap时,去除掉其中一边的圆数据即对应的点集合数据
if (
rightCirclePoints.circlePoints.length !== 0 &&
leftCirclePoints.circlePoints.length !== 0
) {
const leftDistanceRight = this.calculateDistance(
rightCirclePoints.points[rightCirclePoints.points.length - 1].x,
rightCirclePoints.points[rightCirclePoints.points.length - 1].y,
leftCirclePoints.points[leftCirclePoints.points.length - 1].x,
leftCirclePoints.points[leftCirclePoints.points.length - 1].y
);
if (leftDistanceRight < gap && this.surroundGap) {
rightCirclePoints.circlePoints.pop();
rightCirclePoints.points.slice(0, rightCirclePoints.points.length - 3);
}
}
// 初步整理圆心点集合、点集合、默认紧凑时的间距
const circlePoints = [
originCircle.position,
...rightCirclePoints.circlePoints,
...leftCirclePoints.circlePoints,
];
const pointsGather = [
...rightCirclePoints.points,
...leftCirclePoints.points,
];
let distance = this.calculateDistance(
leftCirclePoints.points[leftCirclePoints.points.length - 1].x,
leftCirclePoints.points[leftCirclePoints.points.length - 1].y,
rightCirclePoints.points[rightCirclePoints.points.length - 1].x,
rightCirclePoints.points[rightCirclePoints.points.length - 1].y
);
// 如果左右两部分都没有适合间隔的圆数据,则将originCircle右侧点开始,计算gap距离符合需求的圆数据
if (
rightCirclePoints.circlePoints.length === 0 &&
leftCirclePoints.circlePoints.length === 0 &&
gap <= R * 2 + d * 2
) {
this.findCircleIntersection(
virtualCircle.position.x,
virtualCircle.position.y,
virtualCircle.radius,
(originPoints ? originPoints[1] : { x: 0, y: 0 }).x,
(originPoints ? originPoints[1] : { x: 0, y: 0 }).y,
gap
)?.forEach((item) => {
if (item.x < virtualCircle.position.x) {
pointsGather.push(item);
this.findCircleIntersection(
virtualCircle.position.x,
virtualCircle.position.y,
virtualCircle.radius,
item.x,
item.y,
r
)?.forEach((circle) => {
if (circle.x > item.x) {
circlePoints.push(circle);
this.findCircleIntersection(
virtualCircle.position.x,
virtualCircle.position.y,
virtualCircle.radius,
circle.x,
circle.y,
r
)?.forEach((point) => {
if (point.x > circle.x) {
pointsGather.push(point);
distance = this.calculateDistance(
(originPoints ? originPoints[0] : { x: 0, y: 0 }).x,
(originPoints ? originPoints[0] : { x: 0, y: 0 }).y,
point.x,
point.y
);
}
});
}
});
}
});
}
// 当左右两边的距离还能容纳一个圆时(分是否有gap,以及是否是默认自动均匀分布)
if (
(distance >= r * 2 && gap === 0) ||
(gap !== 0 && (distance >= r * 2 + gap * 2 || this.surroundGap === null))
) {
// 默认 gap 为 0 时,右侧开始补圆的起点
let x2 = rightCirclePoints.points[rightCirclePoints.points.length - 1].x;
let y2 = rightCirclePoints.points[rightCirclePoints.points.length - 1].y;
const x1 = virtualCircle.position.x;
const y1 = virtualCircle.position.y;
const R = virtualCircle.radius;
// 当 gap 不为 0 时,重新设置间隔后,右侧开始补圆的起点
if (gap !== 0) {
this.findCircleIntersection(x1, y1, R, x2, y2, gap)?.forEach((item) => {
if (item.x < x2) {
pointsGather.push(item);
x2 = item.x;
y2 = item.y;
}
});
}
this.findCircleIntersection(x1, y1, R, x2, y2, r)?.forEach((item) => {
if (item.x < x2) {
circlePoints.push(item);
pointsGather.push(item);
this.findCircleIntersection(x1, y1, R, item.x, item.y, r)?.forEach(
(point) => {
if (point.x < item.x) {
pointsGather.push(point);
distance = this.calculateDistance(
leftCirclePoints.points[leftCirclePoints.points.length - 1].x,
leftCirclePoints.points[leftCirclePoints.points.length - 1].y,
point.x,
point.y
);
}
}
);
}
});
}
return { circlePoints, surroundingCircleGap: distance, pointsGather };
}
/**
* 计算两个圆的交点
* @param {number} x1 - 第一个圆心的 x 坐标
* @param {number} y1 - 第一个圆心的 y 坐标
* @param {number} R - 第一个圆的半径
* @param {number} x2 - 第二个圆心的 x 坐标
* @param {number} y2 - 第二个圆心的 y 坐标
* @param {number} r - 第二个圆的半径
* @returns {Array|null} 返回交点坐标数组或 null(如果没有交点)
*/
findCircleIntersection(x1, y1, R, x2, y2, r) {
const d = Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2); // 圆心距离
if (d > R + r || d < Math.abs(R - r)) {
return null; // 两圆没有交点
}
const a = (R ** 2 - r ** 2 + d ** 2) / (2 * d);
const h = Math.sqrt(R ** 2 - a ** 2);
const px = x1 + (a * (x2 - x1)) / d;
const py = y1 + (a * (y2 - y1)) / d;
const offsetX = (h * (y2 - y1)) / d;
const offsetY = (h * (x2 - x1)) / d;
const intersection1 = {
x: px + offsetX,
y: py - offsetY,
};
const intersection2 = {
x: px - offsetX,
y: py + offsetY,
};
return [intersection1, intersection2];
}
/**
* 计算两点之间的距离
* @param {number} x1 - 第一个点的 x 坐标
* @param {number} y1 - 第一个点的 y 坐标
* @param {number} x2 - 第二个点的 x 坐标
* @param {number} y2 - 第二个点的 y 坐标
* @returns {number} 返回两点之间的距离
*/
calculateDistance(x1, y1, x2, y2) {
return Math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2);
}
/**
* 判断两个点是否相同
* @param {Object} point1 - 第一个点坐标对象 { x, y }
* @param {Object} point2 - 第二个点坐标对象 { x, y }
* @returns {boolean} 返回 true 如果两个点相同,否则返回 false
*/
calculatePointsIsSame(point1, point2) {
return point1.x === point2.x && point1.y === point2.y;
}
/**
* 计算从一个点开始,沿着虚拟圆的边界向下找到所有交点
* @param {Object} points - 起始点坐标对象 { x, y }
* @param {Object} virtualCircle - 虚拟圆对象 { position: { x, y }, radius }
* @param {number} r - 原始圆的半径
* @param {number} R - 虚拟圆的半径
* @param {number} gap - 原始小圆之间的间隔
* @param {string} direction - 虚拟圆的左侧或是右侧
* @returns {Array} 返回所有找到的交点坐标数组
*/
calculatePoints(points, virtualCircle, r, R, gap = 0, direction = "right") {
const newPoints = new Map();
newPoints.set(0, points);
let index = 0;
while (
newPoints.has(index) &&
this.calculateDistance(
newPoints.get(index).x,
newPoints.get(index).y,
virtualCircle.position.x,
virtualCircle.position.y - R
) >= r
) {
const x1 = virtualCircle.position.x;
const y1 = virtualCircle.position.y;
const x2 = newPoints.get(index).x;
const y2 = newPoints.get(index).y;
const R = virtualCircle.radius;
const dblPoints = this.findCircleIntersection(
x1,
y1,
R,
x2,
y2,
gap !== null && gap !== 0 && index % 3 === 0 ? gap : r
);
dblPoints?.forEach((item, i) => {
if (
item.y < y2 &&
(direction === "right" ? item.x > x1 : item.x < x1)
) {
newPoints.set(index + 1, item);
}
});
index++;
}
let circleGather = [];
let allPoints = [];
for (let key of newPoints) {
if (gap === 0) {
if (key[0] % 2 !== 0) {
circleGather.push(newPoints.get(key[0]));
}
} else {
if ((key[0] + 1) % 3 === 0) {
circleGather.push(newPoints.get(key[0]));
}
}
allPoints.push(newPoints.get(key[0]));
}
// 处理最后一个点的情况,以防止多余的点被添加到结果中 --- 无间隔
if (allPoints.length - 1 !== circleGather.length * 2 && gap === 0) {
circleGather = circleGather.slice(0, circleGather.length - 1);
allPoints = allPoints.slice(0, circleGather.length * 2 + 1);
}
// 处理最后一个点的情况,以防止多余的点被添加到结果中 --- 有间隔
if (allPoints.length - 1 !== circleGather.length * 3 && gap !== 0) {
// 处理当最后一个圆心点是最后一个点集合数据一样时的问题,此时需要删去最后一个圆点数据,并更新所有点集合数据
if (
this.calculatePointsIsSame(
circleGather[circleGather.length - 1],
allPoints[allPoints.length - 1]
)
) {
circleGather = circleGather.slice(0, circleGather.length - 1);
}
allPoints = allPoints.slice(0, circleGather.length * 3 + 1);
}
return { circlePoints: circleGather, points: allPoints };
}
}