OLD | NEW |
(Empty) | |
| 1 // Copyright 2011 the V8 project authors. All rights reserved. |
| 2 // Redistribution and use in source and binary forms, with or without |
| 3 // modification, are permitted provided that the following conditions are |
| 4 // met: |
| 5 // |
| 6 // * Redistributions of source code must retain the above copyright |
| 7 // notice, this list of conditions and the following disclaimer. |
| 8 // * Redistributions in binary form must reproduce the above |
| 9 // copyright notice, this list of conditions and the following |
| 10 // disclaimer in the documentation and/or other materials provided |
| 11 // with the distribution. |
| 12 // * Neither the name of Google Inc. nor the names of its |
| 13 // contributors may be used to endorse or promote products derived |
| 14 // from this software without specific prior written permission. |
| 15 // |
| 16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| 17 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| 18 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| 19 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| 20 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| 21 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| 22 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 23 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| 24 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 25 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 26 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 27 |
| 28 |
| 29 /** |
| 30 * This function provides requestAnimationFrame in a cross browser way. |
| 31 * http://paulirish.com/2011/requestanimationframe-for-smart-animating/ |
| 32 */ |
| 33 if ( !window.requestAnimationFrame ) { |
| 34 window.requestAnimationFrame = ( function() { |
| 35 return window.webkitRequestAnimationFrame || |
| 36 window.mozRequestAnimationFrame || |
| 37 window.oRequestAnimationFrame || |
| 38 window.msRequestAnimationFrame || |
| 39 function(callback, element) { |
| 40 window.setTimeout( callback, 1000 / 60 ); |
| 41 }; |
| 42 } )(); |
| 43 } |
| 44 |
| 45 var kNPoints = 8000; |
| 46 var kNModifications = 20; |
| 47 var kNVisiblePoints = 200; |
| 48 var kDecaySpeed = 20; |
| 49 |
| 50 var kPointRadius = 4; |
| 51 var kInitialLifeForce = 100; |
| 52 |
| 53 var livePoints = void 0; |
| 54 var dyingPoints = void 0; |
| 55 var scene = void 0; |
| 56 var renderingStartTime = void 0; |
| 57 var scene = void 0; |
| 58 var pausePlot = void 0; |
| 59 var splayTree = void 0; |
| 60 |
| 61 |
| 62 function Point(x, y, z, payload) { |
| 63 this.x = x; |
| 64 this.y = y; |
| 65 this.z = z; |
| 66 |
| 67 this.next = null; |
| 68 this.prev = null; |
| 69 this.payload = payload; |
| 70 this.lifeForce = kInitialLifeForce; |
| 71 } |
| 72 |
| 73 |
| 74 Point.prototype.color = function () { |
| 75 return "rgba(0, 0, 0, " + (this.lifeForce / kInitialLifeForce) + ")"; |
| 76 }; |
| 77 |
| 78 |
| 79 Point.prototype.decay = function () { |
| 80 this.lifeForce -= kDecaySpeed; |
| 81 return this.lifeForce <= 0; |
| 82 }; |
| 83 |
| 84 |
| 85 function PointsList() { |
| 86 this.head = null; |
| 87 this.count = 0; |
| 88 } |
| 89 |
| 90 |
| 91 PointsList.prototype.add = function (point) { |
| 92 if (this.head !== null) this.head.prev = point; |
| 93 point.next = this.head; |
| 94 this.head = point; |
| 95 this.count++; |
| 96 } |
| 97 |
| 98 |
| 99 PointsList.prototype.remove = function (point) { |
| 100 if (point.next !== null) { |
| 101 point.next.prev = point.prev; |
| 102 } |
| 103 if (point.prev !== null) { |
| 104 point.prev.next = point.next; |
| 105 } else { |
| 106 this.head = point.next; |
| 107 } |
| 108 this.count--; |
| 109 } |
| 110 |
| 111 |
| 112 function GeneratePayloadTree(depth, tag) { |
| 113 if (depth == 0) { |
| 114 return { |
| 115 array : [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ], |
| 116 string : 'String for key ' + tag + ' in leaf node' |
| 117 }; |
| 118 } else { |
| 119 return { |
| 120 left: GeneratePayloadTree(depth - 1, tag), |
| 121 right: GeneratePayloadTree(depth - 1, tag) |
| 122 }; |
| 123 } |
| 124 } |
| 125 |
| 126 |
| 127 // To make the benchmark results predictable, we replace Math.random |
| 128 // with a 100% deterministic alternative. |
| 129 Math.random = (function() { |
| 130 var seed = 49734321; |
| 131 return function() { |
| 132 // Robert Jenkins' 32 bit integer hash function. |
| 133 seed = ((seed + 0x7ed55d16) + (seed << 12)) & 0xffffffff; |
| 134 seed = ((seed ^ 0xc761c23c) ^ (seed >>> 19)) & 0xffffffff; |
| 135 seed = ((seed + 0x165667b1) + (seed << 5)) & 0xffffffff; |
| 136 seed = ((seed + 0xd3a2646c) ^ (seed << 9)) & 0xffffffff; |
| 137 seed = ((seed + 0xfd7046c5) + (seed << 3)) & 0xffffffff; |
| 138 seed = ((seed ^ 0xb55a4f09) ^ (seed >>> 16)) & 0xffffffff; |
| 139 return (seed & 0xfffffff) / 0x10000000; |
| 140 }; |
| 141 })(); |
| 142 |
| 143 |
| 144 function GenerateKey() { |
| 145 // The benchmark framework guarantees that Math.random is |
| 146 // deterministic; see base.js. |
| 147 return Math.random(); |
| 148 } |
| 149 |
| 150 function CreateNewPoint() { |
| 151 // Insert new node with a unique key. |
| 152 var key; |
| 153 do { key = GenerateKey(); } while (splayTree.find(key) != null); |
| 154 |
| 155 var point = new Point(Math.random() * 40 - 20, |
| 156 Math.random() * 40 - 20, |
| 157 Math.random() * 40 - 20, |
| 158 GeneratePayloadTree(5, "" + key)); |
| 159 |
| 160 livePoints.add(point); |
| 161 |
| 162 splayTree.insert(key, point); |
| 163 return key; |
| 164 } |
| 165 |
| 166 function ModifyPointsSet() { |
| 167 if (livePoints.count < kNPoints) { |
| 168 for (var i = 0; i < kNModifications; i++) { |
| 169 CreateNewPoint(); |
| 170 } |
| 171 } else if (kNModifications === 20) { |
| 172 kNModifications = 80; |
| 173 kDecay = 30; |
| 174 } |
| 175 |
| 176 for (var i = 0; i < kNModifications; i++) { |
| 177 var key = CreateNewPoint(); |
| 178 var greatest = splayTree.findGreatestLessThan(key); |
| 179 if (greatest == null) { |
| 180 var point = splayTree.remove(key).value; |
| 181 } else { |
| 182 var point = splayTree.remove(greatest.key).value; |
| 183 } |
| 184 livePoints.remove(point); |
| 185 point.payload = null; |
| 186 dyingPoints.add(point); |
| 187 } |
| 188 } |
| 189 |
| 190 |
| 191 function PausePlot(width, height, size) { |
| 192 var canvas = document.createElement("canvas"); |
| 193 canvas.width = this.width = width; |
| 194 canvas.height = this.height = height; |
| 195 document.body.appendChild(canvas); |
| 196 |
| 197 this.ctx = canvas.getContext('2d'); |
| 198 |
| 199 this.maxPause = 0; |
| 200 this.size = size; |
| 201 |
| 202 // Initialize cyclic buffer for pauses. |
| 203 this.pauses = new Array(this.size); |
| 204 this.start = this.size; |
| 205 this.idx = 0; |
| 206 } |
| 207 |
| 208 |
| 209 PausePlot.prototype.addPause = function (p) { |
| 210 if (this.idx === this.size) { |
| 211 this.idx = 0; |
| 212 } |
| 213 |
| 214 if (this.idx === this.start) { |
| 215 this.start++; |
| 216 } |
| 217 |
| 218 if (this.start === this.size) { |
| 219 this.start = 0; |
| 220 } |
| 221 |
| 222 this.pauses[this.idx++] = p; |
| 223 }; |
| 224 |
| 225 |
| 226 PausePlot.prototype.iteratePauses = function (f) { |
| 227 if (this.start < this.idx) { |
| 228 for (var i = this.start; i < this.idx; i++) { |
| 229 f.call(this, i - this.start, this.pauses[i]); |
| 230 } |
| 231 } else { |
| 232 for (var i = this.start; i < this.size; i++) { |
| 233 f.call(this, i - this.start, this.pauses[i]); |
| 234 } |
| 235 |
| 236 var offs = this.size - this.start; |
| 237 for (var i = 0; i < this.idx; i++) { |
| 238 f.call(this, i + offs, this.pauses[i]); |
| 239 } |
| 240 } |
| 241 }; |
| 242 |
| 243 |
| 244 PausePlot.prototype.draw = function () { |
| 245 var first = null; |
| 246 this.iteratePauses(function (i, v) { |
| 247 if (first === null) { |
| 248 first = v; |
| 249 } |
| 250 this.maxPause = Math.max(v, this.maxPause); |
| 251 }); |
| 252 |
| 253 var dx = this.width / this.size; |
| 254 var dy = this.height / this.maxPause; |
| 255 |
| 256 this.ctx.save(); |
| 257 this.ctx.clearRect(0, 0, 480, 240); |
| 258 this.ctx.beginPath(); |
| 259 this.ctx.moveTo(1, dy * this.pauses[this.start]); |
| 260 var p = first; |
| 261 this.iteratePauses(function (i, v) { |
| 262 var delta = v - p; |
| 263 var x = 1 + dx * i; |
| 264 var y = dy * v; |
| 265 this.ctx.lineTo(x, y); |
| 266 if (delta > 2 * (p / 3)) { |
| 267 this.ctx.font = "bold 12px sans-serif"; |
| 268 this.ctx.textBaseline = "bottom"; |
| 269 this.ctx.fillText(v + "ms", x + 2, y); |
| 270 } |
| 271 p = v; |
| 272 }); |
| 273 this.ctx.strokeStyle = "black"; |
| 274 this.ctx.stroke(); |
| 275 this.ctx.restore(); |
| 276 } |
| 277 |
| 278 |
| 279 function Scene(width, height) { |
| 280 var canvas = document.createElement("canvas"); |
| 281 canvas.width = width; |
| 282 canvas.height = height; |
| 283 document.body.appendChild(canvas); |
| 284 |
| 285 this.ctx = canvas.getContext('2d'); |
| 286 this.width = canvas.width; |
| 287 this.height = canvas.height; |
| 288 |
| 289 // Projection configuration. |
| 290 this.x0 = canvas.width / 2; |
| 291 this.y0 = canvas.height / 2; |
| 292 this.z0 = 100; |
| 293 this.f = 1000; // Focal length. |
| 294 |
| 295 // Camera is rotating around y-axis. |
| 296 this.angle = 0; |
| 297 } |
| 298 |
| 299 |
| 300 Scene.prototype.drawPoint = function (x, y, z, color) { |
| 301 // Rotate the camera around y-axis. |
| 302 var rx = x * Math.cos(this.angle) - z * Math.sin(this.angle); |
| 303 var ry = y; |
| 304 var rz = x * Math.sin(this.angle) + z * Math.cos(this.angle); |
| 305 |
| 306 // Perform perspective projection. |
| 307 var px = (this.f * rx) / (rz - this.z0) + this.x0; |
| 308 var py = (this.f * ry) / (rz - this.z0) + this.y0; |
| 309 |
| 310 this.ctx.save(); |
| 311 this.ctx.fillStyle = color |
| 312 this.ctx.beginPath(); |
| 313 this.ctx.arc(px, py, kPointRadius, 0, 2 * Math.PI, true); |
| 314 this.ctx.fill(); |
| 315 this.ctx.restore(); |
| 316 }; |
| 317 |
| 318 |
| 319 Scene.prototype.drawDyingPoints = function () { |
| 320 var point_next = null; |
| 321 for (var point = dyingPoints.head; point !== null; point = point_next) { |
| 322 // Rotate the scene around y-axis. |
| 323 scene.drawPoint(point.x, point.y, point.z, point.color()); |
| 324 |
| 325 point_next = point.next; |
| 326 |
| 327 // Decay the current point and remove it from the list |
| 328 // if it's life-force ran out. |
| 329 if (point.decay()) { |
| 330 dyingPoints.remove(point); |
| 331 } |
| 332 } |
| 333 }; |
| 334 |
| 335 |
| 336 Scene.prototype.draw = function () { |
| 337 this.ctx.save(); |
| 338 this.ctx.clearRect(0, 0, this.width, this.height); |
| 339 this.drawDyingPoints(); |
| 340 this.ctx.restore(); |
| 341 |
| 342 this.angle += Math.PI / 90.0; |
| 343 }; |
| 344 |
| 345 |
| 346 function render() { |
| 347 if (typeof renderingStartTime === 'undefined') { |
| 348 renderingStartTime = Date.now(); |
| 349 } |
| 350 |
| 351 ModifyPointsSet(); |
| 352 |
| 353 scene.draw(); |
| 354 |
| 355 var renderingEndTime = Date.now(); |
| 356 var pause = renderingEndTime - renderingStartTime; |
| 357 pausePlot.addPause(pause); |
| 358 renderingStartTime = renderingEndTime; |
| 359 |
| 360 pausePlot.draw(); |
| 361 |
| 362 div.innerHTML = |
| 363 livePoints.count + "/" + dyingPoints.count + " " + |
| 364 pause + "(max = " + pausePlot.maxPause + ") ms" ; |
| 365 |
| 366 // Schedule next frame. |
| 367 requestAnimationFrame(render); |
| 368 } |
| 369 |
| 370 |
| 371 function init() { |
| 372 livePoints = new PointsList; |
| 373 dyingPoints = new PointsList; |
| 374 |
| 375 splayTree = new SplayTree(); |
| 376 |
| 377 scene = new Scene(640, 480); |
| 378 |
| 379 div = document.createElement("div"); |
| 380 document.body.appendChild(div); |
| 381 |
| 382 pausePlot = new PausePlot(480, 240, 160); |
| 383 } |
| 384 |
| 385 |
| 386 init(); |
| 387 render(); |
OLD | NEW |