粒子瞄准算法

发布于 2024-10-28 21:42:12 字数 580 浏览 1 评论 0原文

我正在构建一个粒子系统,我想添加的功能之一是“目标”功能。我想要做的是为每个粒子设置一个 X,Y 目标并使其到达那里,但不是沿着一条直线(废话),而是考虑到应用于粒子的所有其他运动效果。

我的粒子具有的相关参数:

  • posx, posy :具有任意值的 inits。在每个刻度上,speedx 和 speedy 分别添加到 posx 和 posy
  • speedx, speedy :具有任意值的 inits。在每个刻度上,accelx 和 accely 分别添加到 speedx speedy(如果有)
  • accelx、accely:具有任意值的 inits。目前的实现在粒子的整个生命周期中保持不变。
  • life :从任意值开始,系统每次tick减1。

我想要实现的是粒子在其最后一个生命周期到达目标 X,Y,同时从其原始值(速度和加速度)开始,因此朝向目标的运动将看起来“平滑”。我正在考虑将其朝目标方向加速,同时重新计算每次滴答所需的加速力。但感觉不太对劲,很想听听一些建议。

I'm building a particles systems, one of the features I'd like to add is a "target" feature. What I want to be able to do is set an X,Y target for each particle and make it go there, not in a straight line though (duh), but considering all other motion effects being applied on the particle.

The relevant parameters my particles have:

  • posx, posy : inits with arbitrary values. On each tick speedx and speedy are added to posx and posy respectively
  • speedx, speedy : inits with arbitrary values. On each tick accelx and accely are added to speedx speedy respectively if any)
  • accelx, accely : inits with arbitrary values. With current implementation stays constant through the lifespan of the particle.
  • life : starts with an arbitrary value, and 1 is reduced with each tick of the system.

What I want to achieve is the particle reaching the target X,Y on it's last life tick, while starting with it's original values (speeds and accelerations) so the motion towards the target will look "smooth". I was thinking of accelerating it in the direction of the target, while recalculating the needed acceleration force on each tick. That doesn't feel right though, would love to hear some suggestions.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

十年不长 2024-11-04 21:42:12

对于“平滑”运动,您可以保持速度恒定、加速度恒定或加加速度恒定。这取决于你所说的“顺利”和你所说的“无聊”。让我们保持加速度恒定。

从物理学的角度来看,你有这个约束

targetx - posx = speedx*life + 1/2accelx * life * life
targety - posy = speedy*life + 1/2accely * life * life

,因为行进的距离是v*t+1/2at^2。求解未知加速度给出

accelx = (targetx - posx - speedx*life) / (1/2 * life * life)
accely = (targety - posy - speedy*life) / (1/2 * life * life)

(为此,速度必须与时间采用相同的单位,例如“每刻度像素”,而生命是“刻度数”。)

因为您使用 欧拉积分,这不会使粒子精确地落在目标上。但我怀疑这会是一个真正的问题。

效果就像一个魅力:

result

另一张图片,这次有持续的急动

jerkx = 6.0f*(targetx-x - speedx*life - 0.5f*accelx*life*life)/(life*life*life) 

看起来曲线中还有另一个弯曲......
在此处输入图像描述

Java 代码

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;

@SuppressWarnings("serial")
public class TargetTest extends JPanel {

  List<Particle> particles = new ArrayList<Particle>();
  float tx, ty; // target position

  public TargetTest() {

    tx = 400;
    ty = 400;
    for (int i = 0; i < 50; i++)
      particles.add(new Particle(tx / 2 + (float) (tx * Math.random()), ty / 2
          + (float) (ty * Math.random())));

    this.setPreferredSize(new Dimension((int) tx * 2, (int) ty * 2));
  }

  @Override
  protected void paintComponent(Graphics g1) {
    Graphics2D g = (Graphics2D) g1;
    g.setColor(Color.black);
    // comment next line to draw curves
    g.fillRect(0, 0, getSize().width, getSize().height);

    for (Particle p : particles) {
      p.update();
      p.draw(g);
    }
  }

  public static void main(String[] args) {
    EventQueue.invokeLater(new Runnable() {
      public void run() {
        JFrame f = new JFrame("Particle tracking");
        final TargetTest world = new TargetTest();
        f.add(world);

        // 1 tick every 50 msec
        new Timer(50, new ActionListener() {
          @Override
          public void actionPerformed(ActionEvent arg0) {
            world.repaint();
          }
        }).start();

        f.pack();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setVisible(true);
      }
    });
  }

  class Particle {
    float x, y;// position
    float vx, vy;// speed
    float ax, ay;// acceleration
    float jx, jy;// jerk

    int life; // life

    float lastx, lasty;// previous position, needed to draw lines
    int maxlife; // maxlife, needed for color

