我在 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();
}
}
非常感谢您的帮助。
总结有用的评论并提供一些额外的建议,这项工作中常见的几项活动:
隔离:俗话说,良好的开始是成功的一半,而您的完整的示例允许问题被单独重现和研究。
目标:花一些精力确定您打算支持的最低平台功能。然后验证优化是否按预期扩大。
Profile:通用profiling工具可以帮助发现问题。在 JavaFX 的特定情况下,正如@jewelsea 建议的here 和 here,您可以启用 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();
}
}