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); } }