在 JavaFX 画布上绘制大量动画形状时如何优化渲染性能

问题描述 投票:0回答:1

我在 HTML5 演示中使用 Javafx 实现了相同的效果,但是使用 Javafx 的帧速率非常低

我该如何优化它,是我写错了还是有其他更好的方法来实现它。

这是原始的html5演示项目:

<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <title>snows</title>
    <script type="text/javascript" src="js/jquery.min.js"></script>
    <style>
        html,
        body {
            width: 100%;
            height: 100%;
            margin: 0;
            padding: 0;
            overflow: hidden;
        }

        .container {
            width: 100%;
            height: 100%;
            margin: 0;
            padding: 0;
        }
    </style>
</head>

<body>
    <div id="jsi-snow-container" class="container"></div>

    <script>
        var RENDERER = {
            SNOW_COUNT: { INIT: 100, DELTA: 1 },
            BACKGROUND_COLOR: 'hsl(%h, 50%, %l%)',
            INIT_HUE: 180,
            DELTA_HUE: 0.1,
            init: function () {
                this.setParameters();
                this.reconstructMethod();
                this.createSnow(this.SNOW_COUNT.INIT * this.countRate, true);
                this.render();
            },
            setParameters: function () {
                this.$window = $(window);
                this.$container = $('#jsi-snow-container');
                this.width = this.$container.width();
                this.height = this.$container.height();
                this.center = { x: this.width / 2, y: this.height / 2 };
                this.countRate = this.width * this.height / 500 / 500;
                this.canvas = $('<canvas />').attr({ width: this.width, height: this.height }).appendTo(this.$container).get(0);
                this.context = this.canvas.getContext('2d');
                this.radius = Math.sqrt(this.center.x * this.center.x + this.center.y * this.center.y);
                this.hue = this.INIT_HUE;
                this.snows = [];
            },
            reconstructMethod: function () {
                this.render = this.render.bind(this);
            },
            createSnow: function (count, toRandomize) {
                for (var i = 0; i < count; i++) {
                    this.snows.push(new SNOW(this.width, this.height, this.center, toRandomize));
                }
            },
            render: function () {
                requestAnimationFrame(this.render);
                var gradient = this.context.createRadialGradient(this.center.x, this.center.y, 0, this.center.x, this.center.y, this.radius),
                    backgroundColor = this.BACKGROUND_COLOR.replace('%h', this.hue);
                gradient.addColorStop(0, backgroundColor.replace('%l', 30));
                gradient.addColorStop(0.2, backgroundColor.replace('%l', 20));
                gradient.addColorStop(1, backgroundColor.replace('%l', 5));
                this.context.fillStyle = gradient;
                this.context.fillRect(0, 0, this.width, this.height);
                for (var i = this.snows.length - 1; i >= 0; i--) {
                    if (!this.snows[i].render(this.context)) {
                        this.snows.splice(i, 1);
                    }
                }

                this.hue += this.DELTA_HUE;
                this.hue %= 360;
                this.createSnow(this.SNOW_COUNT.DELTA, false);
            }
        };
        var SNOW = function (width, height, center, toRandomize) {
            this.width = width;
            this.height = height;
            this.center = center;
            this.init(toRandomize);
        };
        SNOW.prototype = {
            RADIUS: 20,
            OFFSET: 4,
            INIT_POSITION_MARGIN: 20,
            COLOR: 'rgba(255, 255, 255, 0.8)',
            TOP_RADIUS: { MIN: 1, MAX: 3 },
            SCALE: { INIT: 0.04, DELTA: 0.01 },
            DELTA_ROTATE: { MIN: -Math.PI / 180 / 2, MAX: Math.PI / 180 / 2 },
            THRESHOLD_TRANSPARENCY: 0.7,
            VELOCITY: { MIN: -1, MAX: 1 },
            LINE_WIDTH: 2,
            BLUR: 10,
            init: function (toRandomize) {
                this.setParameters(toRandomize);
                this.createSnow();
            },
            setParameters: function (toRandomize) {
                if (!this.canvas) {
                    this.radius = this.RADIUS + this.TOP_RADIUS.MAX * 2 + this.LINE_WIDTH;
                    this.length = this.radius * 2;
                    this.canvas = $('<canvas />').attr({
                        width: this.length, height: this.length
                    }).get(0);
                    this.context = this.canvas.getContext('2d');
                }
                this.topRadius = this.getRandomValue(this.TOP_RADIUS);
                var theta = Math.PI * 2 * Math.random();
                this.x = this.center.x + this.INIT_POSITION_MARGIN * Math.cos(theta);
                this.y = this.center.y + this.INIT_POSITION_MARGIN * Math.sin(theta);
                this.vx = this.getRandomValue(this.VELOCITY);
                this.vy = this.getRandomValue(this.VELOCITY);
                this.deltaRotate = this.getRandomValue(this.DELTA_ROTATE);
                this.scale = this.SCALE.INIT;
                this.deltaScale = 1 + this.SCALE.DELTA * 500 / Math.max(this.width, this.height);
                this.rotate = 0;
                if (toRandomize) {
                    for (var i = 0, count = Math.random() * 1000; i < count; i++) {
                        this.x += this.vx;
                        this.y += this.vy;
                        this.scale *= this.deltaScale;
                        this.rotate += this.deltaRotate;
                    }
                }
            },
            getRandomValue: function (range) {
                return range.MIN + (range.MAX - range.MIN) * Math.random();
            },
            createSnow: function () {
                this.context.clearRect(0, 0, this.length, this.length);
                this.context.save();
                this.context.beginPath();
                this.context.translate(this.radius, this.radius);
                this.context.strokeStyle = this.COLOR;
                this.context.lineWidth = this.LINE_WIDTH;
                this.context.shadowColor = this.COLOR;
                this.context.shadowBlur = this.BLUR;
                var angle60 = Math.PI / 180 * 60,
                    sin60 = Math.sin(angle60),
                    cos60 = Math.cos(angle60),
                    threshold = Math.random() * this.RADIUS / this.OFFSET | 0,
                    rate = 0.5 + Math.random() * 0.5,
                    offsetY = this.OFFSET * Math.random() * 2,
                    offsetCount = this.RADIUS / this.OFFSET;
                for (var i = 0; i < 6; i++) {
                    this.context.save();
                    this.context.rotate(angle60 * i);
                    for (var j = 0; j <= threshold; j++) {
                        var y = -this.OFFSET * j;
                        this.context.moveTo(0, y);
                        this.context.lineTo(y * sin60, y * cos60);
                    }
                    for (var j = threshold; j < offsetCount; j++) {
                        var y = -this.OFFSET * j,
                            x = j * (offsetCount - j + 1) * rate;
                        this.context.moveTo(x, y - offsetY);
                        this.context.lineTo(0, y);
                        this.context.lineTo(-x, y - offsetY);
                    }
                    this.context.moveTo(0, 0);
                    this.context.lineTo(0, -this.RADIUS);
                    this.context.arc(0, -this.RADIUS - this.topRadius, this.topRadius, Math.PI /
                        2, Math.PI * 2.5, false);
                    this.context.restore();
                }
                this.context.stroke();
                this.context.restore();
            },
            render: function (context) {
                context.save();
                if (this.scale > this.THRESHOLD_TRANSPARENCY) {
                    context.globalAlpha = Math.max(0, (1 - this.scale) / (1 - this.THRESHOLD_TRANSPARENCY));
                    if (this.scale > 1 || this.x < -this.radius || this.x > this.width + this.radius || this.y < -this.radius || this.y > this.height + this.radius) {
                        context.restore();
                        return false;
                    }
                }
                context.translate(this.x, this.y);
                context.rotate(this.rotate);
                context.scale(this.scale, this.scale);
                context.drawImage(this.canvas, -this.radius, -this.radius);
                context.restore();
                this.x += this.vx;
                this.y += this.vy;
                this.scale *= this.deltaScale;
                this.rotate += this.deltaRotate;
                return true;
            }
        };
        $(function () {
            RENDERER.init();
        });
    </script>