    public Particle(float x, float y) {
      this.x = x;
      this.y = y;
      // pick a random direction to go to
      double angle = 2 * Math.PI * Math.random();
      setVelocity(angle, 2);// 2 pixels per tick = 2 pixels per 50 msec = 40
                            // pixels per second

      // the acceleration direction 'should' be close to being perpendicular to
      // the speed,
      // makes it look interesting, try commenting it if you don't believe me ;)
      if (Math.random() < 0.5)
        angle -= Math.PI / 2;
      else
        angle += Math.PI / 2;
      // add some randomness
      angle += (Math.random() - 0.5) * Math.PI / 10;
      setAcceleration(angle, 0.1);

      life = (int) (100 + Math.random() * 100);
      maxlife = life;
      lastx = x;
      lasty = y;
    }

    public void setVelocity(double angle, double speed) {
      vx = (float) (Math.cos(angle) * speed);
      vy = (float) (Math.sin(angle) * speed);
    }

    public void setAcceleration(double angle, double speed) {
      ax = (float) (Math.cos(angle) * speed);
      ay = (float) (Math.sin(angle) * speed);
    }

    @SuppressWarnings("unused")
    private void calcAcceleration(float tx, float ty) {
      ax = 2 * (tx - x - vx * life) / (life * life);
      ay = 2 * (ty - y - vy * life) / (life * life);
    }

    private void calcJerk(float tx, float ty) {
      jx = 6.0f * (tx - x - vx * life - 0.5f * ax * life * life)
          / (life * life * life);
      jy = 6.0f * (ty - y - vy * life - 0.5f * ay * life * life)
          / (life * life * life);
    }

    public void update() {
      lastx = x;
      lasty = y;
      if (--life <= 0)
        return;

      // calculate jerk
      calcJerk(tx, ty);
      // or uncomment and calculate the acceleration instead
      // calcAcceleration(tx,ty);

      ax += jx;
      ay += jy;// increase acceleration

      vx += ax;
      vy += ay;// increase speed

      x += vx;
      y += vy;// increase position
    }

    public void draw(Graphics2D g) {
      if (life < 0)
        return;
      g.setColor(new Color(255 - 255 * life / maxlife, 
            255 * life / maxlife,0));
      g.drawLine((int) x, (int) y, (int) lastx, (int) lasty);
    }
  }
}

For a "smooth" motion, you either keep the speed constant, or the acceleration constant, or the jerk constant. That depends on what you call "smooth" and what you call "boring". Let's keep the acceleration constant.

From a physics point of view, you have this constraint

targetx - posx = speedx*life + 1/2accelx * life * life
targety - posy = speedy*life + 1/2accely * life * life

Because distance traveled is v*t+1/2at^2. Solving for the unknown acceleration gives

accelx = (targetx - posx - speedx*life) / (1/2 * life * life)
accely = (targety - posy - speedy*life) / (1/2 * life * life)

(For this to work speedy must be in the same unit as time, for example "pixels per tick" and life is a number of "ticks". )

Since you use euler integration, this will not bring the particle exactly on the target. But I doubt it'll be a real issue.

Works like a charm:

result

Another picture, this time with constant jerk

jerkx = 6.0f*(targetx-x - speedx*life - 0.5f*accelx*life*life)/(life*life*life) 

Looks like there is another bend in the curve...
enter image description here

Java code

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;

@SuppressWarnings("serial")
public class TargetTest extends JPanel {

  List<Particle> particles = new ArrayList<Particle>();
  float tx, ty; // target position

  public TargetTest() {

    tx = 400;
    ty = 400;
    for (int i = 0; i < 50; i++)
      particles.add(new Particle(tx / 2 + (float) (tx * Math.random()), ty / 2
          + (float) (ty * Math.random())));

    this.setPreferredSize(new Dimension((int) tx * 2, (int) ty * 2));
  }

  @Override
  protected void paintComponent(Graphics g1) {
    Graphics2D g = (Graphics2D) g1;
    g.setColor(Color.black);
    // comment next line to draw curves
    g.fillRect(0, 0, getSize().width, getSize().height);

    for (Particle p : particles) {
      p.update();
      p.draw(g);
    }
  }

  public static void main(String[] args) {
    EventQueue.invokeLater(new Runnable() {
      public void run() {
        JFrame f = new JFrame("Particle tracking");
        final TargetTest world = new TargetTest();
        f.add(world);

        // 1 tick every 50 msec
        new Timer(50, new ActionListener() {
          @Override
          public void actionPerformed(ActionEvent arg0) {
            world.repaint();
          }
        }).start();

        f.pack();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setVisible(true);
      }
    });
  }

