Initial project
This commit is contained in:
356
src/main/java/com/imyeyu/fx/utils/AnimationRenderer.java
Normal file
356
src/main/java/com/imyeyu/fx/utils/AnimationRenderer.java
Normal file
@ -0,0 +1,356 @@
|
||||
package com.imyeyu.fx.utils;
|
||||
|
||||
import javafx.animation.AnimationTimer;
|
||||
import javafx.beans.property.DoubleProperty;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.ReadOnlyDoubleProperty;
|
||||
import javafx.beans.property.ReadOnlyIntegerProperty;
|
||||
import javafx.beans.property.SimpleDoubleProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.util.Duration;
|
||||
import com.imyeyu.fx.task.RunAsyncScheduled;
|
||||
import com.imyeyu.java.bean.Callback;
|
||||
import com.imyeyu.java.bean.CallbackArg;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
|
||||
/**
|
||||
* 动画帧渲染器,可控帧率,在 JVM 启动参数含<b>-Djavafx.animation.fullspeed=true</b>时,预设 FPS
|
||||
* 才可以突破屏幕刷新率。
|
||||
*
|
||||
* <pre>
|
||||
* Box box = new Box(128, 128, 128);
|
||||
* box.setDrawMode(DrawMode.LINE);
|
||||
* box.setCullFace(CullFace.BACK);
|
||||
* box.setMaterial(new PhongMaterial(RED));
|
||||
* box.setRotationAxis(new Point3D(0, 64, 0));
|
||||
*
|
||||
* // 以 240 FPS 每秒 90 度旋转一个 3D 立方体
|
||||
* AnimationRenderer renderer = new AnimationRenderer(240);
|
||||
* renderer.addRenderCallback(deltaSecond -> {
|
||||
* box.setRotate(box.getRotate() + 90 * deltaSecond);
|
||||
* });
|
||||
* </pre>
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2023-03-08 09:48
|
||||
*/
|
||||
public class AnimationRenderer {
|
||||
|
||||
/** 渲染回调 */
|
||||
protected final LinkedList<CallbackArg<Double>> renderCallbacks;
|
||||
|
||||
/** 动画渲染队列 */
|
||||
protected final LinkedList<Animation> animations;
|
||||
|
||||
/** 渲染器 */
|
||||
protected final AnimationTimer timer;
|
||||
|
||||
/** 平均帧生成时间 */
|
||||
protected final DoubleProperty mpf;
|
||||
|
||||
/** 平均帧率 */
|
||||
protected final IntegerProperty fps;
|
||||
|
||||
/** 状态计时器 */
|
||||
protected final RunAsyncScheduled<?> statusTimer;
|
||||
|
||||
/** 当前帧 */
|
||||
private double nowNanos;
|
||||
|
||||
/** 上一帧 */
|
||||
private double lastNanos;
|
||||
|
||||
/** 累计帧差(纳秒) */
|
||||
private double deltaNanos;
|
||||
|
||||
/** 累计帧差(秒) */
|
||||
private double deltaSecond;
|
||||
|
||||
/** 当前帧差 */
|
||||
private double betweenNanos;
|
||||
|
||||
/** 标准帧生成时间(纳秒) */
|
||||
private double NPF;
|
||||
|
||||
/** 预设渲染帧率 */
|
||||
private int prefFPS;
|
||||
|
||||
/** 默认构造,60 FPS */
|
||||
public AnimationRenderer() {
|
||||
this(60);
|
||||
}
|
||||
|
||||
/**
|
||||
* 标准构造
|
||||
*
|
||||
* @param prefFPS 预设帧率
|
||||
*/
|
||||
public AnimationRenderer(int prefFPS) {
|
||||
setPrefFPS(prefFPS);
|
||||
|
||||
fps = new SimpleIntegerProperty(0);
|
||||
mpf = new SimpleDoubleProperty(0);
|
||||
renderCallbacks = new LinkedList<>();
|
||||
animations = new LinkedList<>();
|
||||
|
||||
timer = new AnimationTimer() {
|
||||
|
||||
@Override
|
||||
public void handle(long now) {
|
||||
nowNanos = now;
|
||||
if (0 < lastNanos) {
|
||||
// 计算帧差
|
||||
deltaNanos += betweenNanos = nowNanos - lastNanos;
|
||||
|
||||
// 累计帧差大于最小帧生成时间(足够渲染下一帧)
|
||||
if (NPF <= deltaNanos) {
|
||||
deltaSecond = deltaNanos * 1E-9;
|
||||
|
||||
synchronized (renderCallbacks) {
|
||||
for (CallbackArg<Double> renderCallback : renderCallbacks) {
|
||||
renderCallback.handler(deltaSecond);
|
||||
}
|
||||
}
|
||||
synchronized (animations) {
|
||||
// 动画
|
||||
Iterator<Animation> iterator = animations.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Animation next = iterator.next();
|
||||
if (next.diedAt < millis()) {
|
||||
next.callback.handler(deltaSecond, 1);
|
||||
if (next.onFinishedEvent != null) {
|
||||
next.onFinishedEvent.handler();
|
||||
}
|
||||
iterator.remove();
|
||||
} else {
|
||||
next.callback.handler(deltaSecond, (millis() - next.startAt) / next.ttl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 消耗剩余帧差
|
||||
deltaNanos = deltaNanos % NPF;
|
||||
}
|
||||
}
|
||||
lastNanos = nowNanos;
|
||||
}
|
||||
};
|
||||
|
||||
// 状态计算
|
||||
statusTimer = RunAsyncScheduled.finish(Duration.seconds(1), new Callback() {
|
||||
|
||||
long total = 0, old;
|
||||
|
||||
{
|
||||
renderCallbacks.add(nowNanos -> total++);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handler() {
|
||||
// 帧生成时间
|
||||
mpf.set(betweenNanos * 1E-6);
|
||||
|
||||
// 帧率
|
||||
fps.set((int) (total - old));
|
||||
old = total;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** 启动 */
|
||||
public void start() {
|
||||
nowNanos = lastNanos = 0;
|
||||
deltaNanos = NPF;
|
||||
timer.start();
|
||||
}
|
||||
|
||||
/** 停止 */
|
||||
public void stop() {
|
||||
timer.stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加渲染动画
|
||||
*
|
||||
* @param duration 持续时间
|
||||
* @param callback 动画回调
|
||||
*/
|
||||
public void render(Duration duration, AnimationCallback callback) {
|
||||
render(duration, callback, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加渲染动画
|
||||
*
|
||||
* @param duration 持续时间
|
||||
* @param callback 动画回调
|
||||
* @param onFinishedEvent 动画完成回调
|
||||
*/
|
||||
public void render(Duration duration, AnimationCallback callback, Callback onFinishedEvent) {
|
||||
synchronized (animations) {
|
||||
Animation animation = new Animation();
|
||||
animation.startAt = nowNanos * 1E-6;
|
||||
animation.diedAt = animation.startAt + duration.toMillis();
|
||||
animation.ttl = duration.toMillis();
|
||||
animation.callback = callback;
|
||||
animation.onFinishedEvent = onFinishedEvent;
|
||||
animations.add(animation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加渲染回调
|
||||
*
|
||||
* @param callback 回调
|
||||
*/
|
||||
public void addRenderCallback(CallbackArg<Double> callback) {
|
||||
synchronized (renderCallbacks) {
|
||||
renderCallbacks.add(callback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除渲染回调
|
||||
*
|
||||
* @param callback 回调
|
||||
*/
|
||||
public void removeRenderCallback(CallbackArg<Double> callback) {
|
||||
synchronized (renderCallbacks) {
|
||||
renderCallbacks.remove(callback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取预设 FPS
|
||||
*
|
||||
* @return 预设 FPS
|
||||
*/
|
||||
public int getPrefFPS() {
|
||||
return prefFPS;
|
||||
}
|
||||
|
||||
/**
|
||||
* 预设 FPS,渲染器会尽量匹配此帧率渲染,可能会突破少许,系统资源紧张时实际渲染帧率会低于预设
|
||||
*
|
||||
* @param prefFPS FPS 取值范围 [1, 1000]
|
||||
*/
|
||||
public void setPrefFPS(int prefFPS) {
|
||||
if (prefFPS < 1) {
|
||||
throw new IllegalArgumentException("pref fps can not less then 1");
|
||||
}
|
||||
this.prefFPS = prefFPS;
|
||||
this.NPF = 1E9 / prefFPS;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取帧生成时间(毫秒)
|
||||
*
|
||||
* @return 帧生成时间
|
||||
*/
|
||||
public double getMPF() {
|
||||
return mpf.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取帧生成时间属性(毫秒)
|
||||
*
|
||||
* @return 帧生成时间属性(毫秒)
|
||||
*/
|
||||
public ReadOnlyDoubleProperty mpfProperty() {
|
||||
return mpf;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前渲染 FPS
|
||||
*
|
||||
* @return FPS
|
||||
*/
|
||||
public int getFPS() {
|
||||
return fps.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前渲染 FPS 属性
|
||||
*
|
||||
* @return FPS 属性
|
||||
*/
|
||||
public ReadOnlyIntegerProperty fpsProperty() {
|
||||
return fps;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取累计帧差(纳秒)
|
||||
*
|
||||
* @return 累计帧差(纳秒)
|
||||
*/
|
||||
public double deltaNanos() {
|
||||
return deltaNanos;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取累计帧差(毫秒)
|
||||
*
|
||||
* @return 累计帧差(毫秒)
|
||||
*/
|
||||
public double deltaMillis() {
|
||||
return deltaNanos * 1E-6;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取累计帧差(秒)
|
||||
*
|
||||
* @return 累计帧差(秒)
|
||||
*/
|
||||
public double deltaSecond() {
|
||||
return deltaNanos * 1E-9;
|
||||
}
|
||||
|
||||
/** @return 当前帧(纳秒) */
|
||||
public double nanos() {
|
||||
return nowNanos;
|
||||
}
|
||||
|
||||
/** @return 当前帧(毫秒) */
|
||||
public double millis() {
|
||||
return nowNanos * 1E-6;
|
||||
}
|
||||
|
||||
/** @return 当前帧(秒) */
|
||||
public double second() {
|
||||
return nowNanos * 1E-9;
|
||||
}
|
||||
|
||||
/**
|
||||
* 一次性动画
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2023-05-16 16:02
|
||||
*/
|
||||
public static class Animation {
|
||||
|
||||
double diedAt;
|
||||
double startAt;
|
||||
double ttl;
|
||||
AnimationCallback callback;
|
||||
Callback onFinishedEvent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 一次性动画回调
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2023-05-14 10:10
|
||||
*/
|
||||
public interface AnimationCallback {
|
||||
|
||||
/**
|
||||
* 处理器
|
||||
*
|
||||
* @param deltaSecond 帧差
|
||||
* @param percent 动画进度百分比,取值范围 [0, 1]
|
||||
*/
|
||||
void handler(double deltaSecond, double percent);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user