</body>

</html>

然后这就是我用 javafx 实现的目标:

java主类:

package fx.demo;

import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.CacheHint;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
import javafx.scene.paint.CycleMethod;
import javafx.scene.paint.RadialGradient;
import javafx.scene.paint.Stop;
import javafx.stage.Stage;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class CanvasTest4 extends Application {
    private static final int SNOW_COUNT_INT = 15;
    private static final int SNOW_COUNT_DELTA = 1;
    private static final String BACKGROUND_COLOR = "hsl(%h, 50%, %l%)";
    private static final double INIT_HUE = 180;
    private static final double DELTA_HUE = 0.1;

    private final double width = 1920;
    private final double height = 911;
    private final double centerX = width / 2.0;
    private final double centerY = height / 2.0;
    private final int countRate = (int) (width * height / 500 / 500);
    private final double radius = Math.sqrt(centerX * centerX + centerY * centerX);
    private double hue = INIT_HUE;
    private final List<Snow> snows = new ArrayList<>();

    private long lastUpdate;

    @Override
    public void start(Stage primaryStage) {
        Group root = new Group();
        Canvas canvas = new Canvas(width, height);
        // try to use cache
        canvas.setCache(true);
        canvas.setCacheHint(CacheHint.SPEED);
        GraphicsContext gc = canvas.getGraphicsContext2D();

        long begin = System.currentTimeMillis();
        createSnow(SNOW_COUNT_INT * countRate, width, height, centerX, centerY, true);
        System.out.printf("initial: %sms%n", (System.currentTimeMillis() - begin));
        AnimationTimer animationTimer = new AnimationTimer() {
            @Override
            public void handle(long now) {
                System.out.printf("frame duration: %sms%n", (now - lastUpdate) / 1_000_000.0);
                lastUpdate = now;

                long start = System.currentTimeMillis();
                // draw background
                drawBackground(gc);
                System.out.printf("draw bg: %sms%n", (System.currentTimeMillis() - start));

                long l = System.currentTimeMillis();
                // draw snows
                snows.removeIf(snow -> !snow.render(gc));
                System.out.printf("draw snows: %sms%n", (System.currentTimeMillis() - l));

                // limit the number
                if (snows.size() < SNOW_COUNT_INT * countRate) {
                    createSnow(SNOW_COUNT_DELTA, width, height, centerX, centerY, false);
                }

                System.out.printf("total time: %sms%n", (System.currentTimeMillis() - start));
                System.out.println("snows: " + snows.size());
                System.out.println("-------------------------");
            }
        };
        animationTimer.start();

        root.getChildren().addAll(canvas);
        primaryStage.setScene(new Scene(root));
        primaryStage.setWidth(width);
        primaryStage.setHeight(height);
        primaryStage.show();
    }

    private void drawBackground(GraphicsContext gc) {
        String background = BACKGROUND_COLOR.replace("%h", String.valueOf(hue));
        List<Stop> stops = Arrays.asList(
                new Stop(0, Color.web(background.replace("%l", "30"))),
                new Stop(0.2, Color.web(background.replace("%l", "20"))),
                new Stop(1, Color.web(background.replace("%l", "5")))
        );
        RadialGradient radialGradient = new RadialGradient(0, 0, centerX, centerY, radius, false, CycleMethod.NO_CYCLE, stops);
        gc.setFill(radialGradient);
        gc.fillRect(0, 0, width, height);

        hue += DELTA_HUE;
        hue %= 360;
    }

    private void createSnow(int count, double width, double height, double centerX, double centerY, boolean toRandomize) {
        for (int i = 0; i < count; i++) {
            Snow snow = new Snow(width, height, centerX, centerY, toRandomize);
            snows.add(snow);
        }
    }
}

