Swing/Line Circle Collision

Line Circle Collision Detection

Soul-Learner 2016. 8. 17. 18:03

선분과 원의 충돌검사 예




AC 벡터와 AB 벡터 간의 벡터의 내적을 이용하여 AD길이를 구할 수 있고 AC길이와 AD길이를 이용하면 코사인 비를 구할 수 있으므로 CAD의 사잇각을 구할 수 있다


CAD 사잇각을 이용하여 사인비를 구하고 사인비에 AC 길이를 곱해주면 CD의 길이를 구할 수 있다.


CD의 길이가 원의 반지름 이하가 될 대 선분과 원의 충돌로 판정할 수 있다






아래의 코드를 실행하고 화면에 보이는 오렌지색 볼을 마우스로 드래그하여 원하는 곳에 위치한 후에 엔터키를 치면 공이 내려가면서 선분과 충돌하면 그 위치에 정지한다. 정지한 공을 마우스로 드래그하여 다시 임의의 장소에 위치시키고 다시 엔터키를 치면 몇번이고 충돌 테스트를 실행할 수 있다.


JFrame 클래스

package collision;

import java.awt.Container;
import java.awt.EventQueue;

import javax.swing.*;

public class LineCircleCollision extends JFrame
{
	public LineCircleCollision(){
		setTitle("선과 원의 충돌검사");
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		Container c = this.getContentPane();
		c.setLayout(null);
		setBounds(100, 50, 640, 600);
		
		GamePanel gp = new GamePanel();
		add(gp);
		
		setVisible(true);
	}
	
	
	
	public static void main(String[] args) 
	{
		//JPanel에서 키리스너가 작동하려면 모든 코드가 EDT(Event Dispatched Thread)안에서 실행되어야 한다
		EventQueue.invokeLater(new Runnable() {
			@Override
			public void run() {
				new LineCircleCollision();
			}
		});
	}
}



GamePanel.java

package collision;

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;

import javax.swing.*;
 
public class GamePanel extends JPanel
{
    BufferedImage line, circle;
    int x=400;          //공의 초기 위치
    int y=0;
    int diffX, diffY;   // 공과 클릭된 지점의 거리차이
    
    public GamePanel()
    {
        setBounds(0,0,600,550);
        setLayout(null);
        setBackground(Color.WHITE);
        setFocusable(true);
        requestFocus();
 
        createLine();
        createCircle();

        this.addMouseListener(new MouseAdapter() {
			@Override
			public void mousePressed(MouseEvent e) {
				diffX = e.getX()-x;
				diffY = e.getY()-y;
			}
		});
        
        this.addMouseMotionListener(new MouseMotionAdapter() {
            @Override
            public void mouseDragged(MouseEvent e) {
                int mx = e.getX();
                int my = e.getY();
                int cx = x+50;
                int cy = y+50;
                int dx = cx-mx;
                int dy = cy-my;
                if(((dx*dx + dy*dy)>(50*50))) return;
                x = mx-diffX;
                y = my-diffY;
                repaint();
            }
        });
         
        this.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                if(e.getKeyCode()==KeyEvent.VK_ENTER){
                    playBall();
                }
            }
        });
    }// end of constructor
     
    void playBall(){
        new Thread() {
            @Override
            public void run() {
                while(true){
                    y++;
                    repaint();
                    if(isCollision()){
                        break;
                    }
                    try {
                        Thread.sleep(33);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();
    }
 
    @Override
    protected void paintComponent(Graphics g) 
    {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g;
        g2d.drawImage(line, 0, 0, null);
        g2d.drawImage(circle, x, y, null);
    }
     
    boolean isCollision()
    {
        double cx = x+50; // 원의 중심좌표(x)
        double cy = y+50; //원의 좌표에 반지름을 더하여 원의 중심을 구함(y)
        Vector2D circleVec = new Vector2D(cx,cy);
        Vector2D lineVec = new Vector2D(500,500);
        Vector2D unitVec = lineVec.unit();
        double dotVal = circleVec.dot(unitVec);
        double len = circleVec.length();
        double cosVal = dotVal/len;
        double rad = Math.acos(cosVal);
        double height = Math.sin(rad)*len;
        return height<=50 ? true : false;
    }
     
    void createLine()
    {
        line = new BufferedImage(500,500,BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2d = (Graphics2D)line.getGraphics();
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setBackground(Color.WHITE);
        g2d.setColor(Color.BLACK);
        g2d.drawLine(0, 0, 500, 500);
        g2d.dispose();
    }
     
    void createCircle()
    {
        circle = new BufferedImage(100,100,BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2d = (Graphics2D)circle.getGraphics();
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setBackground(Color.WHITE);
        g2d.setColor(Color.ORANGE);
        g2d.fillOval(0,0,100,100);
        g2d.dispose();
    }
}



Vector2D.java

package collision;

public class Vector2D 
{
    double x,y;
     
    public Vector2D() {}
     
    public Vector2D(double x, double y){
        this.x = x;
        this.y = y;
    }
     
    public Vector2D add(Vector2D v){
        return new Vector2D(this.x+v.x, this.y+v.y);
    }
     
    public Vector2D sub(Vector2D v){
        return new Vector2D(this.x-v.x, this.y-v.y);
    }
     
    public Vector2D mul(double val){
        return new Vector2D(this.x*val, this.y*val);
    }
     
    public Vector2D div(double val){
        return new Vector2D(this.x/val, this.y/val);
    }
     
    public double length(){
        return Math.sqrt(x*x + y*y);
    }
     
    public double dot(Vector2D v){
        return this.x*v.x + this.y*v.y;
    }
     
    public Vector2D inv() {
        return new Vector2D(this.x*-1, this.y*-1);
    }
    
    public Vector2D unit() {
    	return div( length() );
    }
    
    /** 파라미터로 전달된 일반각(Deg)의 방향에 대한 수직벡터를 단위벡터로 리턴한다 */
    public static Vector2D getVertical(double deg){
        double rad = Math.toRadians(deg-90);
        double sinVal = Math.sin(rad);
        double cosVal = Math.cos(rad);
        return new Vector2D(cosVal, sinVal);
    }
 
    public static void main(String[] args) {
        /* 벡터(5,5)의 방향과 속도로 반사판에 충돌한 공이 정반사할 때의 반사벡터를 구해보세요
        *  반사판의 경사각도는 15도 이다
        */
        Vector2D incVec = new Vector2D(5,5);
        System.out.printf("입사벡터의 성분: x(%f), y(%f) %n", incVec.x, incVec.y);
        Vector2D invVec = incVec.inv();
        System.out.printf("역벡터의 성분: x(%f), y(%f) %n", invVec.x, invVec.y);
        Vector2D vertVec = Vector2D.getVertical(15); //반사판에 수직인 단위벡터
        System.out.printf("반사판에 수직인 벡터의 성분: x(%f), y(%f) %n", vertVec.x, vertVec.y);
        double dotVal = invVec.dot(vertVec);
        System.out.printf("내적값: x(%f) %n", dotVal);
        vertVec = vertVec.mul(dotVal);
        System.out.printf("완성된 수직 벡터의 성분: x(%f), y(%f) %n", vertVec.x, vertVec.y);
        Vector2D reflVec = incVec.add(vertVec).add(vertVec);
        System.out.printf("반사 벡터의 성분: x(%f), y(%f) %n", reflVec.x, reflVec.y);
    }
}