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 启动参数含-Djavafx.animation.fullspeed=true时,预设 FPS
* 才可以突破屏幕刷新率。
*
*
* 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);
* });
*
*
* @author 夜雨
* @since 2023-03-08 09:48
*/
public class AnimationRenderer {
/** 渲染回调 */
protected final LinkedList> renderCallbacks;
/** 动画渲染队列 */
protected final LinkedList 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 renderCallback : renderCallbacks) {
renderCallback.handler(deltaSecond);
}
}
synchronized (animations) {
// 动画
Iterator 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 callback) {
synchronized (renderCallbacks) {
renderCallbacks.add(callback);
}
}
/**
* 移除渲染回调
*
* @param callback 回调
*/
public void removeRenderCallback(CallbackArg 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);
}
}