雪类(将在画布上渲染的形状):

package fx.demo;

import javafx.scene.canvas.GraphicsContext;
import javafx.scene.effect.DropShadow;
import javafx.scene.paint.Color;

public class Snow {
    private static final double RADIUS = 20;
    private static final double OFFSET = 4;
    private static final double INIT_POSITION_MARGIN = 20;
    private static final Color COLOR = Color.web("rgba(255, 255, 255, 0.8)");
    private static final double TOP_RADIUS_MIN = 1;
    private static final double TOP_RADIUS_MAX = 3;
    private static final double SCALE_INIT = 0.04;
    private static final double SCALE_DELTA = 0.01;
    private static final double DELTA_ROTATE_MIN = -Math.PI / 180 / 2;
    private static final double DELTA_ROTATE_MAX = Math.PI / 180 / 2;
    private static final double THRESHOLD_TRANSPARENCY = 0.7;
    private static final double VELOCITY_MIN = -1;
    private static final double VELOCITY_MAX = 1;
    private static final double LINE_WIDTH = 2;
    private static final double BLUR = 10;

    private double length;
    private final double width;
    private final double height;
    private final double centerX;
    private final double centerY;
    private final boolean toRandomize;
    private double radius;
    private double topRadius;
    private double x;
    private double y;
    private double vx;
    private double vy;
    private double deltaRotate;
    private double scale;
    private double deltaScale;
    private double rotate;

