在两点之间创建弧以便在Java中进行插值

时间:2014-03-24 22:08:41

标签: java interpolation spline

我有一个自上而下的游戏,我希望我的敌人能够以弧形移动屏幕。现在,它们在屏幕的两个边缘之间沿直线移动。我在一条边上生成一个起始位置,然后在屏幕的某处找到一个随机位置,并计算移动速度,我将角度的sin / cos乘以它们的速度变量。

我想用它们来指向它们之间产生一些弧线,然后用它来移动我的敌人。我想也许某种样条可以做到这一点,但我不完全确定如何创建一个,也不是更明显如何使用它来插入我的字符。我认为在这一点上它更多的是一个数学问题,而不是编程,但我希望有人可以提供帮助。感谢。

2 个答案:

答案 0 :(得分:0)

是的,样条曲线对你有用。具体来说,我会推荐一个三次样条,因为如果你想要做一个不同的形状,也许是街头霸王风格的上勾,你可以重复使用相同的代码。我记得三次样条是一个不错的通用解决方案。

至于求解三次样条函数,我建议您只使用谷歌伪代码,这对您来说很有意义。只有当你真的想要在飞行中一般性地求解合适的三次样条时。

在实践中,我想你想要的形状几乎一直都是一样的?如果是这样,您可以解决一些样条曲线的一般情况,并将其保存到某些快速数据结构中以提高性能。例如,对于y=x,包含必要信息(预处理)的合适数组将为x[0] = 1,x[1] = 1,x[2] = 2 ... x[n] = n

在实践中,您可以想出一个方程来建模一个简单的两点样条曲线。三次方程有4个未知数。所以你至少有两个数据点,你是起点和终点。另外,你可以计算他跳跃时的衍生物。对于你的第四点,你可以使用你希望他跳过的另一个点,或者当他降落时的衍生物。然后使用https://www.wolframalpha.com/为您解决等式。或者使用方程来求解立方体。

您可以做的另一件事就是使用二次方程+重力+风阻来计算弧。 Google再次知道如何解决这个问题。这个页面是我很快发现的,看起来它可以做到这一点。 http://www.physicsclassroom.com/class/vectors/Lesson-2/Non-Horizontally-Launched-Projectiles-Problem-Solv

答案 1 :(得分:0)

当您打算使用样条线时,可以使用Java中已有的Path2D类。您可以通过

组装任意路径
  • 搬到某一点
  • 附加一个线段
  • 附加二次曲线段
  • 附加三次曲线段

因此,组装此路径应该很简单:您可以创建一个二次曲线,该曲线从屏幕左边框的随机点开始,到屏幕右边框的随机点结束。作为控制点(两端),您可以使用屏幕中心随机位置的点。

(顺便说一句:当你设法将路径表示为通用Path2D时,你可以想象在为敌人设计路径时你有很大的自由。他们可以在圈子或zig中运行-zag,就像你喜欢的那样......)

这里可能更棘手的是让敌人跟随这条道路。

第一步还不是那么棘手:你可以用PathIterator走这条路。但是这个PathIterator只返回一个段 - 即二次曲线。这可以通过创建展平 PathIterator来缓解。这会将所有曲线转换为线段(分辨率可能很高,因此您不会注意到任何角落)。

然而,现在真正棘手的部分是:当迭代这些线段时,移动速度可能会有所不同:根据原始二次曲线的曲率,可能会创建更多或更少的线段。在最坏的情况下,当所有3个点都在一条线上时,只会创建一个线段,并且敌人将在一个步骤中遍历整个屏幕。因此,您必须确保以恒定速度遍历此路径。你必须计算在路径上迭代时已经走了多远,并且可能在路径的两个点之间插入一个位置。

我迅速组织了一个例子。它当然不是防弹,但可以作为起点。

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Path2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;


