356 lines
7.5 KiB
Java
356 lines
7.5 KiB
Java
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);
|
||
}
|
||
} |