    private double sin60;
    private double cos60;
    private double rate;
    private double offsetY;
    private double offsetCount;
    private int threshold;

    public Snow(double width, double height, double centerX, double centerY, boolean toRandomize) {
        this.width = width;
        this.height = height;
        this.centerX = centerX;
        this.centerY = centerY;
        this.toRandomize = toRandomize;

        init();
    }

    private void init() {
        this.radius = RADIUS + TOP_RADIUS_MAX * 2 + LINE_WIDTH;
        this.length = this.radius * 2;

        this.topRadius = getRandomValue(TOP_RADIUS_MIN, TOP_RADIUS_MAX);
        double theta = Math.PI * 2 * Math.random();
        this.x = centerX + INIT_POSITION_MARGIN * Math.cos(theta);
        this.y = centerY + INIT_POSITION_MARGIN * Math.sin(theta);
        this.vx = getRandomValue(VELOCITY_MIN, VELOCITY_MAX);
        this.vy = getRandomValue(VELOCITY_MIN, VELOCITY_MAX);
        this.deltaRotate = getRandomValue(DELTA_ROTATE_MIN, DELTA_ROTATE_MAX);
        this.scale = SCALE_INIT;
        this.deltaScale = 1 + SCALE_DELTA * 500 / Math.max(this.width, this.height);
        this.rotate = 0;
        double angle60 = Math.PI / 180 * 60;
        this.sin60 = Math.sin(angle60);
        this.cos60 = Math.cos(angle60);
        this.threshold = (int) (Math.random() * RADIUS / OFFSET);
        this.rate = 0.5 + Math.random() * 0.5;
        this.offsetY = OFFSET * Math.random() * 2;
        this.offsetCount = RADIUS / OFFSET;

        if (toRandomize) {
            for (int i = 0, count = (int) (Math.random() * 1000); i < count; i++) {
                this.x += this.vx;
                this.y += this.vy;
                this.scale *= this.deltaScale;
                this.rotate += this.deltaRotate;
            }
        }
    }

