Javascript 如何在矩形外最小地置换圆?

Javascript 如何在矩形外最小地置换圆?,javascript,algorithm,math,collision-detection,game-physics,Javascript,Algorithm,Math,Collision Detection,Game Physics,这似乎应该很简单,但我找不到任何明确的答案。假设我有一个圆形和矩形。如果圆位于矩形之外,则应保持其当前位置。但是,如果它位于矩形内部,则应将其位移最小,使其仅位于矩形外部 我在下面创建了一个完整的演示,演示了我当前正在进行的工作。我最初的想法是将圆夹紧到最近的边缘,但这似乎无法正常工作。我想可能有一个解决方案涉及到分离轴定理,但我不确定这是否适用于这里,或者对于这类事情来说,这是不是太过分了 let canvas=document.querySelector(“canvas”); 设ctx=c

这似乎应该很简单,但我找不到任何明确的答案。假设我有一个圆形和矩形。如果圆位于矩形之外,则应保持其当前位置。但是,如果它位于矩形内部,则应将其位移最小,使其仅位于矩形外部

我在下面创建了一个完整的演示,演示了我当前正在进行的工作。我最初的想法是将圆夹紧到最近的边缘,但这似乎无法正常工作。我想可能有一个解决方案涉及到分离轴定理,但我不确定这是否适用于这里,或者对于这类事情来说,这是不是太过分了

let canvas=document.querySelector(“canvas”);
设ctx=canvas.getContext(“2d”);
函数绘图(){
ctx.fillStyle=“#b2c7ef”;
ctx.fillRect(0,0,800,800);
ctx.fillStyle=“#fff”;
画圈(圆圈x,圆圈y,圆圈);
drawSquare(平方位置x、平方位置y、平方W、平方H);
}
函数绘图圆(X中心、Y中心、半径){
ctx.beginPath();
ctx.弧(xCenter,cCenter,半径,0,2*Math.PI);
ctx.fill();
}
函数drawSquare(x,y,w,h){
ctx.beginPath();
ctx.rect(x,y,w,h);
ctx.stroke();
}
功能钳位(值、最小值、最大值){
返回Math.min(Math.max(value,min),max);
}
功能getCircleRectangleDisplacement(rX、rY、rW、rH、cX、cY、cR){
让最接近的X=钳位(cX、rX、rX+rW);
let nearestY=夹具(cY,rY,rY+rH);
设newX=nearestX-cR/2;
设newY=nearestY-cR/2;
返回{x:newX,y:newY};
}
函数置换(){
circlePos=获取CircleRectangle位移(squarePos.x,squarePos.y,squareW,squareH,circlePos.x,circlePos.y,circleR);
draw();
}
设circlePos={x:280,y:70};
设squarePos={x:240,y:110};
设圈数=50;
设平方w=100;
设平方H=100;
draw();
设置超时(置换,500)
canvas{display:flex;margin:0 auto;}

看看这里,核心是calc()函数,它是Java,不是JavaScript,但我认为您可以轻松地翻译它

package test;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;

import javax.swing.JComponent;
import javax.swing.JFrame;

