首页 / JAVA / java – 如何用线程实现缓动函数
java – 如何用线程实现缓动函数
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了java – 如何用线程实现缓动函数,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含11323字,纯文字阅读大概需要17分钟。
内容图文
我正在尝试找到一种有效,正常或简单的方法来将缓动函数实现到我的java程序中.我得到了缓动功能,但我觉得有一种更有效的方法可以做到;一个我看不到的,可能是因为隧道视野.这是我的代码;有人可以告诉我我应该做些什么不同,或者指出我需要去研究的方向
public class slide extends JPanel implements Runnable {
Thread ease = new Thread(this);
float total = 0;
float dur;
slide() {
ease.start();
setLayout(null);
}
public float calc(float t, float b, float c, float d) {
return c * t / d + b;
}
public void run() {
while (true) {
try {
if (total < 50) {
total += 1;
} else {
ease.stop();
}
setBounds(400, Math.round(200 * total / 50 + 0), 250, 150);
repaint();
System.out.println(total + " " + dur);
ease.sleep(10);
} catch (Exception e) {
}
}
}
}
我试图实现我在网上找到的线性缓动函数的calc()方法,但它实际上没用,因为我被迫无法使它工作,除非将方程直接插入到
解决方法:
好的,所以动画是一个相当复杂和深入的主题,我不打算在这里介绍,它还涉及很多我不太了解的数学,所以我们不会进入大量的深度或细节,有更好的人,然后我可以解释它,你可以在网上阅读它
首先,我们做出一些假设……
动画是随时间的变化,时间是可变的. Easement是(在这种情况下)速度随时间的变化.这意味着动画的速度对于任何给定的时间点都是可变的.
基本上,我们想要做的是“规范化”一切.也就是说,在动画开始时,时间为0,结束时为1,其间的所有其他内容都是这两个值之间的分数.
如果你能这样想,事情变得容易多了.因此,基于时间轴上的给定点,您可以决定应该做什么.例如,在50%的时间内,您应该在起点和终点之间
好的,但这对我们有什么帮助?如果我们要绘制一个轻松和缓出的动画,它看起来就像……
其中x轴是时间,y轴是速度(两轴都在0和1之间).因此,在x(在时间上)的任何给定点,我们应该能够计算速度.
现在,我们可以使用Bézierspine / curve的一些数学来计算,并计算时间轴上给定点的对象速度.
现在,我直接从Timing Framework中借用了大部分代码,但是如果你真的很感兴趣,你也可以看一下Bézier Curves for your Games: A Tutorial
(nb:我确实写了这样的东西,然后2天后,发现Timing Framework已经实现了……这是一个有趣的练习…)
现在,关于这个实现的重要注意事项是,它实际上不会返回对象的速度,但它会沿着时间轴(0-1)返回一段时间,好吧,听起来很奇怪,但它是什么允许你做的是沿时间线计算起点和终点之间的当前位置(startValue((endValue – startValue)*进度))
我不会详细介绍这个,因为我真的不懂数学,我只知道如何应用它,但基本上,我们计算沿曲线的点(x / y),我们然后将这些值(0-1)标准化,以便更容易查找.
插值方法使用二分搜索在给定的时间内找到最接近的匹配点,然后计算该点的速度/ y位置
public class SplineInterpolator {
private final double points[];
private final List<PointUnit> normalisedCurve;
public SplineInterpolator(double x1, double y1, double x2, double y2) {
points = new double[]{ x1, y1, x2, y2 };
final List<Double> baseLengths = new ArrayList<>();
double prevX = 0;
double prevY = 0;
double cumulativeLength = 0;
for (double t = 0; t <= 1; t += 0.01) {
Point2D xy = getXY(t);
double length = cumulativeLength
+ Math.sqrt((xy.getX() - prevX) * (xy.getX() - prevX)
+ (xy.getY() - prevY) * (xy.getY() - prevY));
baseLengths.add(length);
cumulativeLength = length;
prevX = xy.getX();
prevY = xy.getY();
}
normalisedCurve = new ArrayList<>(baseLengths.size());
int index = 0;
for (double t = 0; t <= 1; t += 0.01) {
double length = baseLengths.get(index++);
double normalLength = length / cumulativeLength;
normalisedCurve.add(new PointUnit(t, normalLength));
}
}
public double interpolate(double fraction) {
int low = 1;
int high = normalisedCurve.size() - 1;
int mid = 0;
while (low <= high) {
mid = (low + high) / 2;
if (fraction > normalisedCurve.get(mid).getPoint()) {
low = mid + 1;
} else if (mid > 0 && fraction < normalisedCurve.get(mid - 1).getPoint()) {
high = mid - 1;
} else {
break;
}
}
/*
* The answer lies between the "mid" item and its predecessor.
*/
final PointUnit prevItem = normalisedCurve.get(mid - 1);
final double prevFraction = prevItem.getPoint();
final double prevT = prevItem.getDistance();
final PointUnit item = normalisedCurve.get(mid);
final double proportion = (fraction - prevFraction) / (item.getPoint() - prevFraction);
final double interpolatedT = prevT + (proportion * (item.getDistance() - prevT));
return getY(interpolatedT);
}
protected Point2D getXY(double t) {
final double invT = 1 - t;
final double b1 = 3 * t * invT * invT;
final double b2 = 3 * t * t * invT;
final double b3 = t * t * t;
final Point2D xy = new Point2D.Double((b1 * points[0]) + (b2 * points[2]) + b3, (b1 * points[1]) + (b2 * points[3]) + b3);
return xy;
}
protected double getY(double t) {
final double invT = 1 - t;
final double b1 = 3 * t * invT * invT;
final double b2 = 3 * t * t * invT;
final double b3 = t * t * t;
return (b1 * points[2]) + (b2 * points[3]) + b3;
}
public class PointUnit {
private final double distance;
private final double point;
public PointUnit(double distance, double point) {
this.distance = distance;
this.point = point;
}
public double getDistance() {
return distance;
}
public double getPoint() {
return point;
}
}
}
如果我们做某事……
SplineInterpolator si = new SplineInterpolator(1, 0, 0, 1);
for (double t = 0; t <= 1; t += 0.1) {
System.out.println(si.interpolate(t));
}
我们得到像……
0.0
0.011111693284790492
0.057295031944523504
0.16510933001160544
0.3208510585798438
0.4852971690762217
0.6499037832761319
0.8090819765428142
0.9286158775101805
0.9839043020410436
0.999702
好吧,现在你可能在想,“等一下,这是一个线性的进展!”,但事实并非如此,如果你画了它,你会发现前三个和后三个值非常接近,其他值差不多在不同程度上,这是我们的“进步”价值,我们应该在时间轴上走多远
所以现在,你的头应该爆炸(我的是) – 这就是为什么我说,使用一个框架!
但你怎么用它?!这是有趣的部分,现在记住,一切都是可变的,动画的持续时间,对象随时间的速度,刻度或更新的数量,它都是变量……
这很重要,因为这就是这种东西的力量所在!例如,如果动画由于某些外部因素而停滞,则此实现能够简单地跳过那些“帧”,而不是陷入瓶颈和蹒跚.这可能听起来像是一件坏事,但请相信我,这就是愚弄眼睛“思考”某些事情正在发生变化;)
(以下就像是8fps,所以很糟糕)
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.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private int startAt = 0;
private int endAt;
private int x = startAt;
private Timer timer;
private SplineInterpolator splineInterpolator;
private long startTime = -1;
private long playTime = 5000; // 5 seconds
public TestPane() {
splineInterpolator = new SplineInterpolator(1, 0, 0, 1);
timer = new Timer(5, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (startTime < 0) {
startTime = System.currentTimeMillis();
}
long now = System.currentTimeMillis();
long duration = now - startTime;
double t = (double) duration / (double) playTime;
if (duration >= playTime) {
t = 1;
}
double progress = splineInterpolator.interpolate(t);
x = startAt + ((int) Math.round((endAt - startAt) * progress));
repaint();
}
});
timer.setInitialDelay(0);
addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (!timer.isRunning()) {
startTime = -1;
startAt = 0;
endAt = getWidth() - 10;
timer.start();
}
}
});
}
@Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.RED);
g2d.fillRect(x, (getHeight() / 2) - 5, 10, 10);
g2d.dispose();
}
}
public static class SplineInterpolator {
private final double points[];
private final List<PointUnit> normalisedCurve;
public SplineInterpolator(double x1, double y1, double x2, double y2) {
points = new double[]{x1, y1, x2, y2};
final List<Double> baseLengths = new ArrayList<>();
double prevX = 0;
double prevY = 0;
double cumulativeLength = 0;
for (double t = 0; t <= 1; t += 0.01) {
Point2D xy = getXY(t);
double length = cumulativeLength
+ Math.sqrt((xy.getX() - prevX) * (xy.getX() - prevX)
+ (xy.getY() - prevY) * (xy.getY() - prevY));
baseLengths.add(length);
cumulativeLength = length;
prevX = xy.getX();
prevY = xy.getY();
}
normalisedCurve = new ArrayList<>(baseLengths.size());
int index = 0;
for (double t = 0; t <= 1; t += 0.01) {
double length = baseLengths.get(index++);
double normalLength = length / cumulativeLength;
normalisedCurve.add(new PointUnit(t, normalLength));
}
}
public double interpolate(double fraction) {
int low = 1;
int high = normalisedCurve.size() - 1;
int mid = 0;
while (low <= high) {
mid = (low + high) / 2;
if (fraction > normalisedCurve.get(mid).getPoint()) {
low = mid + 1;
} else if (mid > 0 && fraction < normalisedCurve.get(mid - 1).getPoint()) {
high = mid - 1;
} else {
break;
}
}
/*
* The answer lies between the "mid" item and its predecessor.
*/
final PointUnit prevItem = normalisedCurve.get(mid - 1);
final double prevFraction = prevItem.getPoint();
final double prevT = prevItem.getDistance();
final PointUnit item = normalisedCurve.get(mid);
final double proportion = (fraction - prevFraction) / (item.getPoint() - prevFraction);
final double interpolatedT = prevT + (proportion * (item.getDistance() - prevT));
return getY(interpolatedT);
}
protected Point2D getXY(double t) {
final double invT = 1 - t;
final double b1 = 3 * t * invT * invT;
final double b2 = 3 * t * t * invT;
final double b3 = t * t * t;
final Point2D xy = new Point2D.Double((b1 * points[0]) + (b2 * points[2]) + b3, (b1 * points[1]) + (b2 * points[3]) + b3);
return xy;
}
protected double getY(double t) {
final double invT = 1 - t;
final double b1 = 3 * t * invT * invT;
final double b2 = 3 * t * t * invT;
final double b3 = t * t * t;
return (b1 * points[2]) + (b2 * points[3]) + b3;
}
public class PointUnit {
private final double distance;
private final double point;
public PointUnit(double distance, double point) {
this.distance = distance;
this.point = point;
}
public double getDistance() {
return distance;
}
public double getPoint() {
return point;
}
}
}
}
因此,除了SplineInterpolator之外,魔术发生在ActionListener内部的javax.swing.Timer(以及mouseClicked事件处理程序中的一些)
基本上,这会计算动画播放的时间(持续时间),这将成为我们在时间线上的标准化时间t或分数值(0-1),然后我们使用它来计算我们在时间轴上的“进展” SplineInterpolator并根据它的开始和结束位置乘以当前“进展”之间的差异来更新对象的位置
if (startTime < 0) {
startTime = System.currentTimeMillis();
}
long now = System.currentTimeMillis();
long duration = now - startTime;
double t = (double) duration / (double) playTime;
if (duration >= playTime) {
t = 1;
}
double progress = splineInterpolator.interpolate(t);
x = startAt + ((int) Math.round((endAt - startAt) * progress));
repaint();
瞧,我们有一个轻松和轻松的动画!
现在,去使用动画框架!这只是SOOOO简单得多:P
>对于“快进/慢出”,您可以使用0,0,1,1
>对于“慢进/快出”,您可以使用0,1,0,0
>对于“慢速”,您可以使用1,0,1,1
>对于“慢速”,您可以使用0,0,0,1
(或者至少那些是我使用的值)
试验,看看你得到了什么
内容总结
以上是互联网集市为您收集整理的java – 如何用线程实现缓动函数全部内容,希望文章能够帮你解决java – 如何用线程实现缓动函数所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。