    public boolean render(GraphicsContext gc) {
        gc.save();
        if (this.scale > THRESHOLD_TRANSPARENCY) {
            gc.setGlobalAlpha(Math.max(0, (1 - this.scale) / (1 - THRESHOLD_TRANSPARENCY)));
            if (this.scale > 1 || this.x < -this.radius || this.x > this.width + this.radius ||
                    this.y < -this.radius || this.y > this.height + this.radius) {
                gc.restore();
                // invisible
                return false;
            }
        }

        gc.beginPath();
        gc.translate(x, y);
        gc.rotate(rotate);
        gc.scale(scale, scale);

        gc.setStroke(COLOR);
        gc.setLineWidth(LINE_WIDTH);

        DropShadow dropShadow = new DropShadow();
        dropShadow.setColor(COLOR);
        dropShadow.setRadius(BLUR);
        gc.setEffect(dropShadow);

        for (int i = 0; i < 6; i++) {
            gc.save();
            gc.rotate(60 * i);

            for (int j = 0; j <= threshold; j++) {
                double y = -4 * j;
                gc.moveTo(0, y);
                gc.lineTo(y * sin60, y * cos60);
            }
            for (int j = threshold; j < offsetCount; j++) {
                double y = -4 * j,
                        x = j * (offsetCount - j + 1) * rate;
                gc.moveTo(x, y - offsetY);
                gc.lineTo(0, y);
                gc.lineTo(-x, y - offsetY);
            }

            gc.moveTo(0, 0);
            gc.lineTo(0, -RADIUS);
            gc.arc(0, -RADIUS - this.topRadius, this.topRadius, this.topRadius, 0, 360);

            gc.restore();
        }
        gc.stroke();
        gc.restore();

        this.x += this.vx;
        this.y += this.vy;
        this.scale *= this.deltaScale;
        // origin
        this.rotate += this.deltaRotate;
        // too slowly,let it speed
        this.rotate += this.deltaRotate + this.deltaRotate > 0 ? 0.2 : -0.2;

        return true;
    }

    private double getRandomValue(double rangeMin, double rangeMax) {
        return rangeMin + (rangeMax - rangeMin) * Math.random();
    }
}

非常感谢您的帮助。

performance javafx canvas graphics draw
1个回答
0
投票

总结有用的评论并提供一些额外的建议,这项工作中常见的几项活动:

隔离:俗话说,良好的开始是成功的一半,而您的完整的示例允许问题被单独重现和研究。

目标:花一些精力确定您打算支持的最低平台功能。然后验证优化是否按预期扩大。

Profile:通用profiling工具可以帮助发现问题。在 JavaFX 的特定情况下,正如@jewelsea 建议的herehere,您可以启用 JavaFX Pulse Logger。它存在于 Java 8 中,在最近的版本中被“恢复”。在运行时将其启用为 java 系统属性:

-Djavafx.pulseLogger=true

您应该看到连续编号的记录,其中包含任何花费时间超过单个脉冲的绘画的详细信息。

… PULSE: 33 [16ms:20ms] … T16 (1 +2ms): Waiting for previous rendering …

随着运行时优化的发展,您应该会看到过渡到显示及时绘制的记录。

[34 17ms:17ms] [35 16ms:15ms] [36 16ms:13ms] [37 16ms:13ms] [38 16ms:12ms] [39 16ms:12ms] [40 16ms:12ms] [41 16ms:14ms] [42 16ms:13ms]

时间

:正如 @James_D 观察到的这里DropShadow“正在导致帧速率大幅下降。”随着屏幕尺寸的变化,一种策略是让画布增大以填充封闭的父级,如

此处
所述。通过这种方式,渲染负担可以适应环境或由用户调整。下面的变体使用此处所示的基本方法,而此方法说明了自定义布局。此外,该示例省略了任意 DropShadow 下面的
scale
渲染:
if (scale > 0.20) gc.setEffect(dropShadow);

Memory

:由于DropShadow实例保持不变,因此可以仅实例化一次并在

render()
中重复使用。该示例还使用
RADIUS
作为实例。

代码