public class CircleOutside extends JComponent {
    protected Rectangle2D rect;
    protected Point2D originalCenter;
    protected double radius;
    protected Point2D movedCenter;
    
    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);

        Graphics2D g2=(Graphics2D) g;
        g2.draw(rect);
        g.setColor(Color.red);
        g2.draw(new Ellipse2D.Double(originalCenter.getX()-radius, originalCenter.getY()-radius, 2*radius, 2*radius));
        g.setColor(Color.green);
        g2.draw(new Ellipse2D.Double(movedCenter.getX()-radius, movedCenter.getY()-radius, 2*radius, 2*radius));
        
        addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                originalCenter=e.getPoint();
                calc();
                repaint();
            }
        });
    }
    
    public void calc() {
        movedCenter=originalCenter;
        
        //Circle center distance from edges greater than radius, do not move 
        if (originalCenter.getY()+radius<=rect.getY()) {
            return;
        }
        if (originalCenter.getY()-radius>=rect.getY()+rect.getHeight()) {
            return;
        }
        if (originalCenter.getX()+radius<=rect.getX()) {
            return;
        }
        if (originalCenter.getX()-radius>=rect.getX()+rect.getWidth()) {
            return;
        }

        double moveX=0;
        double moveY=0;
        boolean movingY=false;
        boolean movingX=false;

        //Center projects into rectangle's width, move up or down
        if (originalCenter.getX()>=rect.getX()&&originalCenter.getX()<=rect.getX()+rect.getWidth()) {
            System.out.println("X in width");
            double moveUp=rect.getY()-originalCenter.getY()-radius;
            double moveDown=rect.getY()+rect.getHeight()-originalCenter.getY()+radius;
            if (Math.abs(moveUp)<=Math.abs(moveDown)) {
                moveY=moveUp;
            } else {
                moveY=moveDown;
            }
            System.out.println("UP "+moveUp+" DOWN "+moveDown);
            movingY=true;
        }

        //Center projects into rectangle's height, move left or right
        if (originalCenter.getY()>=rect.getY()&&originalCenter.getY()<=rect.getY()+rect.getHeight()) {
            double moveLeft=rect.getX()-originalCenter.getX()-radius;
            double moveRight=rect.getX()+rect.getWidth()-originalCenter.getX()+radius;
            if (Math.abs(moveLeft)<=Math.abs(moveRight)) {
                moveX=moveLeft;
            } else {
                moveX=moveRight;
            }
            movingX=true;
        }
            
        //If circle can be moved both on X or Y, choose the lower distance
        if (movingX&&movingY) {
            if (Math.abs(moveY)<Math.abs(moveX)) {
                moveX=0;
            } else {
                moveY=0;
            }
        }

        //Note that the following cases are mutually excluding with the previous ones
        
        //Center is in the arc [90-180] centered in upper left corner with same radius as circle, calculate distance from corner and adjust both axis 
        if (originalCenter.getX()<rect.getX()&&originalCenter.getY()<rect.getY()) {
            double dist=originalCenter.distance(rect.getX(),rect.getY());
            if (dist<radius) {
                double factor=(radius-dist)/dist;
                moveX=factor*(originalCenter.getX()-rect.getX());
                moveY=factor*(originalCenter.getY()-rect.getY());
            }
        }

        //Center is in the arc [0-90] centered in upper right corner with same radius as circle, calculate distance from corner and adjust both axis 
        if (originalCenter.getX()>rect.getX()+rect.getWidth()&&originalCenter.getY()<rect.getY()) {
            double dist=originalCenter.distance(rect.getX()+rect.getWidth(),rect.getY());
            if (dist<radius) {
                double factor=(radius-dist)/dist;
                moveX=factor*(originalCenter.getX()-rect.getX()-rect.getWidth());
                moveY=factor*(originalCenter.getY()-rect.getY());
            }
        }

        //Center is in the arc [270-360] centered in lower right corner with same radius as circle, calculate distance from corner and adjust both axis 
        if (originalCenter.getX()>rect.getX()+rect.getWidth()&&originalCenter.getY()>rect.getY()+rect.getHeight()) {
            double dist=originalCenter.distance(rect.getX()+rect.getWidth(),rect.getY()+rect.getHeight());
            if (dist<radius) {
                double factor=(radius-dist)/dist;
                moveX=factor*(originalCenter.getX()-rect.getX()-rect.getWidth());
                moveY=factor*(originalCenter.getY()-rect.getY()-rect.getHeight());
            }
        }

        //Center is in the arc [180-270] centered in lower left corner with same radius as circle, calculate distance from corner and adjust both axis 
        if (originalCenter.getX()<rect.getX()&&originalCenter.getY()>rect.getY()+rect.getHeight()) {
            double dist=originalCenter.distance(rect.getX(),rect.getY()+rect.getHeight());
            if (dist<radius) {
                double factor=(radius-dist)/dist;
                moveX=factor*(originalCenter.getX()-rect.getX());
                moveY=factor*(originalCenter.getY()-rect.getY()-rect.getHeight());
            }
        }

        movedCenter=new Point2D.Double(originalCenter.getX()+moveX,originalCenter.getY()+moveY);
    }
    
    
    public static void main(String[] args) {
        Rectangle2D rect=new Rectangle2D.Double(240, 110, 100, 100);
        Point2D center=new Point2D.Double(280, 70);
        double radius=50;
        
        CircleOutside o=new CircleOutside();
        o.rect=rect;
        o.originalCenter=center;
        o.radius=radius;
        o.calc();
        o.setPreferredSize(new Dimension(800,600));
        JFrame frame=new JFrame("Test circle");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        frame.setContentPane(o);
        frame.pack();
        frame.setVisible(true);
    }
}
封装测试;
导入java.awt.Color;
导入java.awt.Dimension;
导入java.awt.Graphics;
导入java.awt.Graphics2D;
导入java.awt.event.MouseAdapter;
导入java.awt.event.MouseEvent;
导入java.awt.geom.Ellipse2D;
导入java.awt.geom.Point2D;
导入java.awt.geom.Rectangle2D;
导入javax.swing.JComponent;
导入javax.swing.JFrame;
公共类CircleOutside扩展JComponent{
保护矩形2d-rect;
受保护点2D原始中心;
保护双半径;
受保护的点2D移动中心;
@凌驾
受保护组件(图形g){
超级组件(g);
图形2d g2=(图形2d)g;
g2.绘制(rect);
g、 setColor(Color.red);
g2.绘制(新的椭圆E2D.Double(originalCenter.getX()-半径,originalCenter.getY()-半径,2*半径,2*半径));
g、 setColor(Color.green);
g2.绘制(新的椭圆E2D.Double(movedCenter.getX()-radius,movedCenter.getY()-radius,2*半径,2*半径));
addMouseListener(新的MouseAdapter(){
@凌驾
公共无效mouseClicked(MouseEvent e){
originalCenter=e.getPoint();
计算();
重新油漆();
}
});
}
公共无效计算(){
移动中心=原始中心;
//圆中心距边的距离大于半径,请勿移动
如果(originalCenter.getY()+radius=rect.getY()+rect.getHeight()){
返回;
}
if(originalCenter.getX()+radius=rect.getX()+rect.getWidth()){
返回;
}
双动x=0;
双动=0;
布尔movingY=false;
布尔movingX=false;
//居中投影到矩形的宽度,向上或向下移动

如果(originalCenter.getX()>=rect.getX()&&originalCenter.getX()对代码进行了以下修改:

  • 增加了一个函数,除了计算线段上相应的垂直点外,还计算点到线段的距离

  • 添加了确定点位于多边形内部还是外部的函数

  • 修改函数
    GetCirclerRectangleDisplacement
    以执行以下操作:

    • 创建一个边界多边形,将矩形的边延伸半径的长度。然后,如果圆心位于该边界多边形内,则需要将其移动到四(4)个边界多边形中的一个扩展边。函数
      pointInPolygon
      确定圆心是否在边界多边形中,如果在边界多边形中,则使用
      pointToSegmentDistance
      查找四(4)条扩展边之一上最近的点,该点现在代表新的圆心
    • 否则,如果圆心位于边界多边形之外,则该函数将检查圆心是否小于四个顶点之一的半径长度,如果小于,则将圆心移离顶点,使距离现在为半径

画布{display:flex;边距:0 auto;}
让canvas=document.querySelector(“canvas”);
设ctx=canvas.getContext(“2d”);
函数绘图(){
ctx.fillStyle=“#fff”;
画圈(圆圈x,圆圈y,圆圈);
drawSquare(平方位置x、平方位置y、平方W、平方H);
}
函数绘图圆(X中心、Y中心、半径){
ctx.fillStyle=“#fff”;
ctx.beginPath();
ctx.弧(xCenter,cCenter,半径,0,2*Math.PI);
ctx.fill();
}
函数drawSquare(x,y,w,h){
ctx.fillStyle=“#f0f”;
ctx.beginPath();
ctx.rect(x,y,w,h);
ctx.fill();
}
//来源和改编自https://stackoverflow.com/a/6853926/7696162
功能点到分段距离(点、分段、分段){
var A=点x-点x;
var B=点y-分段y;