You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在受限矩形内平移凸多边形最大化与其他多边形的最小距离(可假设为旋转矩形)

问题本质拆解

你遇到的是计算几何领域典型的带约束最大最小放置问题:

  • 待放置凸多边形只有两个自由度:X方向平移量tx、Y方向平移量ty
  • 硬约束:平移后的多边形必须完全落入给定的轴对齐有界区域内部
  • 优化目标:找到一组(tx, ty),让待放置多边形和所有障碍多边形的边框最小距离取最大值,这个距离的最小值越大,放置效果越优。
    你已经实现的GJK距离算法,刚好是解决这类问题的核心基础能力,不需要更换底层计算逻辑。
核心可行算法思路

整个方案的逻辑基于凸形状的距离特性,实现复杂度很低,不需要复杂的优化框架。

第一步:先划定平移的合法可行域

对任意凸形状的待放置多边形,先算出tx、ty的允许取值范围:

  • 先统计待放置多边形自身的顶点在局部坐标系下的X范围[minPx, maxPx]、Y范围[minPy, maxPy]
  • 平移后的绝对X范围为[tx + minPx, tx + maxPx],要求完全落在有界区域[bounds.x, bounds.x + bounds.width]内,因此tx的合法区间为[bounds.x - minPx, bounds.x + bounds.width - maxPx]
  • 同理可得ty的合法区间为[bounds.y - minPy, bounds.y + bounds.height - maxPy]
    只要tx、ty落在这个区间里,凸形状就一定完全在有界区域内,不需要逐点校验边界。

注意:不管障碍多边形有没有超出有界区域,都不需要提前做裁剪——GJK算法计算两个凸形状的最小距离时,自动会取两个形状上离得最近的点,和形状在哪个区域无关,只要待放多边形不越界就行。

第二步:用二分搜索架构建整个优化流程

这个目标函数有一个非常好用的数学性质:

如果能找到一组(tx, ty),让待放多边形和所有障碍的距离都不小于r,那r值就可以进一步往上提;如果找不到,就把r值往下调。
基于这个性质,完全可以用二分搜索快速逼近最优解,不需要暴力遍历所有平移位置:

  • 先给r定一个合理的上下界:下界取0,上界取有界区域的对角线长度(这个长度是可能的最大距离的理论上限)
  • 取当前区间的中间值midR,检测:在tx、ty的合法区间内,是否存在一组平移量,让待放多边形和每个障碍的GJK距离都>=midR?
  • 如果检测到存在这样的平移位置,说明当前midR还可以更大,就把搜索区间的下界移到midR;如果不存在,就把搜索区间的上界移到midR
  • 重复迭代30~50次,精度就可以满足工程要求(误差小于1e-3量级)

第三步:旋转矩形场景的快速优化

如果所有形状都是旋转矩形,整个流程可以再提速,因为凸多边形的距离函数是分段线性的,最优解一定落在有限个候选点上,不需要大范围搜索:

  • 候选点来源主要分三类:
    • 待放矩形的角点,刚好碰到有界区域边界的平移位置
    • 待放矩形的边,刚好和某个障碍矩形的边平行、距离为当前r的平移位置
    • 待放矩形的角点,刚好碰到某个障碍矩形的角点的平移位置
  • 这些候选点的总数和障碍数量成正比,O(n)级别,每次检测只需要对每个候选点调用一次GJK算距离,计算量极低。
    这里不需要真的把每个障碍向外膨胀midR生成圆角区域——GJK本身就是基于两个形状的闵可夫斯基差实现的,直接计算待放多边形和障碍的距离,和预先膨胀后的检测结果完全等价,省掉了形状生成的开销。
JavaScript 简化实现参考

下面的代码结构可以直接基于你已经实现的GJK距离函数改写:

/**
 * 已有实现:计算两个凸多边形的最小边框距离
 * @param {Array<[number, number]>} polyA 形状A的顶点数组
 * @param {Array<[number, number]>} polyB 形状B的顶点数组
 * @returns {number} 两个形状的最小距离
 */
function gjkDistance(polyA, polyB) {
  // 你已有的GJK实现
}

/**
 * 给定目标距离值r,检测是否存在合法放置位置
 * @param {number} r 目标最小距离
 * @param {Array<[number, number]>} placeablePoly 待放置多边形的局部顶点
 * @param {Array<Array<[number, number]>>} obstacles 所有障碍多边形的顶点
 * @param {{x:number, y:number, width:number, height:number}} bounds 有界区域参数
 * @returns {null | {tx:number, ty:number}} 找到的合法位置,找不到返回null
 */
function checkFeasible(r, placeablePoly, obstacles, bounds) {
  // 第一步:计算平移可行域的范围
  const pxList = placeablePoly.map(p => p[0]);
  const pyList = placeablePoly.map(p => p[1]);
  const minPx = Math.min(...pxList), maxPx = Math.max(...pxList);
  const minPy = Math.min(...pyList), maxPy = Math.max(...pyList);
  const txRange = [bounds.x - minPx, bounds.x + bounds.width - maxPx];
  const tyRange = [bounds.y - minPy, bounds.y + bounds.height - maxPy];

  // 粗定位:在可行域内用步长10做网格扫一遍,收集初步候选点
  const step = 10;
  for(let tx = txRange[0]; tx <= txRange[1]; tx += step) {
    for(let ty = tyRange[0]; ty <= tyRange[1]; ty += step) {
      const movedPoly = placeablePoly.map(([px, py]) => [px + tx, py + ty]);
      let meetReq = true;
      for(const obs of obstacles) {
        const d = gjkDistance(movedPoly, obs);
        if(d < r) {
          meetReq = false;
          break;
        }
      }
      if(meetReq) return {tx, ty};
    }
  }
  return null;
}

/**
 * 主流程:搜索最优放置位置
 * @param {Array<[number, number]>} placeablePoly 待放置多边形
 * @param {Array<Array<[number, number]>>} obstacles 所有障碍多边形
 * @param {{x:number, y:number, width:number, height:number}} bounds 有界区域
 * @returns {{maxDistance:number, position: {tx:number, ty:number}}}
 */
function findOptimalPlacement(placeablePoly, obstacles, bounds) {
  let low = 0;
  let high = Math.hypot(bounds.width, bounds.height);
  let bestR = 0;
  let bestPos = null;

  // 迭代50次,精度足够
  for(let i = 0; i < 50; i++) {
    const midR = (low + high) / 2;
    const pos = checkFeasible(midR, placeablePoly, obstacles, bounds);
    if(pos) {
      bestR = midR;
      bestPos = pos;
      low = midR;
    } else {
      high = midR;
    }
  }
  return {maxDistance: bestR, position: bestPos};
}
工程实现注意事项
  • 不要用梯度上升类的优化方法直接搜全局最优点,凸形状的距离函数是分段凸的,很容易卡在局部最优,二分搜索的方法没有局部最优的问题。
  • 粗扫的步长可以根据场景的尺度调整,最后找到粗略位置之后,再在这个点周围做小范围细扫,精度会更高。
  • 如果待放置多边形是带旋转自由度的(也就是不仅能平移,还能转角度),只要把第三个自由度(旋转角度)也加入二分搜索的维度就行,整体框架不需要变。

内容的提问来源于stack exchange,提问作者Stefano Balzarotti

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.24 03:45:04