spread.js revision 877:cf4d2252d444
1/*
2 * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24/**
25 * Testing JavaFX canvas run by Nashorn.
26 *
27 * @test/nocompare
28 * @run
29 * @fork
30 */
31
32TESTNAME = "spread";
33
34var WIDTH = 800;
35var HEIGHT = 600;
36var canvas = new Canvas(WIDTH, HEIGHT);
37var context = canvas.graphicsContext2D;
38
39/* "Spread" tech demo of canvas by Tom Theisen
40 *
41 * This will animate a sequence of branch structures in a canvas element.
42 * Each frame, a new direction is calculated, similar to the last frame.
43 */
44
45var start_width = 20;           // starting width of each branch
46var frame_time = 30;            // milliseconds per frame
47var straighten_factor = 0.95;   // value from 0 to 1, factor applied to direction_offset every frame
48var curviness = 0.2;            // amount of random direction change each frame
49
50var color_speed = 0.03;     // speed at which colors change when cycling is enabled
51var branch_shrink = 0.95;   // factor by which branches shrink every frame
52var min_width = 1;          // minimum WIDTH for branch, after which they are discontinued
53var branch_opacity = 0.4;   // opacity of lines drawn
54var branch_count = 3;       // branch count per tree
55var branch_bud_size = 0.5;  // ratio of original branch size at which branch will split
56var branch_bud_angle = 1;   // angle offset for split branch;
57
58var paper;                  // reference to graphics context
59var branches = Object();    // linked list of active branches
60var color_styles = [];      // pre-computed list of colors as styles. format: (r,g,b,a)
61var direction_offset = 0;   // current direction offset in radians.  this is applied to all branches.
62var frame = 0;              // frame counter
63var timespent = 0;          // total time spent so far, used to calculate average frame render duration
64var frameratespan;          // html span element for updating performance number
65
66// preferences object, contains an attribute for each user setting
67var prefs = {
68    wrap: true,             // causes branches reaching edge of viewable area to appear on opposite side
69    fade: false,             // fade existing graphics on each frame
70    cycle: true,            // gradually change colors each frame
71    new_branch_frames: 20    // number of frames elapsed between each auto-generated tree
72};
73
74// create tree at the specified position with number of branches
75function create_tree(branches, start_width, position, branch_count) {
76    var angle_offset = Math.PI * 2 / branch_count;
77    for (var i = 0; i < branch_count; ++i) {
78        branch_add(branches, new Branch(position, angle_offset * i, start_width));
79    }
80}
81
82// add branch to collection
83function branch_add(branches, branch) {
84    branch.next = branches.next;
85    branches.next = branch;
86}
87
88// get the coordinates for the position of a new tree
89// use the center of the canvas
90function get_new_tree_center(width, height) {
91    return {
92        x: 0.5 * width,
93        y: 0.5 * height
94    };
95}
96
97// Branch constructor
98// position has x and y properties
99// direction is in radians
100function Branch(position, direction, width) {
101    this.x = position.x;
102    this.y = position.y;
103    this.width = width;
104    this.original_width = width;
105    this.direction = direction;
106}
107
108// update position, direction and width of a particular branch
109function branch_update(branches, branch, paper) {
110    paper.beginPath();
111    paper.lineWidth = branch.width;
112    paper.moveTo(branch.x, branch.y);
113
114    branch.width *= branch_shrink;
115    branch.direction += direction_offset;
116    branch.x += Math.cos(branch.direction) * branch.width;
117    branch.y += Math.sin(branch.direction) * branch.width;
118
119    paper.lineTo(branch.x, branch.y);
120    paper.stroke();
121
122    if (prefs.wrap) wrap_branch(branch, WIDTH, HEIGHT);
123
124    if (branch.width < branch.original_width * branch_bud_size) {
125        branch.original_width *= branch_bud_size;
126        branch_add(branches, new Branch(branch, branch.direction + 1, branch.original_width));
127    }
128}
129
130function draw_frame() {
131    if (prefs.fade) {
132        paper.fillRect(0, 0, WIDTH, HEIGHT);
133    }
134
135    if (prefs.cycle) {
136        paper.setStroke(Paint.valueOf(color_styles[frame % color_styles.length]));
137    }
138
139    if (frame++ % prefs.new_branch_frames == 0) {
140        create_tree(branches, start_width, get_new_tree_center(WIDTH, HEIGHT), branch_count);
141    }
142
143    direction_offset += (0.35 + (frame % 200) * 0.0015) * curviness - curviness / 2;
144    direction_offset *= straighten_factor;
145
146    var branch = branches;
147    var prev_branch = branches;
148    while (branch = branch.next) {
149        branch_update(branches, branch, paper);
150
151        if (branch.width < min_width) {
152            // remove branch from list
153            prev_branch.next = branch.next;
154        }
155
156        prev_branch = branch;
157    }
158}
159
160// constrain branch position to visible area by "wrapping" from edge to edge
161function wrap_branch(branch, WIDTH, HEIGHT) {
162    branch.x = positive_mod(branch.x, WIDTH);
163    branch.y = positive_mod(branch.y, HEIGHT);
164}
165
166// for a < 0, b > 0, javascript returns a negative number for a % b
167// this is a variant of the % operator that adds b to the result in this case
168function positive_mod(a, b) {
169    // ECMA 262 11.5.3: Applying the % Operator
170    // remainder operator does not convert operands to integers,
171    // although negative results are possible
172
173    return ((a % b) + b) % b;
174}
175
176// pre-compute color styles that will be used for color cycling
177function populate_colors(color_speed, color_styles, branch_opacity) {
178    // used in calculation of RGB values
179    var two_thirds_pi = Math.PI * 2 / 3;
180    var four_thirds_pi = Math.PI * 4 / 3;
181    var two_pi = Math.PI * 2;
182
183    // hue does represent hue, but not in the conventional HSL scheme
184    for(var hue = 0; hue < two_pi; hue += color_speed) {
185        var r = Math.floor(Math.sin(hue) * 128 + 128);
186        var g = Math.floor(Math.sin(hue + two_thirds_pi) * 128 + 128);
187        var b = Math.floor(Math.sin(hue + four_thirds_pi) * 128 + 128);
188        color = "rgba(" + [r, g, b, branch_opacity].join() + ")";
189
190        color_styles.push(color);
191    }
192}
193
194// apply initial settings to canvas object
195function setup_canvas() {
196    paper = canvas.graphicsContext2D;
197    paper.setFill(Paint.valueOf('rgb(0, 0, 0)'));
198    paper.fillRect(0, 0, WIDTH, HEIGHT);
199    paper.setFill(Paint.valueOf("rgba(0, 0, 0, 0.005)"));
200    paper.setStroke(Paint.valueOf("rgba(128, 128, 64, " + String(branch_opacity) + ")"));
201}
202
203populate_colors(color_speed, color_styles, branch_opacity);
204setup_canvas();
205
206var stack = new StackPane();
207var pane = new BorderPane();
208pane.setCenter(canvas);
209stack.getChildren().add(pane);
210$STAGE.scene = new Scene(stack);
211var timer = new AnimationTimerExtend() {
212    handle: function handle(now) {
213        if (frame < 200) {
214            draw_frame();
215        } else {
216            checkImageAndExit();
217            timer.stop();
218        }
219    }
220};
221timer.start();
222
223