import javafx.animation.AnimationTimer; import javafx.application.Application; import javafx.scene.CacheHint; import javafx.scene.Scene; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.paint.Color; import javafx.scene.paint.CycleMethod; import javafx.scene.paint.RadialGradient; import javafx.scene.paint.Stop; import javafx.stage.Stage; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import javafx.scene.control.Label; import javafx.scene.effect.DropShadow; import javafx.scene.layout.BorderPane; import javafx.scene.layout.Pane; /** * @see https://stackoverflow.com/q/77890758/230513 */ public class CanvasSnow extends Application { private static final double PREF_WIDTH = 1200; private static final double PREF_HEIGHT = PREF_WIDTH * 0.618; private static final int SNOW_COUNT_INT = 15; private static final int SNOW_COUNT_DELTA = 1; private static final String BACKGROUND_COLOR = "hsl(%h, 50%, %l%)"; private static final double INIT_HUE = 180; private static final double DELTA_HUE = 0.1; private final List<Snow> snows = new ArrayList<>(); private double hue = INIT_HUE; private long lastUpdate; @Override public void start(Stage primaryStage) { var root = new BorderPane(); var status = new Label("Rate Hz."); var canvas = new Canvas(PREF_WIDTH, PREF_HEIGHT); canvas.setCache(true); canvas.setCacheHint(CacheHint.SPEED); var view = new Pane(canvas); view.setPrefSize(PREF_WIDTH, PREF_HEIGHT); var gc = canvas.getGraphicsContext2D(); var countRate = (int) (canvas.getWidth() * canvas.getHeight() / 500 / 500); createSnow(canvas, SNOW_COUNT_INT * countRate, true); AnimationTimer animationTimer = new AnimationTimer() { @Override public void handle(long now) { status.setText(String.format("Snows: %d; Rate: %7.4f Hz.", snows.size(), 1.0e9 / (now - lastUpdate))); lastUpdate = now; drawBackground(canvas); snows.removeIf(snow -> !snow.render(gc)); if (snows.size() < SNOW_COUNT_INT * countRate) { createSnow(canvas, SNOW_COUNT_DELTA, false); } } }; canvas.widthProperty().bind(view.widthProperty()); canvas.heightProperty().bind(view.heightProperty()); root.setCenter(view); root.setBottom(status); primaryStage.setScene(new Scene(root)); primaryStage.show(); animationTimer.start(); } private void drawBackground(Canvas canvas) { String background = BACKGROUND_COLOR.replace("%h", String.valueOf(hue)); List<Stop> stops = Arrays.asList( new Stop(0, Color.web(background.replace("%l", "30"))), new Stop(0.2, Color.web(background.replace("%l", "20"))), new Stop(1, Color.web(background.replace("%l", "5"))) ); var gc = canvas.getGraphicsContext2D(); var centerX = canvas.getWidth() / 2; var centerY = canvas.getHeight() / 2; var radius = Math.sqrt(centerX * centerX + centerY * centerX); RadialGradient radialGradient = new RadialGradient(0, 0, centerX, centerY, radius, false, CycleMethod.NO_CYCLE, stops); gc.setFill(radialGradient); gc.fillRect(0, 0, canvas.getWidth(), canvas.getHeight()); hue += DELTA_HUE; hue %= 360; } private void createSnow(Canvas canvas, int count, boolean toRandomize) { var gc = canvas.getGraphicsContext2D(); var width = canvas.getWidth(); var height = canvas.getHeight(); var centerX = canvas.getWidth() / 2; var centerY = canvas.getHeight() / 2; for (int i = 0; i < count; i++) { Snow snow = new Snow(width, height, centerX, centerY, toRandomize); snows.add(snow); } } private static class Snow { private static final double RADIUS = 20; private static final double OFFSET = 4; private static final double INIT_POSITION_MARGIN = 20; private static final Color COLOR = Color.web("rgba(255, 255, 255, 0.8)"); private static final double TOP_RADIUS_MIN = 1; private static final double TOP_RADIUS_MAX = 3; private static final double SCALE_INIT = 0.04; private static final double SCALE_DELTA = 0.01; private static final double DELTA_ROTATE_MIN = -Math.PI / 180 / 2; private static final double DELTA_ROTATE_MAX = Math.PI / 180 / 2; private static final double THRESHOLD_TRANSPARENCY = 0.7; private static final double VELOCITY_MIN = -1; private static final double VELOCITY_MAX = 1; private static final double LINE_WIDTH = 2; private final DropShadow dropShadow = new DropShadow(RADIUS, COLOR); private final double width; private final double height; private final double centerX; private final double centerY; private final boolean toRandomize; private double radius; private double topRadius; private double x; private double y; private double vx; private double vy; private double deltaRotate; private double scale; private double deltaScale; private double rotate; private double sin60; private double cos60; private double rate; private double offsetY; private double offsetCount; private int threshold; public Snow(double width, double height, double centerX, double centerY, boolean toRandomize) { this.width = width; this.height = height; this.centerX = centerX; this.centerY = centerY; this.toRandomize = toRandomize; init(); } private void init() { this.radius = RADIUS + TOP_RADIUS_MAX * 2 + LINE_WIDTH; this.topRadius = getRandomValue(TOP_RADIUS_MIN, TOP_RADIUS_MAX); double theta = Math.PI * 2 * Math.random(); this.x = centerX + INIT_POSITION_MARGIN * Math.cos(theta); this.y = centerY + INIT_POSITION_MARGIN * Math.sin(theta); this.vx = getRandomValue(VELOCITY_MIN, VELOCITY_MAX); this.vy = getRandomValue(VELOCITY_MIN, VELOCITY_MAX); this.deltaRotate = getRandomValue(DELTA_ROTATE_MIN, DELTA_ROTATE_MAX); this.scale = SCALE_INIT; this.deltaScale = 1 + SCALE_DELTA * 500 / Math.max(this.width, this.height); this.rotate = 0; double angle60 = Math.PI / 180 * 60; this.sin60 = Math.sin(angle60); this.cos60 = Math.cos(angle60); this.threshold = (int) (Math.random() * RADIUS / OFFSET); this.rate = 0.5 + Math.random() * 0.5; this.offsetY = OFFSET * Math.random() * 2; this.offsetCount = RADIUS / OFFSET; if (toRandomize) { for (int i = 0, count = (int) (Math.random() * 1000); i < count; i++) { this.x += this.vx; this.y += this.vy; this.scale *= this.deltaScale; this.rotate += this.deltaRotate; } } } public boolean render(GraphicsContext gc) { gc.save(); if (this.scale > THRESHOLD_TRANSPARENCY) { gc.setGlobalAlpha(Math.max(0, (1 - this.scale) / (1 - THRESHOLD_TRANSPARENCY))); if (this.scale > 1 || this.x < -this.radius || this.x > this.width + this.radius || this.y < -this.radius || this.y > this.height + this.radius) { gc.restore(); // invisible return false; } } gc.beginPath(); gc.translate(x, y); gc.rotate(rotate); gc.scale(scale, scale); gc.setStroke(COLOR); gc.setLineWidth(LINE_WIDTH); if (scale > 0.20) gc.setEffect(dropShadow); for (int i = 0; i < 6; i++) { gc.save(); gc.rotate(60 * i); for (int j = 0; j <= threshold; j++) { double y = -4 * j; gc.moveTo(0, y); gc.lineTo(y * sin60, y * cos60); } for (int j = threshold; j < offsetCount; j++) { double y = -4 * j, x = j * (offsetCount - j + 1) * rate; gc.moveTo(x, y - offsetY); gc.lineTo(0, y); gc.lineTo(-x, y - offsetY); } gc.moveTo(0, 0); gc.lineTo(0, -RADIUS); gc.arc(0, -RADIUS - this.topRadius, this.topRadius, this.topRadius, 0, 360); gc.restore(); } gc.stroke(); gc.restore(); this.x += this.vx; this.y += this.vy; this.scale *= this.deltaScale; // origin this.rotate += this.deltaRotate; // too slowly,let it speed this.rotate += this.deltaRotate + this.deltaRotate > 0 ? 0.2 : -0.2; return true; } private double getRandomValue(double rangeMin, double rangeMax) { return rangeMin + (rangeMax - rangeMin) * Math.random(); } } public static void main(String[] args) { Application.launch(); } }

© www.soinside.com 2019 - 2024. All rights reserved.