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); } }