2D空间射线反射Java代码异常:反射射线未按入射角反射问题排查
问题描述
运行提供的2D射线反射Java程序时,当用户创建的射线与硬编码直线相交,程序未能绘制出与入射角等角度的正确反射射线,反而呈现出随鼠标角度滚动的异常效果。
问题原因分析
反射角度计算逻辑完全错误
原代码中尝试通过角度叠加计算反射方向的代码:angleBetween *= 2; angleBetween += 2*(90 - angleBetween); angleBetween += mouseVector.getAngle();化简后实际等价于
angleBetween = 180 - angleBetween + mouseVector.getAngle(),这完全不符合反射定律的数学逻辑,无法得到正确的反射方向。Vector类角度计算存在bug
setAngle(double... components)方法中使用Math.atan(components[0]/components[1])计算角度:Math.atan仅能返回-90°到90°的角度,无法区分不同象限,导致向量角度计算错误。- 参数顺序错误,应该使用
Math.atan2(components[1], components[0])(y分量在前,x分量在后)来正确计算向量与x轴的夹角。
未使用标准反射向量公式
正确的反射计算应该基于向量运算的标准公式:反射向量r = 入射向量i - 2*(i·n)*n,其中n是反射面的单位法线向量。原代码试图通过角度叠加实现,绕路且错误。
修复方案
修正Vector类的角度计算
将setAngle(double... components)方法中的角度计算替换为Math.atan2,并调整参数顺序,确保能正确识别所有象限的向量角度。替换反射方向计算逻辑
移除原有的错误角度叠加代码,改用标准向量反射公式计算反射向量:- 先将入射向量和法线向量单位化
- 计算入射向量与法线的点积
- 代入反射公式得到反射向量
- 根据反射向量绘制反射射线
确保法线向量为单位向量
在初始化反射面法线向量时,调用normalizeVector()方法将其转为单位向量,满足反射公式的要求。修正交点判断逻辑
原代码仅检查交点的x轴范围,新增y轴范围检查,确保交点确实在硬编码直线的线段范围内。
修复后的完整代码
TestPanel类
import javax.swing.*; import java.awt.*; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.geom.Point2D; import java.text.InvalidPropertiesFormatException; public class TestPanel extends JPanel { private static final Point2D originPoint = new Point(2000, 100); private static final Point2D endPoint = new Point(2500, 500); private final Point2D mousePoint; private final double slope; private final Vector lineNormal; // 单位化的法线向量 public TestPanel() { setFocusable(true); requestFocus(); MouseListener mouseListener = new MouseListener(); this.mousePoint = mouseListener.mousePoint; addMouseMotionListener(mouseListener); double deltaX = endPoint.getX() - originPoint.getX(); double deltaY = endPoint.getY() - originPoint.getY(); slope = deltaY == 0 ? 0 : deltaY / deltaX; // 生成直线的法线向量并单位化 lineNormal = new Vector(-deltaY, deltaX); lineNormal.normalizeVector(); } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2 = (Graphics2D) g; g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); int thickness = 30; drawCircle(g, thickness, new Point(getWidth() / 2, getHeight() / 2)); g2.setStroke(new BasicStroke(5)); g.drawLine((int) originPoint.getX(), (int) originPoint.getY(), (int) endPoint.getX(), (int) endPoint.getY()); g.drawLine(getWidth() / 2, getHeight() / 2, (int) mousePoint.getX(), (int) mousePoint.getY()); g.setColor(Color.RED); Point2D intersectingPoint = getIntersectingPoint(); if (intersectingPoint != null && intersectingPoint.getX() >= Math.min(originPoint.getX(), endPoint.getX()) && intersectingPoint.getX() <= Math.max(originPoint.getX(), endPoint.getX()) && intersectingPoint.getY() >= Math.min(originPoint.getY(), endPoint.getY()) && intersectingPoint.getY() <= Math.max(originPoint.getY(), endPoint.getY()) // 确保射线是从圆心朝向直线 && ((mousePoint.getX() - getWidth()/2) * (intersectingPoint.getX() - getWidth()/2) >= 0) && ((mousePoint.getY() - getHeight()/2) * (intersectingPoint.getY() - getHeight()/2) >= 0) ) { drawCircle(g, 10, intersectingPoint); // 入射向量:从圆心指向交点(朝向反射面) double incidentX = intersectingPoint.getX() - getWidth() / 2; double incidentY = intersectingPoint.getY() - getHeight() / 2; Vector incidentVector = new Vector(incidentX, incidentY); incidentVector.normalizeVector(); try { // 应用反射向量公式:r = i - 2*(i·n)*n double dotProduct = incidentVector.dot(lineNormal); double rX = incidentVector.getComponentAt(0) - 2 * dotProduct * lineNormal.getComponentAt(0); double rY = incidentVector.getComponentAt(1) - 2 * dotProduct * lineNormal.getComponentAt(1); Vector reflectVector = new Vector(rX, rY); // 绘制反射射线 int steps = 200; double endX = intersectingPoint.getX() + reflectVector.getComponentAt(0) * steps; double endY = intersectingPoint.getY() + reflectVector.getComponentAt(1) * steps; g.drawLine((int) intersectingPoint.getX(), (int) intersectingPoint.getY(), (int) endX, (int) endY); // 显示入射角信息(可选) double angleBetween = incidentVector.angleBetween(lineNormal); g.setFont(new Font("Arial", Font.BOLD, 30)); String angleStr = String.format("入射角: %.1f°", 90 - angleBetween); int width = g.getFontMetrics().stringWidth(angleStr); g.drawString(angleStr, getWidth() - width - 10, getHeight() - 100); } catch (InvalidPropertiesFormatException e) { throw new RuntimeException(e); } } } private Point2D getIntersectingPoint() { double centerX = getWidth() / 2; double centerY = getHeight() / 2; double mouseX = mousePoint.getX(); double mouseY = mousePoint.getY(); // 参数方程法计算两条线段的交点 double dx1 = mouseX - centerX; double dy1 = mouseY - centerY; double dx2 = endPoint.getX() - originPoint.getX(); double dy2 = endPoint.getY() - originPoint.getY(); double denominator = dx1 * dy2 - dy1 * dx2; if (denominator == 0) { return null; // 平行或重合 } double t = ((originPoint.getX() - centerX) * dy2 - (originPoint.getY() - centerY) * dx2) / denominator; double s = ((originPoint.getX() - centerX) * dy1 - (originPoint.getY() - centerY) * dx1) / denominator; // 检查交点是否在射线和线段范围内 if (t >= 0 && s >= 0 && s <= 1) { double x = centerX + t * dx1; double y = centerY + t * dy1; return new Point2D.Double(x, y); } return null; } private void drawCircle(Graphics graphics, int rad, Point2D originPoint) { graphics.fillOval((int) originPoint.getX() - (rad / 2), (int) originPoint.getY() - (rad / 2), rad, rad); } private class MouseListener extends MouseAdapter { private final Point2D mousePoint = new Point(0, 0); @Override public void mouseMoved(MouseEvent e) { mousePoint.setLocation(e.getX(), e.getY()); repaint(); } @Override public void mouseDragged(MouseEvent e) { mousePoint.setLocation(e.getX(), e.getY()); repaint(); } } public static void main(String[] args) { JFrame frame = new JFrame("Test"); frame.setExtendedState(JFrame.MAXIMIZED_BOTH); frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); frame.setUndecorated(true); frame.setContentPane(new TestPanel()); frame.setVisible(true); } }
Vector类
import java.text.InvalidPropertiesFormatException; import java.util.Arrays; public class Vector { private double[] components; private double magnitude; private double angle; public Vector(double... components) { if (components.length <= 1) throw new IllegalArgumentException("Components length must be larger than 1"); this.components = Arrays.copyOf(components, components.length); this.magnitude = calculateMagnitude(); setAngle(components); } public Vector(int angle) { magnitude = 1; setAngle(angle); } public void normalizeVector() { if (magnitude != 1) { for (int i = 0; i < components.length; i++) components[i] /= magnitude; magnitude = 1; } } public Vector getPerpendicularVector() { double[] perpendicularComponents = new double[this.getDimensions()]; perpendicularComponents[0] = -components[1]; perpendicularComponents[1] = components[0]; for (int i = 2; i < perpendicularComponents.length; i++) perpendicularComponents[i] = 0; return new Vector(perpendicularComponents); } public double angleBetween(Vector vector) throws InvalidPropertiesFormatException { double dotProduct = this.dot(vector); double magnitudesProduct = magnitude * vector.magnitude; double cosTheta = dotProduct / magnitudesProduct; // 处理浮点精度问题,避免acos返回NaN cosTheta = Math.max(-1.0, Math.min(1.0, cosTheta)); double angleInRadians = Math.acos(cosTheta); return Math.toDegrees(angleInRadians); } public static double dot(double[] aComponents, double[] bComponents) throws InvalidPropertiesFormatException { if (aComponents.length != bComponents.length) throw new InvalidPropertiesFormatException("Vector a and Vector b must have the same components length"); double sum = 0.0; for (int i = 0; i < aComponents.length; i++) sum += aComponents[i] * bComponents[i]; return sum; } public double dot(Vector vector) throws InvalidPropertiesFormatException { return dot(this, vector); } public double dot(double... components) throws InvalidPropertiesFormatException { return dot(this.components, components); } public static double dot(Vector aVector, Vector bVector) throws InvalidPropertiesFormatException { return dot(aVector.components, bVector.components); } public double getMagnitude() { return magnitude; } public double[] getComponents() { return components; } public double getComponentAt(int i) { if (i >= components.length || i < 0) throw new IndexOutOfBoundsException(String.format("Index %s out of bounds for %s component vector", i, components.length)); return components[i]; } public int getDimensions() { return components.length; } public double getAngle() { return angle; } public void setAngle(double angle) { this.angle = angle; double radians = Math.toRadians(angle); this.components = new double[]{Math.cos(radians), Math.sin(radians)}; } public void setAngle(double... components) { // 修正角度计算:使用atan2(y, x)获取全象限角度 angle = Math.toDegrees(Math.atan2(components[1], components[0])); } private double calculateMagnitude() { double sum = 0.0; for (double component : components) sum += Math.pow(component, 2); return Math.sqrt(sum); } }
内容的提问来源于stack exchange,提问作者user17370683
相关产品推荐
相关产品推荐