  class Particle {
    float x, y;// position
    float vx, vy;// speed
    float ax, ay;// acceleration
    float jx, jy;// jerk

    int life; // life

    float lastx, lasty;// previous position, needed to draw lines
    int maxlife; // maxlife, needed for color

    public Particle(float x, float y) {
      this.x = x;
      this.y = y;
      // pick a random direction to go to
      double angle = 2 * Math.PI * Math.random();
      setVelocity(angle, 2);// 2 pixels per tick = 2 pixels per 50 msec = 40
                            // pixels per second

      // the acceleration direction 'should' be close to being perpendicular to
      // the speed,
      // makes it look interesting, try commenting it if you don't believe me ;)
      if (Math.random() < 0.5)
        angle -= Math.PI / 2;
      else
        angle += Math.PI / 2;
      // add some randomness
      angle += (Math.random() - 0.5) * Math.PI / 10;
      setAcceleration(angle, 0.1);

      life = (int) (100 + Math.random() * 100);
      maxlife = life;
      lastx = x;
      lasty = y;
    }

    public void setVelocity(double angle, double speed) {
      vx = (float) (Math.cos(angle) * speed);
      vy = (float) (Math.sin(angle) * speed);
    }

    public void setAcceleration(double angle, double speed) {
      ax = (float) (Math.cos(angle) * speed);
      ay = (float) (Math.sin(angle) * speed);
    }

    @SuppressWarnings("unused")
    private void calcAcceleration(float tx, float ty) {
      ax = 2 * (tx - x - vx * life) / (life * life);
      ay = 2 * (ty - y - vy * life) / (life * life);
    }

    private void calcJerk(float tx, float ty) {
      jx = 6.0f * (tx - x - vx * life - 0.5f * ax * life * life)
          / (life * life * life);
      jy = 6.0f * (ty - y - vy * life - 0.5f * ay * life * life)
          / (life * life * life);
    }

    public void update() {
      lastx = x;
      lasty = y;
      if (--life <= 0)
        return;

      // calculate jerk
      calcJerk(tx, ty);
      // or uncomment and calculate the acceleration instead
      // calcAcceleration(tx,ty);

      ax += jx;
      ay += jy;// increase acceleration

      vx += ax;
      vy += ay;// increase speed

      x += vx;
      y += vy;// increase position
    }

    public void draw(Graphics2D g) {
      if (life < 0)
        return;
      g.setColor(new Color(255 - 255 * life / maxlife, 
            255 * life / maxlife,0));
      g.drawLine((int) x, (int) y, (int) lastx, (int) lasty);
    }
  }
}
一袭白衣梦中忆 2024-11-04 21:42:12

您可以认为您的颗粒最初“施加”了一个力 (Fv),该力对应于其初始速度所具有的惯性。然后应用与目标距离成正比的吸引力 (Fa)。然后,您可以将这些力相加,并给定粒子权重,您可以推导出在时间 t 时考虑的加速度。

Fa(t) = (Constant / distanceToTarget(t))* [direction to target]
Fv(t) = [initialForce] * dampening(t)
a(t) = (Fa(t) + Fv(t)) / mass

然后你可以像往常一样从 v(t-1) 和 a(t) 计算 v(t)

编辑:我忘记了粒子的生命可以直接从到目标的距离计算(例如:生命 = 距离 / 初始距离将从 1 开始,接近 0 接近目标)

编辑:您可以将其视为一种磁铁。请参阅维基百科了解力公式

You could consider that your particule is initially "applied" a force (Fv) which corresponds to the inertia it has from its initial velocity. Then you apply an attraction force (Fa) that is proportionnal to the distance to the target. You can then sum those forces, and given a particle weight, you can deduce acceleration to consider at time t.

Fa(t) = (Constant / distanceToTarget(t))* [direction to target]
Fv(t) = [initialForce] * dampening(t)
a(t) = (Fa(t) + Fv(t)) / mass

Then you can compute v(t) from v(t-1) and a(t) as usual

Edit: I forgot the life of the particle can directly be computed from the distance to the target (for instance: life = distance / initialDistance will go from 1 at start and approch 0 near the target)

Edit: You could think of this as a kind of magnet. See wikipedia for the force formula.

辞取 2024-11-04 21:42:12

您可以使用的一种运动是匀加速 http://en.wikipedia.org/wiki/ Acceleration#Uniform_acceleration

你的粒子将平滑地移向目标并以相当高的速度撞击它。

为了满足你规定的标准,请执行以下操作:

one kind of movement you can use is the uniform acceleration http://en.wikipedia.org/wiki/Acceleration#Uniform_acceleration

Your particles will make a smoth move towards the target and hit it with rather high velocity

For meeting your stated criteria, do the following:

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文