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  
  32 TESTNAME = "spread";
  33 
  34 var WIDTH = 800;
  35 var HEIGHT = 600;
  36 var canvas = new Canvas(WIDTH, HEIGHT);
  37 var 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 
  45 var start_width = 20;           // starting width of each branch
  46 var frame_time = 30;            // milliseconds per frame
  47 var straighten_factor = 0.95;   // value from 0 to 1, factor applied to direction_offset every frame
  48 var curviness = 0.2;            // amount of random direction change each frame
  49 
  50 var color_speed = 0.03;     // speed at which colors change when cycling is enabled
  51 var branch_shrink = 0.95;   // factor by which branches shrink every frame
  52 var min_width = 1;          // minimum WIDTH for branch, after which they are discontinued
  53 var branch_opacity = 0.4;   // opacity of lines drawn
  54 var branch_count = 3;       // branch count per tree
  55 var branch_bud_size = 0.5;  // ratio of original branch size at which branch will split
  56 var branch_bud_angle = 1;   // angle offset for split branch;
  57 
  58 var paper;                  // reference to graphics context
  59 var branches = Object();    // linked list of active branches
  60 var color_styles = [];      // pre-computed list of colors as styles. format: (r,g,b,a)    
  61 var direction_offset = 0;   // current direction offset in radians.  this is applied to all branches.
  62 var frame = 0;              // frame counter
  63 var timespent = 0;          // total time spent so far, used to calculate average frame render duration
  64 var frameratespan;          // html span element for updating performance number
  65 
  66 // preferences object, contains an attribute for each user setting
  67 var 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
  75 function 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
  83 function 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
  90 function 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
 100 function 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
 109 function 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 
 130 function 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
 161 function 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
 168 function 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
 177 function 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
 195 function 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 
 203 populate_colors(color_speed, color_styles, branch_opacity);
 204 setup_canvas();
 205 
 206 var stack = new StackPane();
 207 var pane = new BorderPane();
 208 pane.setCenter(canvas);
 209 stack.getChildren().add(pane);
 210 $STAGE.scene = new Scene(stack);
 211 var 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 };
 221 timer.start();
 222