public class SplineMovementTest
{
    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                createAndShowGUI();
            }
        });
    }

    private static PathFollower pathFollower;

    private static void createAndShowGUI()
    {
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().setLayout(new BorderLayout());

        final Random random = new Random(0);

        final SplineMovementPanel p = new SplineMovementPanel();

        JButton generateButton = new JButton("Generate");
        generateButton.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                Shape spline = generateSpline(p,
                    random.nextDouble(),  
                    random.nextDouble(),  
                    random.nextDouble());
                p.setSpline(spline);
                pathFollower = new PathFollower(spline);
                p.repaint();
            }
        });
        frame.getContentPane().add(generateButton, BorderLayout.NORTH);
        startAnimation(p);

        frame.getContentPane().add(p, BorderLayout.CENTER);
        frame.setSize(800, 800);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    private static Shape generateSpline(
        JComponent c, double yLeft, double yCenter, double yRight)
    {
        Path2D spline = new Path2D.Double();
        double x0 = 0;
        double y0 = yLeft * c.getHeight();
        double x1 = c.getWidth() / 2;
        double y1 = yCenter * c.getHeight();
        double x2 = c.getWidth();
        double y2 = yRight * c.getHeight();
        spline.moveTo(x0, y0);
        spline.curveTo(x1, y1, x1, y1, x2, y2);
        return spline;
    }

    private static void startAnimation(final SplineMovementPanel p)
    {
        Timer timer = new Timer(20, new ActionListener()
        {
            double position = 0.0;

            @Override
            public void actionPerformed(ActionEvent e)
            {
                position += 0.005;
                position %= 1.0;
                if (pathFollower != null)
                {
                    Point2D point = pathFollower.computePointAt(
                        position * pathFollower.getPathLength());
                    p.setObjectLocation(point);
                }
            }
        });
        timer.start();
    }
}

class PathFollower
{
    private final List<Point2D> points;
    private final double pathLength;

    PathFollower(Shape spline)
    {
        points = createPointList(spline);
        pathLength = computeLength(points);
    }

    public double getPathLength()
    {
        return pathLength;
    }

    Point2D computePointAt(double length)
    {
        if (length < 0)
        {
            Point2D p = points.get(0);
            return new Point2D.Double(p.getX(), p.getY());
        }
        if (length > pathLength)
        {
            Point2D p = points.get(points.size()-1);
            return new Point2D.Double(p.getX(), p.getY());
        }
        double currentLength = 0;
        for (int i=0; i<points.size()-1; i++)
        {
            Point2D p0 = points.get(i);
            Point2D p1 = points.get(i+1);
            double distance = p0.distance(p1);
            double nextLength = currentLength + distance;
            if (nextLength > length)
            {
                double rel = 1 - (nextLength - length) / distance;
                double x0 = p0.getX();
                double y0 = p0.getY();
                double dx = p1.getX() - p0.getX();
                double dy = p1.getY() - p0.getY();
                double x = x0 + rel * dx;
                double y = y0 + rel * dy;
                return new Point2D.Double(x,y);
            }
            currentLength = nextLength;
        }
        Point2D p = points.get(points.size()-1);
        return new Point2D.Double(p.getX(), p.getY());
    }

    private static double computeLength(List<Point2D> points)
    {
        double length = 0;
        for (int i=0; i<points.size()-1; i++)
        {
            Point2D p0 = points.get(i);
            Point2D p1 = points.get(i+1);
            length += p0.distance(p1);
        }
        return length;
    }

    private static List<Point2D> createPointList(Shape shape)
    {
        List<Point2D> points = new ArrayList<Point2D>();
        PathIterator pi = shape.getPathIterator(null,  0.1);
        double coords[] = new double[6];
        while (!pi.isDone())
        {
            int s = pi.currentSegment(coords);
            switch (s)
            {
                case PathIterator.SEG_MOVETO:
                    points.add(new Point2D.Double(coords[0], coords[1]));

                case PathIterator.SEG_LINETO:
                    points.add(new Point2D.Double(coords[0], coords[1]));
            }
            pi.next();
        }
        return points;
    }

}


class SplineMovementPanel extends JPanel
{
    void setSpline(Shape shape)
    {
        this.spline = shape;
    }

    void setObjectLocation(Point2D objectLocation)
    {
        this.objectLocation = objectLocation;
        repaint();
    }
    private Shape spline = null;
    private Point2D objectLocation = null;


    @Override
    protected void paintComponent(Graphics gr)
    {
        super.paintComponent(gr);
        Graphics2D g = (Graphics2D)gr;
        g.setRenderingHint(
            RenderingHints.KEY_ANTIALIASING, 
            RenderingHints.VALUE_ANTIALIAS_ON);

        if (spline != null)
        {
            g.setColor(Color.BLACK);
            g.draw(spline);
        }

        if (objectLocation != null)
        {
            g.setColor(Color.RED);
            int x = (int)objectLocation.getX()-15;
            int y = (int)objectLocation.getY()-15;
            g.fillOval(x, y, 30, 30);
        }
    }


}