├── Clock.gif ├── Readme.md ├── clock.css ├── clock.js └── index.html /Clock.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tunabrain/physiclock/4f25f5ac0958614217a7c3b83eddfeec4fd310c4/Clock.gif -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | 2 | # Physiclock # 3 | 4 | A physically based block in JS, based on verlet physics. 5 | 6 | License: zlib/libpng. 7 | 8 | ![Physiclock](https://github.com/tunabrain/physiclock/blob/master/Clock.gif?raw=true "Physiclock") -------------------------------------------------------------------------------- /clock.css: -------------------------------------------------------------------------------- 1 | #footer { 2 | text-align: center; 3 | height: 0px; 4 | position: absolute; 5 | bottom: 20px; 6 | left: 0; 7 | right: 0; 8 | } 9 | 10 | .clickable, a { 11 | color: #bbb; 12 | cursor: pointer; 13 | text-decoration: underline; 14 | margin: 0 20px; 15 | -webkit-touch-callout: none; 16 | -webkit-user-select: none; 17 | -khtml-user-select: none; 18 | -moz-user-select: none; 19 | -ms-user-select: none; 20 | user-select: none; 21 | } 22 | -------------------------------------------------------------------------------- /clock.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /* Disclaimer: This is *not* how you should do physics. 4 | 5 | Copyright (c) 2016 Benedikt Bitterli 6 | 7 | This software is provided 'as-is', without any express or implied warranty. 8 | In no event will the authors be held liable for any damages arising from 9 | the use of this software. 10 | 11 | Permission is granted to anyone to use this software for any purpose, 12 | including commercial applications, and to alter it and redistribute it freely, 13 | subject to the following restrictions: 14 | 15 | 1. The origin of this software must not be misrepresented; you must not claim 16 | that you wrote the original software. If you use this software in a product, 17 | an acknowledgment in the product documentation would be appreciated but is 18 | not required. 19 | 20 | 2. Altered source versions must be plainly marked as such, and must not be 21 | misrepresented as being the original software. 22 | 23 | 3. This notice may not be removed or altered from any source distribution. 24 | 25 | */ 26 | 27 | var CollisionInfo = function() { 28 | this.reset(); 29 | } 30 | CollisionInfo.prototype.reset = function() { 31 | this.x = this.y = this.nx = this.ny = 0; 32 | this.depth = Infinity; 33 | } 34 | CollisionInfo.prototype.update = function(depth, nx, ny, x, y) { 35 | if (depth < this.depth) { 36 | this.x = x; 37 | this.y = y; 38 | this.nx = nx; 39 | this.ny = ny; 40 | this.depth = depth; 41 | return 1; 42 | } 43 | return 0; 44 | } 45 | 46 | var Body = function(type) { 47 | this.type = type; 48 | this.visible = true; 49 | } 50 | Body.GRAVITY = 1; 51 | 52 | Body.SPHERE = 0; 53 | Body.LINE = 1; 54 | Body.SEESAW = 2; 55 | 56 | Body.prototype.sphereSphereCollision = function(other, collisionInfo) { 57 | var dx = other.x - this.x; 58 | var dy = other.y - this.y; 59 | var dSq = dx*dx + dy*dy; 60 | if (dSq < (this.radius + other.radius)*(this.radius + other.radius)) { 61 | var d = Math.sqrt(dSq); 62 | return collisionInfo.update(this.radius + other.radius - d, dx/d, dy/d, this.x, this.y); 63 | } 64 | return 0; 65 | } 66 | Body.prototype.lineSphereCollision = function(other, collisionInfo) { 67 | var dx = other.x - this.s1.x, dy = other.y - this.s1.y; 68 | var lx = this.s2.x - this.s1.x, ly = this.s2.y - this.s1.y; 69 | var t = (dx*lx + dy*ly)/(lx*lx + ly*ly); 70 | 71 | if (t < 0.0) { 72 | return this.s1.sphereSphereCollision(other, collisionInfo); 73 | } else if (t > 1.0) { 74 | return this.s2.sphereSphereCollision(other, collisionInfo); 75 | } else { 76 | var l = Math.sqrt(lx*lx + ly*ly); 77 | var nx = -ly/l; 78 | var ny = lx/l; 79 | var dot = nx*dx + ny*dy; 80 | if (dot < 0.0) { 81 | dot = -dot; 82 | nx = -nx; 83 | ny = -ny; 84 | } 85 | if (dot < this.radius + other.radius) 86 | return collisionInfo.update(this.radius + other.radius - dot, nx, ny, other.x, other.y); 87 | } 88 | return 0; 89 | } 90 | Body.prototype.lineLineCollision = function(other, collisionInfo) { 91 | var os1 = this.lineSphereCollision(other.s1, collisionInfo); 92 | var os2 = this.lineSphereCollision(other.s2, collisionInfo); 93 | var s1o = other.lineSphereCollision(this.s1, collisionInfo); 94 | var s2o = other.lineSphereCollision(this.s2, collisionInfo); 95 | return (s1o || s2o) ? -1 : (os1 || os2) ? 1 : 0; 96 | } 97 | Body.prototype.detectCollision = function(other, collisionInfo) { 98 | if (this.type == Body.SPHERE) { 99 | if (other.type == Body.SPHERE) 100 | return this.sphereSphereCollision(other, collisionInfo); 101 | else 102 | return -other.lineSphereCollision(this, collisionInfo); 103 | } else { 104 | if (other.type == Body.SPHERE) 105 | return this.lineSphereCollision(other, collisionInfo); 106 | else 107 | return this.lineLineCollision(other, collisionInfo); 108 | } 109 | } 110 | Body.prototype.computeMass = function(info) { 111 | if (this.type == Body.SPHERE) { 112 | return this.mass; 113 | } else { 114 | var dx = info.x - this.s1.x, dy = info.y - this.s1.y; 115 | var lx = this.s2.x - this.s1.x, ly = this.s2.y - this.s1.y; 116 | var t = (dx*lx + dy*ly)/(lx*lx + ly*ly); 117 | return (1.0 - t)*this.s1.mass + t*this.s2.mass; 118 | } 119 | } 120 | Body.prototype.applyImpulse = function(info, scale, isStatic) { 121 | if (this.type == Body.SPHERE) { 122 | this.x -= info.nx*info.depth*scale; 123 | this.y -= info.ny*info.depth*scale; 124 | if (isStatic) { 125 | var vx = this.x - this.oldX; 126 | var vy = this.y - this.oldY; 127 | var dot = Math.min(vx*info.nx*scale + vy*info.ny*scale, 0); 128 | vx -= info.nx*scale*dot; 129 | vy -= info.ny*scale*dot; 130 | this.oldX = this.x - vx; 131 | this.oldY = this.y - vy; 132 | } 133 | } else if (this.type == Body.SEESAW) { 134 | var dx = info.x - this.s1.x, dy = info.y - this.s1.y; 135 | var lx = this.s2.x - this.s1.x, ly = this.s2.y - this.s1.y; 136 | var t = (dx*lx + dy*ly)/(lx*lx + ly*ly); 137 | var r = this.s1.mass/(this.s1.mass + this.s2.mass); 138 | this.s1.applyImpulse(info, scale*(1.0 - t)*(1.0 - r)); 139 | this.s2.applyImpulse(info, scale*t*r); 140 | this.updateSeesawAlignment(); 141 | } 142 | } 143 | Body.prototype.updateSeesawAlignment = function() { 144 | var hingeX = this.s1.x*(1.0 - this.hinge) + this.s2.x*this.hinge; 145 | var hingeY = this.s1.y*(1.0 - this.hinge) + this.s2.y*this.hinge; 146 | var dx = (this.hingeX - hingeX)*this.lambda; 147 | var dy = (this.hingeY - hingeY)*this.lambda; 148 | var r = this.s1.mass/(this.s1.mass + this.s2.mass); 149 | this.s1.x += dx*(1.0 - this.hinge)*(1.0 - r); 150 | this.s1.y += dy*(1.0 - this.hinge)*(1.0 - r); 151 | this.s2.x += dx*this.hinge*r; 152 | this.s2.y += dy*this.hinge*r; 153 | 154 | var lx = this.s2.x - this.s1.x; 155 | var ly = this.s2.y - this.s1.y; 156 | var l = Math.sqrt(lx*lx + ly*ly); 157 | var dl = (l - this.length)/l*0.5; 158 | 159 | this.s1.x += lx*dl; 160 | this.s1.y += ly*dl; 161 | this.s2.x -= lx*dl; 162 | this.s2.y -= ly*dl; 163 | } 164 | Body.prototype.update = function() { 165 | if (this.type == Body.SPHERE) { 166 | var oldX = this.x, oldY = this.y; 167 | this.x += this.x - this.oldX; 168 | this.y += this.y - this.oldY; 169 | this.oldX = oldX; 170 | this.oldY = oldY - Body.GRAVITY; 171 | } else if (this.type == Body.SEESAW) { 172 | this.s1.update(); 173 | this.s2.update(); 174 | this.updateSeesawAlignment(); 175 | } 176 | } 177 | Body.prototype.draw = function(ctx) { 178 | ctx.beginPath(); 179 | ctx.fillStyle = "#ccc"; 180 | ctx.strokeStyle = "#ccc"; 181 | if (this.type == Body.SPHERE) { 182 | ctx.ellipse(this.x, this.y, this.radius, this.radius, 0.0, 0.0, Math.PI*2.0, false); 183 | ctx.fill(); 184 | } else { 185 | ctx.lineCap = "round"; 186 | ctx.lineWidth = this.radius*2.0; 187 | ctx.moveTo(this.s1.x, this.s1.y); 188 | ctx.lineTo(this.s2.x, this.s2.y); 189 | ctx.stroke(); 190 | } 191 | } 192 | function Sphere(x, y, radius, mass) { 193 | var body = new Body(Body.SPHERE); 194 | body.oldX = body.x = x; 195 | body.oldY = body.y = y; 196 | body.radius = radius; 197 | body.mass = mass; 198 | return body; 199 | } 200 | function Line(x1, y1, x2, y2, radius) { 201 | var body = new Body(Body.LINE); 202 | body.s1 = Sphere(x1, y1, radius, 1.0); 203 | body.s2 = Sphere(x2, y2, radius, 1.0); 204 | body.radius = radius; 205 | return body; 206 | } 207 | function Seesaw(x1, y1, x2, y2, radius, hinge, mass1, mass2) { 208 | var body = new Body(Body.SEESAW); 209 | var ratio = mass1/(mass1 + mass2); 210 | body.lambda = 1.0/(hinge*hinge*ratio + (1.0 - hinge)*(1.0 - hinge)*(1.0 - ratio)); 211 | body.s1 = Sphere(x1, y1, radius, mass1); 212 | body.s2 = Sphere(x2, y2, radius, mass2); 213 | body.hinge = hinge; 214 | body.hingeX = x1*(1.0 - hinge) + x2*hinge; 215 | body.hingeY = y1*(1.0 - hinge) + y2*hinge; 216 | body.length = Math.sqrt((x2 - x1)*(x2 - x1) + (y2 - y1)*(y2 - y1)); 217 | body.radius = radius; 218 | return body; 219 | } 220 | 221 | var Elevator = function(clock, ratio, x, y, width, height) { 222 | this.x = x; 223 | this.y = y; 224 | this.width = width; 225 | this.height = height; 226 | 227 | this.line1 = Line(0, 0, 10, 10, 3); 228 | this.line2 = Line(10, 10, 20, 0, 3); 229 | this.s1 = Sphere(x + width*0.5, y + width*0.5, width*0.5, 1.0); 230 | this.s2 = Sphere(x + width*0.5, y + height - width*0.5, width*0.5, 1.0); 231 | clock.addStaticBody(this.line1); 232 | clock.addStaticBody(this.line2); 233 | clock.addStaticBody(this.s1); 234 | clock.addStaticBody(this.s2); 235 | 236 | this.setRatio(ratio); 237 | 238 | if (ratio >= 0.475 && ratio < 0.5) 239 | clock.addDynamicBody(Sphere(20, 380, 10, 0.1)); 240 | else if (ratio > 0.5) 241 | clock.addDynamicBody(Sphere(0.5*(this.line1.s1.x + this.line2.s1.x), 0.5*(this.line1.s1.y + this.line2.s1.y) - 8, 10, 0.1)); 242 | } 243 | Elevator.prototype.setRatio = function(ratio) { 244 | var circumCircle = this.width*Math.PI; 245 | var circleRatio = circumCircle/(circumCircle + (this.height - this.width)*2); 246 | var ropeRatio = 1.0 - circleRatio; 247 | 248 | if (ratio < 0.25*circleRatio) { 249 | var angle = Math.PI*0.5*(1.0 - ratio/(0.25*circleRatio)); 250 | this.setCircleRatio(angle, this.s1.x, this.s1.y); 251 | } else if (ratio >= ropeRatio + circleRatio*0.75) { 252 | var angle = -Math.PI*(1.0 + 0.5*(ratio - ropeRatio - circleRatio*0.75)/(0.25*circleRatio)); 253 | this.setCircleRatio(angle, this.s1.x, this.s1.y); 254 | } else if (ratio >= ropeRatio*0.5 + 0.25*circleRatio && ratio <= ropeRatio*0.5 + 0.75*circleRatio) { 255 | var angle = -Math.PI*(ratio - ropeRatio*0.5 - 0.25*circleRatio)/(0.5*circleRatio); 256 | this.setCircleRatio(angle, this.s2.x, this.s2.y); 257 | } else if (ratio >= 0.25*circleRatio && ratio <= 0.25*circleRatio + ropeRatio*0.5) { 258 | this.setRightRopeRatio((ratio - 0.25*circleRatio)/(0.5*ropeRatio)); 259 | } else { 260 | this.setLeftRopeRatio((ratio - 0.5*ropeRatio - 0.75*circleRatio)/(0.5*ropeRatio)); 261 | } 262 | } 263 | Elevator.prototype.setLeftRopeRatio = function(ratio) { 264 | var y = this.y - this.width*0.5 + this.height - (this.height - this.width)*ratio; 265 | this.line1.s1.x = this.x; this.line1.s1.y = y; 266 | this.line1.s2.x = this.x - 10; this.line1.s2.y = y + 10; 267 | this.line2.s2.x = this.x - 10; this.line2.s2.y = y + 10; 268 | this.line2.s1.x = this.x - 20; this.line2.s1.y = y; 269 | } 270 | Elevator.prototype.setRightRopeRatio = function(ratio) { 271 | var y = this.y + this.width*0.5 + (this.height - this.width)*ratio; 272 | this.line1.s1.x = this.x + this.width; this.line1.s1.y = y; 273 | this.line1.s2.x = this.x + this.width + 10; this.line1.s2.y = y - 10; 274 | this.line2.s2.x = this.x + this.width + 10; this.line2.s2.y = y - 10; 275 | this.line2.s1.x = this.x + this.width + 20; this.line2.s1.y = y; 276 | } 277 | 278 | Elevator.prototype.setCircleRatio = function(angle, x, y) { 279 | var d1 = this.width*0.5; 280 | var d2 = Math.sqrt((d1 + 10.0)*(d1 + 10.0) + 100.0); 281 | var d3 = d1 + 20.0; 282 | var delta = -Math.atan2(-10, d1 + 10); 283 | 284 | var x1 = x + d1*Math.cos(angle ), y1 = y - d1*Math.sin(angle); 285 | var x2 = x + d2*Math.cos(angle + delta), y2 = y - d2*Math.sin(angle + delta); 286 | var x3 = x + d3*Math.cos(angle ), y3 = y - d3*Math.sin(angle); 287 | 288 | this.line1.s1.x = x1; this.line1.s1.y = y1; 289 | this.line1.s2.x = x2; this.line1.s2.y = y2; 290 | this.line2.s2.x = x2; this.line2.s2.y = y2; 291 | this.line2.s1.x = x3; this.line2.s1.y = y3; 292 | } 293 | Elevator.prototype.drawBackground = function(ctx) { 294 | ctx.strokeStyle = "#eee"; 295 | ctx.lineCap = "square"; 296 | ctx.lineWidth = 2; 297 | ctx.beginPath(); 298 | ctx.moveTo(this.x + this.width - 2, this.y + this.width*0.5); 299 | ctx.lineTo(this.x + this.width - 2, this.y + this.height - this.width*0.5); 300 | ctx.moveTo(this.x + 2, this.y + this.width*0.5); 301 | ctx.lineTo(this.x + 2, this.y + this.height - this.width*0.5); 302 | ctx.stroke(); 303 | } 304 | Elevator.prototype.drawForeground = function(ctx, seconds) { 305 | var circumference = this.width*Math.PI + this.height*2 - this.width*2; 306 | var turnPerSecond = Math.PI*2.0*circumference/(this.width*Math.PI*60.0); 307 | 308 | var rotation = seconds*turnPerSecond; 309 | var numSpokes = 10; 310 | 311 | for (var wheel = 0; wheel < 2; ++wheel) { 312 | ctx.save(); 313 | ctx.translate(this.x + this.width*0.5, this.y + this.width*0.5 + wheel*(this.height - this.width)); 314 | ctx.rotate(rotation); 315 | for (var i = 0; i < numSpokes; ++i) { 316 | ctx.strokeStyle = "#fff"; 317 | ctx.lineCap = "round"; 318 | ctx.lineWidth = 1; 319 | ctx.beginPath(); 320 | ctx.moveTo(10, 0); 321 | ctx.lineTo(this.width*0.5 - 5, 0); 322 | ctx.stroke(); 323 | ctx.rotate(Math.PI*2.0/numSpokes); 324 | } 325 | ctx.restore(); 326 | } 327 | } 328 | 329 | var Clock = function(canvas) { 330 | this.canvas = canvas; 331 | this.ctx = canvas.getContext('2d'); 332 | this.iterations = 20; 333 | this.speed = 1; 334 | this.lastTime = Date.now(); 335 | 336 | this.setTime(new Date()); 337 | this.reset(); 338 | 339 | if (!this.ctx.ellipse) { 340 | this.ctx.ellipse = function(cx, cy, rx, ry, rot, aStart, aEnd) { 341 | this.save(); 342 | this.translate(cx, cy); 343 | this.rotate(rot); 344 | this.translate(-rx, -ry); 345 | this.scale(rx, ry); 346 | this.arc(1, 1, 1, aStart, aEnd, false); 347 | this.restore(); 348 | } 349 | } 350 | } 351 | Clock.prototype.addDynamicBody = function(body) { 352 | this.dynamicBodies.push(body); 353 | } 354 | Clock.prototype.addStaticBody = function(body) { 355 | this.staticBodies.push(body); 356 | } 357 | Clock.prototype.curDate = function() { 358 | return new Date(this.baseTime + (Date.now() - this.baseDate)*this.speed); 359 | } 360 | Clock.prototype.seconds = function() { 361 | var date = this.curDate(); 362 | return date.getSeconds() + date.getMilliseconds()*1e-3; 363 | } 364 | Clock.prototype.reset = function() { 365 | this.staticBodies = []; 366 | this.dynamicBodies = []; 367 | 368 | this.addStaticBody(Line( 90, 407, 400, 397, 7)); 369 | this.addStaticBody(Line( 67, 397, 80, 405, 3)); 370 | this.addStaticBody(Line(-7, 383, 10, 405, 7)); 371 | this.addStaticBody(Line( 50, 377, 367, 365, 3)); 372 | this.addStaticBody(Line( 90, 363, 367, 365, 3)); 373 | this.addStaticBody(Line( 90, 363, 23, 250, 3)); 374 | this.addStaticBody(Line( 410, 360, 395, 395, 10)); 375 | this.addStaticBody(Line( 100, 104.5, 250, 123, 3)); 376 | this.addStaticBody(Line( 390, 145, 400, 145, 3)); 377 | this.addStaticBody(Line( 170, 175, 290, 167, 3)); 378 | this.addStaticBody(Line( 110, 197, 190, 207, 3)); 379 | this.addStaticBody(Line( 390, 237, 400, 237, 3)); 380 | this.addStaticBody(Line( 160, 260, 200, 256, 3)); 381 | this.addStaticBody(Line( 250, 246, 250, 246, 3)); 382 | this.addStaticBody(Line( 110, 336, 160, 335, 3)); 383 | this.addStaticBody(Line( 260, 324, 260, 324, 3)); 384 | this.addStaticBody(Line( 110, 280, 150, 285.5, 3)); 385 | this.addStaticBody(Line( 390, 320, 400, 320, 3)); 386 | this.addStaticBody(Line( 0, -10, 400, -10, 10)); 387 | this.addStaticBody(Line( 0, 410, 400, 410, 10)); 388 | this.addStaticBody(Line(-12, 0, -12, 410, 10)); 389 | this.addStaticBody(Line( 410, 0, 410, 400, 10)); 390 | 391 | this.addDynamicBody(Seesaw(260, 124, 390, 139, 3, 0.80, 1.0, 4.2)); 392 | this.addDynamicBody(Seesaw(200, 212, 390, 230, 3, 0.80, 1.0, 4.5)); 393 | this.addDynamicBody(Seesaw(163, 291, 390, 313, 3, 0.80, 1.0, 4.6)); 394 | 395 | var date = this.curDate(); 396 | var seesaw1 = date.getMinutes() % 6; 397 | var seesaw2 = Math.floor(date.getMinutes()/6); 398 | var seesaw3 = date.getHours() % 12; 399 | 400 | for (var i = 0; i < seesaw1; ++i) 401 | this.addDynamicBody(Sphere(365 - i*20, 122 - i*2, 10, 0.1)); 402 | for (var i = 0; i < seesaw2; ++i) 403 | this.addDynamicBody(Sphere(390 - i*20, 216 - i*2, 10, 0.1)); 404 | for (var i = 0; i < seesaw3; ++i) 405 | this.addDynamicBody(Sphere(390 - i*20, 300 - i*2, 10, 0.1)); 406 | 407 | var restCount = 5 - seesaw1 + 9 - seesaw2 + 11 - seesaw3 + 2; 408 | 409 | for (var i = 0; i < Math.min(restCount, 14); ++i) 410 | this.addDynamicBody(Sphere(80 + i*21, 390 - i, 10, 0.1)); 411 | for (var i = 0; i < restCount - 14; ++i) 412 | this.addDynamicBody(Sphere(373 - i*21, 350, 10, 0.1)); 413 | 414 | this.elevator = new Elevator(this, this.seconds()/60.0, 20, 50, 60, 330); 415 | } 416 | Clock.prototype.update = function() { 417 | var curTime = Date.now(); 418 | if (curTime - this.lastTime > 500) 419 | this.reset(); 420 | this.lastTime = curTime; 421 | 422 | this.elevator.setRatio(this.seconds()/60.0); 423 | 424 | for (var i = 0; i < this.dynamicBodies.length; ++i) 425 | this.dynamicBodies[i].update(); 426 | 427 | var collisionInfo = new CollisionInfo(); 428 | for (var iter = 0; iter < this.iterations; ++iter) { 429 | for (var i = 0; i < this.dynamicBodies.length; ++i) { 430 | for (var j = 0; j < this.staticBodies.length; ++j) { 431 | collisionInfo.reset(); 432 | var sign = this.dynamicBodies[i].detectCollision(this.staticBodies[j], collisionInfo); 433 | if (sign) 434 | this.dynamicBodies[i].applyImpulse(collisionInfo, sign, false); 435 | } 436 | } 437 | 438 | for (var i = 0; i < this.dynamicBodies.length; ++i) { 439 | for (var j = i + 1; j < this.dynamicBodies.length; ++j) { 440 | collisionInfo.reset(); 441 | var sign = this.dynamicBodies[i].detectCollision(this.dynamicBodies[j], collisionInfo); 442 | if (sign) { 443 | var mass1 = this.dynamicBodies[i].computeMass(collisionInfo); 444 | var mass2 = this.dynamicBodies[j].computeMass(collisionInfo); 445 | 446 | var ratio = mass1/(mass1 + mass2); 447 | 448 | this.dynamicBodies[i].applyImpulse(collisionInfo, sign*(1.0 - ratio), false); 449 | this.dynamicBodies[j].applyImpulse(collisionInfo, -sign*ratio, false); 450 | } 451 | } 452 | } 453 | } 454 | } 455 | Clock.prototype.draw = function() { 456 | var rect = this.canvas.getBoundingClientRect(); 457 | this.canvas.width = rect.width; 458 | this.canvas.height = rect.height; 459 | 460 | var scale = Math.min(rect.width, rect.height)/415.0; 461 | this.ctx.setTransform(1, 0, 0, 1, 0, 0); 462 | this.ctx.translate(rect.width/2 - 200.0*scale, rect.height/2 - 200.0*scale) 463 | this.ctx.scale(scale, scale); 464 | 465 | this.ctx.fillStyle = "#fff"; 466 | this.ctx.beginPath(); 467 | this.ctx.rect(0, 0, 400, 400); 468 | this.ctx.fill(); 469 | 470 | this.ctx.strokeStyle = "#ccc"; 471 | this.ctx.lineCap = "square"; 472 | this.ctx.lineWidth = 20; 473 | this.ctx.beginPath(); 474 | this.ctx.moveTo(-12, -10); 475 | this.ctx.lineTo(410, -10); 476 | this.ctx.lineTo(410, 410); 477 | this.ctx.lineTo(-12, 410); 478 | this.ctx.lineTo(-12, -10); 479 | this.ctx.stroke(); 480 | 481 | this.elevator.drawBackground(this.ctx, this.seconds()); 482 | 483 | for (var i = 0; i < this.dynamicBodies.length; ++i) 484 | this.dynamicBodies[i].draw(this.ctx); 485 | for (var i = 0; i < this.staticBodies.length; ++i) 486 | this.staticBodies[i].draw(this.ctx); 487 | 488 | this.elevator.drawForeground(this.ctx, this.curDate().getTime()*1e-3 % 10000.0); 489 | 490 | this.ctx.textAlign = "right"; 491 | this.fillStyle = "#000"; 492 | this.ctx.save(); 493 | this.ctx.translate(385, 150); 494 | this.ctx.rotate(6.5*Math.PI/180.0); 495 | for (var i = 0; i < 6; ++i) 496 | this.ctx.fillText((i + 1).toString(), -i*20 + (i ? 3 : 0), 0); 497 | this.ctx.restore(); 498 | this.ctx.save(); 499 | this.ctx.translate(385, 242); 500 | this.ctx.rotate(6.5*Math.PI/180.0); 501 | for (var i = 0; i < 10; ++i) 502 | this.ctx.fillText(((i + 1)*6).toString(), -i*20 + (i ? 7 : 0), 0); 503 | this.ctx.restore(); 504 | this.ctx.save(); 505 | this.ctx.translate(385, 325); 506 | this.ctx.rotate(6.5*Math.PI/180.0); 507 | for (var i = 0; i < 12; ++i) 508 | this.ctx.fillText((i + 1).toString(), -i*20 + (i ? 5 : 0), 0); 509 | this.ctx.restore(); 510 | } 511 | Clock.prototype.setTime = function(date) { 512 | this.baseTime = date.getTime(); 513 | this.baseDate = Date.now(); 514 | } 515 | Clock.prototype.setSpeed = function(speed) { 516 | this.setTime(this.curDate()); 517 | this.speed = speed; 518 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Physiclock | Benedikt Bitterli's Portfolio 6 | 7 | 8 | 9 | 10 | 36 | 37 | 38 |
39 | 40 |
41 | 47 | 48 | 49 | --------------------------------------------------------------------------------