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

2D空间射线反射Java代码异常:反射射线未按入射角反射问题排查

问题描述

运行提供的2D射线反射Java程序时,当用户创建的射线与硬编码直线相交,程序未能绘制出与入射角等角度的正确反射射线,反而呈现出随鼠标角度滚动的异常效果。

问题原因分析
  1. 反射角度计算逻辑完全错误
    原代码中尝试通过角度叠加计算反射方向的代码:

    angleBetween *= 2;
    angleBetween  += 2*(90 - angleBetween);
    angleBetween += mouseVector.getAngle();
    

    化简后实际等价于 angleBetween = 180 - angleBetween + mouseVector.getAngle(),这完全不符合反射定律的数学逻辑,无法得到正确的反射方向。

  2. Vector类角度计算存在bug
    setAngle(double... components)方法中使用Math.atan(components[0]/components[1])计算角度:

    • Math.atan仅能返回-90°到90°的角度,无法区分不同象限,导致向量角度计算错误。
    • 参数顺序错误,应该使用Math.atan2(components[1], components[0])(y分量在前,x分量在后)来正确计算向量与x轴的夹角。
  3. 未使用标准反射向量公式
    正确的反射计算应该基于向量运算的标准公式:反射向量r = 入射向量i - 2*(i·n)*n,其中n是反射面的单位法线向量。原代码试图通过角度叠加实现,绕路且错误。

修复方案
  1. 修正Vector类的角度计算
    将setAngle(double... components)方法中的角度计算替换为Math.atan2,并调整参数顺序,确保能正确识别所有象限的向量角度。

  2. 替换反射方向计算逻辑
    移除原有的错误角度叠加代码,改用标准向量反射公式计算反射向量:

    • 先将入射向量和法线向量单位化
    • 计算入射向量与法线的点积
    • 代入反射公式得到反射向量
    • 根据反射向量绘制反射射线
  3. 确保法线向量为单位向量
    在初始化反射面法线向量时,调用normalizeVector()方法将其转为单位向量,满足反射公式的要求。

  4. 修正交点判断逻辑
    原代码仅检查交点的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 20:41:59