├── README.md ├── LICENSE.md └── rainyday.js /README.md: -------------------------------------------------------------------------------- 1 | rainyday.js 2 | =========== 3 | A simple script for simulating raindrops falling on a glass surface. 4 | 5 | demos available at github pages: [demo 1](http://maroslaw.github.io/rainyday.js/demo1.html) [demo 2](http://maroslaw.github.io/rainyday.js/demo2.html) 6 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {{description}} 294 | Copyright (C) {{year}} {{fullname}} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /rainyday.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Defines a new instance of the rainyday.js. 3 | * @param canvasid DOM id of the canvas used for rendering 4 | * @param sourceid DOM id of the image element used as background image 5 | * @param width width of the rendering 6 | * @param height height of the rendering 7 | * @param opacity opacity attribute value of the glass canvas (default: 1) 8 | * @param blur blur radius (default: 20) 9 | */ 10 | 11 | function RainyDay(canvasid, sourceid, width, height, opacity, blur) { 12 | this.canvasid = canvasid; 13 | this.canvas = document.getElementById(canvasid); 14 | 15 | this.sourceid = sourceid; 16 | this.img = document.getElementById(sourceid); 17 | 18 | // draw and blur the backgroiund image 19 | this.prepareBackground(blur ? blur : 20, width, height); 20 | this.w = this.canvas.width; 21 | this.h = this.canvas.height; 22 | 23 | // create the glass canvas 24 | this.prepareGlass(opacity ? opacity : 1); 25 | 26 | // assume default reflection mechanism 27 | this.reflection = this.REFLECTION_MINIATURE; 28 | 29 | // assume default trail mechanism 30 | this.trail = this.TRAIL_DROPS; 31 | 32 | // assume default gravity 33 | this.gravity = this.GRAVITY_NON_LINEAR; 34 | 35 | // drop size threshold for the gravity algorhitm 36 | this.VARIABLE_GRAVITY_THRESHOLD = 3; 37 | 38 | // gravity angle 39 | this.VARIABLE_GRAVITY_ANGLE = Math.PI / 2; 40 | 41 | // frames per second animation speed 42 | this.VARIABLE_FPS = 25; 43 | 44 | // context fill style when no REFLECTION_NONE is used 45 | this.VARIABLE_FILL_STYLE = '#8ED6FF'; 46 | 47 | // collisions enabled by default 48 | this.VARIABLE_COLLISIONS = false; 49 | 50 | // assume default collision algorhitm 51 | this.collision = this.COLLISION_SIMPLE; 52 | } 53 | 54 | /** 55 | * Create the helper canvas for rendering raindrop reflections. 56 | */ 57 | RainyDay.prototype.prepareReflections = function() { 58 | // new canvas 59 | this.reflected = document.createElement('canvas'); 60 | this.reflected.width = this.canvas.width; 61 | this.reflected.height = this.canvas.height; 62 | 63 | var ctx = this.reflected.getContext('2d'); 64 | 65 | // rotate by 180 degress 66 | ctx.translate(this.reflected.width / 2, this.reflected.height / 2); 67 | ctx.rotate(Math.PI); 68 | 69 | ctx.drawImage(this.img, -this.reflected.width / 2, -this.reflected.height / 2, this.reflected.width, this.reflected.height); 70 | }; 71 | 72 | /** 73 | * Create the glass canvas and position it directly over the main one. 74 | * @param opacity opacity attribute value of the glass canvas 75 | */ 76 | RainyDay.prototype.prepareGlass = function(opacity) { 77 | this.glass = document.createElement('canvas'); 78 | this.glass.width = this.canvas.width; 79 | this.glass.height = this.canvas.height; 80 | this.glass.style.position = "absolute"; 81 | this.glass.style.top = this.canvas.offsetTop; 82 | this.glass.style.left = this.canvas.offsetLeft; 83 | this.glass.style.zIndex = this.canvas.style.zIndex + 100; 84 | this.canvas.parentNode.appendChild(this.glass); 85 | this.context = this.glass.getContext('2d'); 86 | this.glass.style.opacity = opacity; 87 | }; 88 | 89 | /** 90 | * Creates a new preset object with given attributes. 91 | * @param min minimum size of a drop 92 | * @param base base value for randomizing drop size 93 | * @param quan probability of selecting this preset (must be between 0 and 1) 94 | * @returns present object with given attributes 95 | */ 96 | RainyDay.prototype.preset = function(min, base, quan) { 97 | return { 98 | "min": min, 99 | "base": base, 100 | "quan": quan 101 | } 102 | }; 103 | 104 | /** 105 | * Main function for starting rain rendering. 106 | * @param presets list of presets to be applied 107 | * @param speed speed of the animation (if not provided or 0 static image will be generated) 108 | */ 109 | RainyDay.prototype.rain = function(presets, speed) { 110 | // prepare canvas for drop reflections 111 | if (this.reflection != this.REFLECTION_NONE) { 112 | this.prepareReflections(); 113 | } 114 | 115 | if (speed > 0) { 116 | // animation 117 | this.presets = presets; 118 | 119 | this.PRIVATE_GRAVITY_FORCE_FACTOR_Y = (this.VARIABLE_FPS * 0.005) / 25; 120 | this.PRIVATE_GRAVITY_FORCE_FACTOR_X = ((Math.PI / 2) - this.VARIABLE_GRAVITY_ANGLE) * (this.VARIABLE_FPS * 0.005) / 50; 121 | 122 | // prepare gravity matrix 123 | if (this.VARIABLE_COLLISIONS) { 124 | 125 | // calculate max radius of a drop to establish gravity matrix resolution 126 | var maxDropRadius = 0; 127 | for (var i = 0; i < presets.length; i++) { 128 | if (presets[i].base + presets[i].min > maxDropRadius) { 129 | maxDropRadius = Math.floor(presets[i].base + presets[i].min); 130 | } 131 | } 132 | 133 | if (maxDropRadius > 0) { 134 | // initialize the gravity matrix 135 | var mwi = Math.ceil(this.w / maxDropRadius); 136 | var mhi = Math.ceil(this.h / maxDropRadius); 137 | this.matrix = new CollisionMatrix(mwi, mhi, maxDropRadius); 138 | } else { 139 | this.VARIABLE_COLLISIONS = false; 140 | } 141 | } 142 | 143 | setInterval( 144 | (function(self) { 145 | return function() { 146 | var random = Math.random(); 147 | // select matching preset 148 | var preset; 149 | for (var i = 0; i < presets.length; i++) { 150 | if (random < presets[i].quan) { 151 | preset = presets[i]; 152 | break; 153 | } 154 | } 155 | if (preset) { 156 | self.putDrop(new Drop(self, Math.random() * self.w, Math.random() * self.h, preset.min, preset.base)); 157 | } 158 | } 159 | })(this), 160 | speed 161 | ); 162 | } else { 163 | // static picture 164 | for (var i = 0; i < presets.length; i++) { 165 | var preset = presets[i]; 166 | for (var c = 0; c < preset.quan; ++c) { 167 | this.putDrop(new Drop(this, Math.random() * this.w, Math.random() * this.h, preset.min, preset.base)); 168 | } 169 | } 170 | } 171 | }; 172 | 173 | /** 174 | * Adds a new raindrop to the animation. 175 | * @param drop drop object to be added to the animation 176 | */ 177 | RainyDay.prototype.putDrop = function(drop) { 178 | drop.draw(); 179 | if (this.gravity && drop.r1 > this.VARIABLE_GRAVITY_THRESHOLD) { 180 | 181 | if (this.VARIABLE_COLLISIONS) { 182 | // put on the gravity matrix 183 | this.matrix.update(drop); 184 | } 185 | 186 | drop.animate(); 187 | } 188 | }; 189 | 190 | /** 191 | * Imperfectly approximates shape of a circle. 192 | * @param iterations number of iterations applied to the size approximation algorithm 193 | * @returns list of points approximating a circle shape 194 | */ 195 | RainyDay.prototype.getLinepoints = function(iterations) { 196 | var pointList = {}; 197 | pointList.first = { 198 | x: 0, 199 | y: 1 200 | }; 201 | var lastPoint = { 202 | x: 1, 203 | y: 1 204 | } 205 | var minY = 1; 206 | var maxY = 1; 207 | var point; 208 | var nextPoint; 209 | var dx, newX, newY; 210 | 211 | pointList.first.next = lastPoint; 212 | for (var i = 0; i < iterations; i++) { 213 | point = pointList.first; 214 | while (point.next != null) { 215 | nextPoint = point.next; 216 | 217 | dx = nextPoint.x - point.x; 218 | newX = 0.5 * (point.x + nextPoint.x); 219 | newY = 0.5 * (point.y + nextPoint.y); 220 | newY += dx * (Math.random() * 2 - 1); 221 | 222 | var newPoint = { 223 | x: newX, 224 | y: newY 225 | }; 226 | 227 | //min, max 228 | if (newY < minY) { 229 | minY = newY; 230 | } else if (newY > maxY) { 231 | maxY = newY; 232 | } 233 | 234 | //put between points 235 | newPoint.next = nextPoint; 236 | point.next = newPoint; 237 | 238 | point = nextPoint; 239 | } 240 | } 241 | 242 | //normalize to values between 0 and 1 243 | if (maxY != minY) { 244 | var normalizeRate = 1 / (maxY - minY); 245 | point = pointList.first; 246 | while (point != null) { 247 | point.y = normalizeRate * (point.y - minY); 248 | point = point.next; 249 | } 250 | } else { 251 | point = pointList.first; 252 | while (point != null) { 253 | point.y = 1; 254 | point = point.next; 255 | } 256 | } 257 | 258 | return pointList; 259 | }; 260 | 261 | /** 262 | * Defines a new raindrop object. 263 | * @param rainyday reference to the parent object 264 | * @param centerX x position of the center of this drop 265 | * @param centerY y position of the center of this drop 266 | * @param min minimum size of a drop 267 | * @param base base value for randomizing drop size 268 | */ 269 | 270 | function Drop(rainyday, centerX, centerY, min, base) { 271 | this.x = Math.floor(centerX); 272 | this.y = Math.floor(centerY); 273 | this.r1 = (Math.random() * base) + min; 274 | this.rainyday = rainyday; 275 | var iterations = 4; 276 | this.r2 = 0.8 * this.r1; 277 | this.linepoints = rainyday.getLinepoints(iterations); 278 | this.context = rainyday.context; 279 | this.reflection = rainyday.reflected; 280 | } 281 | 282 | /** 283 | * Draws a raindrop on canvas at the current position. 284 | */ 285 | Drop.prototype.draw = function() { 286 | var phase = 0; 287 | var point; 288 | var rad, theta; 289 | var x0, y0; 290 | 291 | this.context.save(); 292 | this.context.beginPath(); 293 | point = this.linepoints.first; 294 | theta = phase; 295 | rad = this.r2 + 0.5 * Math.random() * (this.r2 - this.r1); 296 | x0 = this.x + rad * Math.cos(theta); 297 | y0 = this.y + rad * Math.sin(theta); 298 | this.context.lineTo(x0, y0); 299 | while (point.next != null) { 300 | point = point.next; 301 | theta = (Math.PI * 2 * point.x) + phase; 302 | rad = this.r2 + 0.5 * Math.random() * (this.r2 - this.r1); 303 | x0 = this.x + rad * Math.cos(theta); 304 | y0 = this.y + rad * Math.sin(theta); 305 | this.context.lineTo(x0, y0); 306 | } 307 | 308 | this.context.clip(); 309 | 310 | if (this.rainyday.reflection) { 311 | this.rainyday.reflection(this); 312 | } 313 | 314 | this.context.restore(); 315 | }; 316 | 317 | /** 318 | * Clears the raindrop region. 319 | * @param force force stop 320 | * @returns true if the animation is stopped 321 | */ 322 | Drop.prototype.clear = function(force) { 323 | this.context.clearRect(this.x - this.r1 - 1, this.y - this.r1 - 1, 2 * this.r1 + 2, 2 * this.r1 + 2); 324 | if (force) { 325 | // forced 326 | clearInterval(this.intid); 327 | return true; 328 | } 329 | if (this.y - this.r1 > this.rainyday.h) { 330 | // over the bottom edge, stop the thread 331 | clearInterval(this.intid); 332 | return true; 333 | } 334 | if ((this.x - this.r1 > this.rainyday.w) || (this.x + this.r1 < 0)) { 335 | // over the right or left edge, stop the thread 336 | clearInterval(this.intid); 337 | return true; 338 | } 339 | return false; 340 | }; 341 | 342 | /** 343 | * Moves the raindrop to a new position according to the gravity. 344 | */ 345 | Drop.prototype.animate = function() { 346 | this.intid = setInterval( 347 | (function(self) { 348 | return function() { 349 | var stopped = self.rainyday.gravity(self); 350 | if (!stopped && self.rainyday.trail) { 351 | self.rainyday.trail(self); 352 | } 353 | if (self.rainyday.VARIABLE_COLLISIONS) { 354 | var collision = self.rainyday.matrix.update(self, stopped); 355 | if (collision) { 356 | self.rainyday.collision(self, collision.drop); 357 | } 358 | } 359 | } 360 | })(this), 361 | Math.floor(1000 / this.rainyday.VARIABLE_FPS) 362 | ); 363 | }; 364 | 365 | /** 366 | * TRAIL function: no trail at all 367 | * @param drop raindrop object 368 | */ 369 | RainyDay.prototype.TRAIL_NONE = function(drop) { 370 | // nothing going on here 371 | }; 372 | 373 | /** 374 | * TRAIL function: trail of small drops (default) 375 | * @param drop raindrop object 376 | */ 377 | RainyDay.prototype.TRAIL_DROPS = function(drop) { 378 | if (!drop.trail_y || drop.y - drop.trail_y >= Math.random() * 10 * drop.r1) { 379 | drop.trail_y = drop.y; 380 | this.putDrop(new Drop(this, drop.x, drop.y - drop.r1 - 5, 0, Math.ceil(drop.r1 / 5))); 381 | } 382 | }; 383 | 384 | /** 385 | * GRAVITY function: no gravity at all 386 | * @param drop raindrop object 387 | * @returns true if the animation is stopped 388 | */ 389 | RainyDay.prototype.GRAVITY_NONE = function(drop) { 390 | return true; 391 | }; 392 | 393 | /** 394 | * GRAVITY function: linear gravity 395 | * @param drop raindrop object 396 | * @returns true if the animation is stopped 397 | */ 398 | RainyDay.prototype.GRAVITY_LINEAR = function(drop) { 399 | if (drop.clear()) { 400 | return true; 401 | } 402 | 403 | if (drop.yspeed) { 404 | drop.yspeed += this.PRIVATE_GRAVITY_FORCE_FACTOR_Y * Math.floor(drop.r1); 405 | drop.xspeed += this.PRIVATE_GRAVITY_FORCE_FACTOR_X * Math.floor(drop.r1); 406 | } else { 407 | drop.yspeed = this.PRIVATE_GRAVITY_FORCE_FACTOR_Y; 408 | drop.xspeed = this.PRIVATE_GRAVITY_FORCE_FACTOR_X; 409 | } 410 | 411 | drop.y += drop.yspeed; 412 | drop.draw(); 413 | return false; 414 | }; 415 | 416 | /** 417 | * GRAVITY function: non-linear gravity (default) 418 | * @param drop raindrop object 419 | * @returns true if the animation is stopped 420 | */ 421 | RainyDay.prototype.GRAVITY_NON_LINEAR = function(drop) { 422 | if (drop.clear()) { 423 | return true; 424 | } 425 | 426 | if (!drop.seed || drop.seed < 0) { 427 | drop.seed = Math.floor(Math.random() * this.VARIABLE_FPS); 428 | drop.skipping = drop.skipping == false ? true : false; 429 | drop.slowing = true; 430 | } 431 | 432 | drop.seed--; 433 | 434 | if (drop.yspeed) { 435 | if (drop.slowing) { 436 | drop.yspeed /= 1.1; 437 | drop.xspeed /= 1.1; 438 | if (drop.yspeed < this.PRIVATE_GRAVITY_FORCE_FACTOR_Y) { 439 | drop.slowing = false; 440 | } 441 | } else if (drop.skipping) { 442 | drop.yspeed = this.PRIVATE_GRAVITY_FORCE_FACTOR_Y; 443 | drop.xspeed = this.PRIVATE_GRAVITY_FORCE_FACTOR_X; 444 | } else { 445 | drop.yspeed += 10 * this.PRIVATE_GRAVITY_FORCE_FACTOR_Y * Math.floor(drop.r1); 446 | drop.xspeed += 10 * this.PRIVATE_GRAVITY_FORCE_FACTOR_X * Math.floor(drop.r1); 447 | } 448 | } else { 449 | drop.yspeed = this.PRIVATE_GRAVITY_FORCE_FACTOR_Y; 450 | drop.xspeed = this.PRIVATE_GRAVITY_FORCE_FACTOR_X; 451 | } 452 | 453 | drop.y += drop.yspeed; 454 | drop.x += drop.xspeed; 455 | 456 | drop.draw(); 457 | return false; 458 | }; 459 | 460 | /** 461 | * REFLECTION function: no reflection at all 462 | * @param drop raindrop object 463 | */ 464 | RainyDay.prototype.REFLECTION_NONE = function(drop) { 465 | this.context.fillStyle = this.VARIABLE_FILL_STYLE; 466 | this.context.fill(); 467 | }; 468 | 469 | /** 470 | * REFLECTION function: miniature reflection (default) 471 | * @param drop raindrop object 472 | */ 473 | RainyDay.prototype.REFLECTION_MINIATURE = function(drop) { 474 | this.context.drawImage(this.reflected, drop.x - drop.r1, drop.y - drop.r1, drop.r1 * 2, drop.r1 * 2); 475 | }; 476 | 477 | /** 478 | * COLLISION function: default collision implementation 479 | * @param drop1 one of the drops colliding 480 | * @param drop2 the other one 481 | */ 482 | RainyDay.prototype.COLLISION_SIMPLE = function(drop1, drop2) { 483 | drop1.clear(); 484 | // force stopping the second drop 485 | drop2.clear(true); 486 | 487 | drop1.x = (drop1.x + drop2.x) / 2; 488 | drop1.y = (drop1.y + drop2.y) / 2; 489 | }; 490 | 491 | var mul_table = [ 492 | 512, 512, 456, 512, 328, 456, 335, 512, 405, 328, 271, 456, 388, 335, 292, 512, 493 | 454, 405, 364, 328, 298, 271, 496, 456, 420, 388, 360, 335, 312, 292, 273, 512, 494 | 482, 454, 428, 405, 383, 364, 345, 328, 312, 298, 284, 271, 259, 496, 475, 456, 495 | 437, 420, 404, 388, 374, 360, 347, 335, 323, 312, 302, 292, 282, 273, 265, 512, 496 | 497, 482, 468, 454, 441, 428, 417, 405, 394, 383, 373, 364, 354, 345, 337, 328, 497 | 320, 312, 305, 298, 291, 284, 278, 271, 265, 259, 507, 496, 485, 475, 465, 456, 498 | 446, 437, 428, 420, 412, 404, 396, 388, 381, 374, 367, 360, 354, 347, 341, 335, 499 | 329, 323, 318, 312, 307, 302, 297, 292, 287, 282, 278, 273, 269, 265, 261, 512, 500 | 505, 497, 489, 482, 475, 468, 461, 454, 447, 441, 435, 428, 422, 417, 411, 405, 501 | 399, 394, 389, 383, 378, 373, 368, 364, 359, 354, 350, 345, 341, 337, 332, 328, 502 | 324, 320, 316, 312, 309, 305, 301, 298, 294, 291, 287, 284, 281, 278, 274, 271, 503 | 268, 265, 262, 259, 257, 507, 501, 496, 491, 485, 480, 475, 470, 465, 460, 456, 504 | 451, 446, 442, 437, 433, 428, 424, 420, 416, 412, 408, 404, 400, 396, 392, 388, 505 | 385, 381, 377, 374, 370, 367, 363, 360, 357, 354, 350, 347, 344, 341, 338, 335, 506 | 332, 329, 326, 323, 320, 318, 315, 312, 310, 307, 304, 302, 299, 297, 294, 292, 507 | 289, 287, 285, 282, 280, 278, 275, 273, 271, 269, 267, 265, 263, 261, 259 508 | ]; 509 | 510 | var shg_table = [ 511 | 9, 11, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17, 512 | 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, 513 | 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20, 514 | 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21, 515 | 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 516 | 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 517 | 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 518 | 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 519 | 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 520 | 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 521 | 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 522 | 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 523 | 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 524 | 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 525 | 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 526 | 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24 527 | ]; 528 | 529 | /** 530 | * Resizes canvas, draws original image and applies bluring algorithm. 531 | * @param radius blur radius to be applied 532 | * @param width width of the canvas 533 | * @param height height of the canvas 534 | */ 535 | RainyDay.prototype.prepareBackground = function(radius, width, height) { 536 | if (width && height) { 537 | this.canvas.style.width = width + "px"; 538 | this.canvas.style.height = height + "px"; 539 | this.canvas.width = width; 540 | this.canvas.height = height; 541 | } else { 542 | width = this.canvas.width; 543 | height = this.canvas.height; 544 | } 545 | 546 | var context = this.canvas.getContext("2d"); 547 | context.clearRect(0, 0, width, height); 548 | context.drawImage(this.img, 0, 0, width, height); 549 | 550 | if (isNaN(radius) || radius < 1) return; 551 | 552 | this.stackBlurCanvasRGB(0, 0, width, height, radius); 553 | }; 554 | 555 | /** 556 | * Implements the Stack Blur Algorithm (@see http://www.quasimondo.com/StackBlurForCanvas/StackBlurDemo.html). 557 | * @param top_x x of top-left corner of the blurred rectangle 558 | * @param top_y y of top-left corner of the blurred rectangle 559 | * @param width width of the canvas 560 | * @param height height of the canvas 561 | * @param radius blur radius 562 | */ 563 | RainyDay.prototype.stackBlurCanvasRGB = function(top_x, top_y, width, height, radius) { 564 | radius |= 0; 565 | 566 | var context = this.canvas.getContext("2d"); 567 | var imageData = context.getImageData(top_x, top_y, width, height); 568 | 569 | var pixels = imageData.data; 570 | 571 | var x, y, i, p, yp, yi, yw, r_sum, g_sum, b_sum, 572 | r_out_sum, g_out_sum, b_out_sum, 573 | r_in_sum, g_in_sum, b_in_sum, 574 | pr, pg, pb, rbs; 575 | 576 | var div = radius + radius + 1; 577 | var w4 = width << 2; 578 | var widthMinus1 = width - 1; 579 | var heightMinus1 = height - 1; 580 | var radiusPlus1 = radius + 1; 581 | var sumFactor = radiusPlus1 * (radiusPlus1 + 1) / 2; 582 | 583 | var stackStart = new BlurStack(); 584 | var stack = stackStart; 585 | for (i = 1; i < div; i++) { 586 | stack = stack.next = new BlurStack(); 587 | if (i == radiusPlus1) var stackEnd = stack; 588 | } 589 | stack.next = stackStart; 590 | var stackIn = null; 591 | var stackOut = null; 592 | 593 | yw = yi = 0; 594 | 595 | var mul_sum = mul_table[radius]; 596 | var shg_sum = shg_table[radius]; 597 | 598 | for (y = 0; y < height; y++) { 599 | r_in_sum = g_in_sum = b_in_sum = r_sum = g_sum = b_sum = 0; 600 | 601 | r_out_sum = radiusPlus1 * (pr = pixels[yi]); 602 | g_out_sum = radiusPlus1 * (pg = pixels[yi + 1]); 603 | b_out_sum = radiusPlus1 * (pb = pixels[yi + 2]); 604 | 605 | r_sum += sumFactor * pr; 606 | g_sum += sumFactor * pg; 607 | b_sum += sumFactor * pb; 608 | 609 | stack = stackStart; 610 | 611 | for (i = 0; i < radiusPlus1; i++) { 612 | stack.r = pr; 613 | stack.g = pg; 614 | stack.b = pb; 615 | stack = stack.next; 616 | } 617 | 618 | for (i = 1; i < radiusPlus1; i++) { 619 | p = yi + ((widthMinus1 < i ? widthMinus1 : i) << 2); 620 | r_sum += (stack.r = (pr = pixels[p])) * (rbs = radiusPlus1 - i); 621 | g_sum += (stack.g = (pg = pixels[p + 1])) * rbs; 622 | b_sum += (stack.b = (pb = pixels[p + 2])) * rbs; 623 | 624 | r_in_sum += pr; 625 | g_in_sum += pg; 626 | b_in_sum += pb; 627 | 628 | stack = stack.next; 629 | } 630 | 631 | stackIn = stackStart; 632 | stackOut = stackEnd; 633 | for (x = 0; x < width; x++) { 634 | pixels[yi] = (r_sum * mul_sum) >> shg_sum; 635 | pixels[yi + 1] = (g_sum * mul_sum) >> shg_sum; 636 | pixels[yi + 2] = (b_sum * mul_sum) >> shg_sum; 637 | 638 | r_sum -= r_out_sum; 639 | g_sum -= g_out_sum; 640 | b_sum -= b_out_sum; 641 | 642 | r_out_sum -= stackIn.r; 643 | g_out_sum -= stackIn.g; 644 | b_out_sum -= stackIn.b; 645 | 646 | p = (yw + ((p = x + radius + 1) < widthMinus1 ? p : widthMinus1)) << 2; 647 | 648 | r_in_sum += (stackIn.r = pixels[p]); 649 | g_in_sum += (stackIn.g = pixels[p + 1]); 650 | b_in_sum += (stackIn.b = pixels[p + 2]); 651 | 652 | r_sum += r_in_sum; 653 | g_sum += g_in_sum; 654 | b_sum += b_in_sum; 655 | 656 | stackIn = stackIn.next; 657 | 658 | r_out_sum += (pr = stackOut.r); 659 | g_out_sum += (pg = stackOut.g); 660 | b_out_sum += (pb = stackOut.b); 661 | 662 | r_in_sum -= pr; 663 | g_in_sum -= pg; 664 | b_in_sum -= pb; 665 | 666 | stackOut = stackOut.next; 667 | 668 | yi += 4; 669 | } 670 | yw += width; 671 | } 672 | 673 | 674 | for (x = 0; x < width; x++) { 675 | g_in_sum = b_in_sum = r_in_sum = g_sum = b_sum = r_sum = 0; 676 | 677 | yi = x << 2; 678 | r_out_sum = radiusPlus1 * (pr = pixels[yi]); 679 | g_out_sum = radiusPlus1 * (pg = pixels[yi + 1]); 680 | b_out_sum = radiusPlus1 * (pb = pixels[yi + 2]); 681 | 682 | r_sum += sumFactor * pr; 683 | g_sum += sumFactor * pg; 684 | b_sum += sumFactor * pb; 685 | 686 | stack = stackStart; 687 | 688 | for (i = 0; i < radiusPlus1; i++) { 689 | stack.r = pr; 690 | stack.g = pg; 691 | stack.b = pb; 692 | stack = stack.next; 693 | } 694 | 695 | yp = width; 696 | 697 | for (i = 1; i <= radius; i++) { 698 | yi = (yp + x) << 2; 699 | 700 | r_sum += (stack.r = (pr = pixels[yi])) * (rbs = radiusPlus1 - i); 701 | g_sum += (stack.g = (pg = pixels[yi + 1])) * rbs; 702 | b_sum += (stack.b = (pb = pixels[yi + 2])) * rbs; 703 | 704 | r_in_sum += pr; 705 | g_in_sum += pg; 706 | b_in_sum += pb; 707 | 708 | stack = stack.next; 709 | 710 | if (i < heightMinus1) { 711 | yp += width; 712 | } 713 | } 714 | 715 | yi = x; 716 | stackIn = stackStart; 717 | stackOut = stackEnd; 718 | for (y = 0; y < height; y++) { 719 | p = yi << 2; 720 | pixels[p] = (r_sum * mul_sum) >> shg_sum; 721 | pixels[p + 1] = (g_sum * mul_sum) >> shg_sum; 722 | pixels[p + 2] = (b_sum * mul_sum) >> shg_sum; 723 | 724 | r_sum -= r_out_sum; 725 | g_sum -= g_out_sum; 726 | b_sum -= b_out_sum; 727 | 728 | r_out_sum -= stackIn.r; 729 | g_out_sum -= stackIn.g; 730 | b_out_sum -= stackIn.b; 731 | 732 | p = (x + (((p = y + radiusPlus1) < heightMinus1 ? p : heightMinus1) * width)) << 2; 733 | 734 | r_sum += (r_in_sum += (stackIn.r = pixels[p])); 735 | g_sum += (g_in_sum += (stackIn.g = pixels[p + 1])); 736 | b_sum += (b_in_sum += (stackIn.b = pixels[p + 2])); 737 | 738 | stackIn = stackIn.next; 739 | 740 | r_out_sum += (pr = stackOut.r); 741 | g_out_sum += (pg = stackOut.g); 742 | b_out_sum += (pb = stackOut.b); 743 | 744 | r_in_sum -= pr; 745 | g_in_sum -= pg; 746 | b_in_sum -= pb; 747 | 748 | stackOut = stackOut.next; 749 | 750 | yi += width; 751 | } 752 | } 753 | 754 | context.putImageData(imageData, top_x, top_y); 755 | 756 | }; 757 | 758 | /** 759 | * Defines a new helper object for Stack Blur Algorithm. 760 | */ 761 | 762 | function BlurStack() { 763 | this.r = 0; 764 | this.g = 0; 765 | this.b = 0; 766 | this.a = 0; 767 | this.next = null; 768 | } 769 | 770 | /** 771 | * Defines a gravity matrix object which handles collision detection. 772 | * @param x number of columns in the matrix 773 | * @param y number of rows in the matrix 774 | * @param r grid size 775 | */ 776 | 777 | function CollisionMatrix(x, y, r) { 778 | this.resolution = r; 779 | this.xc = x; 780 | this.yc = y; 781 | this.matrix = new Array(x); 782 | for (var i = 0; i <= (x + 5); i++) { 783 | this.matrix[i] = Array(y); 784 | for (var j = 0; j <= (y + 5); ++j) { 785 | this.matrix[i][j] = new DropItem(null); 786 | } 787 | } 788 | } 789 | 790 | /** 791 | * Updates position of the given drop on the collision matrix. 792 | * @param drop raindrop to be positioned/repositioned 793 | * @forceDelete if true the raindrop will be removed from the matrix 794 | * @returns collisions if any 795 | */ 796 | CollisionMatrix.prototype.update = function(drop, forceDelete) { 797 | if (drop.gid) { 798 | this.matrix[drop.gmx][drop.gmy].remove(drop); 799 | if (forceDelete) { 800 | return null; 801 | } 802 | 803 | drop.gmx = Math.floor(drop.x / this.resolution); 804 | drop.gmy = Math.floor(drop.y / this.resolution); 805 | this.matrix[drop.gmx][drop.gmy].add(drop); 806 | 807 | var collisions = this.collisions(drop); 808 | if (collisions && collisions.next != null) { 809 | return collisions.next; 810 | } 811 | } else { 812 | drop.gid = Math.random().toString(36).substr(2, 9); 813 | drop.gmx = Math.floor(drop.x / this.resolution); 814 | drop.gmy = Math.floor(drop.y / this.resolution); 815 | this.matrix[drop.gmx][drop.gmy].add(drop); 816 | } 817 | return null; 818 | }; 819 | 820 | /** 821 | * Looks for collisions with the given raindrop. 822 | * @param drop raindrop to be checked 823 | * @returns list of drops that collide with it 824 | */ 825 | CollisionMatrix.prototype.collisions = function(drop) { 826 | var item = new DropItem(null); 827 | var first = item; 828 | 829 | item = this.addAll(item, drop.gmx - 1, drop.gmy); 830 | item = this.addAll(item, drop.gmx - 1, drop.gmy + 1); 831 | item = this.addAll(item, drop.gmx, drop.gmy + 1); 832 | item = this.addAll(item, drop.gmx + 1, drop.gmy + 1); 833 | item = this.addAll(item, drop.gmx + 1, drop.gmy); 834 | 835 | return first; 836 | }; 837 | 838 | /** 839 | * Appends all found drop at a given location to the given item. 840 | * @param to item to which the results will be appended to 841 | * @param x x position in the matrix 842 | * @param y y position in the matrix 843 | * @returns last discovered item on the list 844 | */ 845 | CollisionMatrix.prototype.addAll = function(to, x, y) { 846 | if (x > 0 && y > 0 && x < this.xc && y < this.yc) { 847 | var items = this.matrix[x][y]; 848 | while (items.next != null) { 849 | items = items.next; 850 | to.next = new DropItem(items.drop); 851 | to = to.next; 852 | } 853 | } 854 | return to; 855 | }; 856 | 857 | /** 858 | * Defines a linked list item. 859 | */ 860 | 861 | function DropItem(drop) { 862 | this.drop = drop; 863 | this.next = null; 864 | } 865 | 866 | /** 867 | * Adds the raindrop to the end of the list. 868 | * @param drop raindrop to be added 869 | */ 870 | DropItem.prototype.add = function(drop) { 871 | var item = this; 872 | while (item.next != null) { 873 | item = item.next; 874 | } 875 | item.next = new DropItem(drop); 876 | }; 877 | 878 | /** 879 | * Removes the raindrop from the list. 880 | * @param drop raindrop to be removed 881 | */ 882 | DropItem.prototype.remove = function(drop) { 883 | var item = this; 884 | var prevItem = null; 885 | while (item.next != null) { 886 | prevItem = item; 887 | item = item.next; 888 | if (item.drop.gid == drop.gid) { 889 | prevItem.next = item.next; 890 | } 891 | } 892 | }; --------------------------------------------------------------------------------