├── img └── numbers.ai ├── wip-screenshots ├── webgl-001.png ├── webgl-002.gif ├── webgl-003.png ├── webgl-004.gif ├── webgl-005.gif ├── webgl-006.gif ├── webgl-006.png ├── webgl-007.png ├── webgl-008.png ├── webgl-009.gif ├── webgl-010.gif ├── webgl-011.png └── webgl-012.gif ├── index.html ├── README.md ├── LICENSE └── ready.js /img/numbers.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carloscabo/a-million-times-webgl-demo/HEAD/img/numbers.ai -------------------------------------------------------------------------------- /wip-screenshots/webgl-001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carloscabo/a-million-times-webgl-demo/HEAD/wip-screenshots/webgl-001.png -------------------------------------------------------------------------------- /wip-screenshots/webgl-002.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carloscabo/a-million-times-webgl-demo/HEAD/wip-screenshots/webgl-002.gif -------------------------------------------------------------------------------- /wip-screenshots/webgl-003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carloscabo/a-million-times-webgl-demo/HEAD/wip-screenshots/webgl-003.png -------------------------------------------------------------------------------- /wip-screenshots/webgl-004.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carloscabo/a-million-times-webgl-demo/HEAD/wip-screenshots/webgl-004.gif -------------------------------------------------------------------------------- /wip-screenshots/webgl-005.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carloscabo/a-million-times-webgl-demo/HEAD/wip-screenshots/webgl-005.gif -------------------------------------------------------------------------------- /wip-screenshots/webgl-006.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carloscabo/a-million-times-webgl-demo/HEAD/wip-screenshots/webgl-006.gif -------------------------------------------------------------------------------- /wip-screenshots/webgl-006.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carloscabo/a-million-times-webgl-demo/HEAD/wip-screenshots/webgl-006.png -------------------------------------------------------------------------------- /wip-screenshots/webgl-007.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carloscabo/a-million-times-webgl-demo/HEAD/wip-screenshots/webgl-007.png -------------------------------------------------------------------------------- /wip-screenshots/webgl-008.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carloscabo/a-million-times-webgl-demo/HEAD/wip-screenshots/webgl-008.png -------------------------------------------------------------------------------- /wip-screenshots/webgl-009.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carloscabo/a-million-times-webgl-demo/HEAD/wip-screenshots/webgl-009.gif -------------------------------------------------------------------------------- /wip-screenshots/webgl-010.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carloscabo/a-million-times-webgl-demo/HEAD/wip-screenshots/webgl-010.gif -------------------------------------------------------------------------------- /wip-screenshots/webgl-011.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carloscabo/a-million-times-webgl-demo/HEAD/wip-screenshots/webgl-011.png -------------------------------------------------------------------------------- /wip-screenshots/webgl-012.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carloscabo/a-million-times-webgl-demo/HEAD/wip-screenshots/webgl-012.gif -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Document 6 | 7 | 8 | 9 | 10 | 11 | 12 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # a-million-times-webgl-demo 2 | 3 | See it working here [https://a-million-times.netlify.app/](https://a-million-times.netlify.app/) 4 | 5 | ![a-million-times-webgl-demo](https://github.com/carloscabo/a-million-times-webgl-demo/raw/master/wip-screenshots/webgl-012.gif) 6 | 7 | ## Requires 8 | 9 | - `three.js` r75 10 | - `TweenJS` 11 | 12 | ## Disclaimer 13 | Yep, it seems 2D instead 3D, I'll try with something more 3Dish next time ;) 14 | 15 | This is only a proof of concept, expect fast-and-dirty code and problably some bugs / glitches. 16 | Enjoy ;) 17 | 18 | ## Original idea 19 | 20 | Fast WebGL test concept inspired by the **"A million Times"** kinetic installation by _Humans since 1982_ 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Carlos Cabo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ready.js: -------------------------------------------------------------------------------- 1 | var 2 | scene, 3 | camera, 4 | renderer, 5 | materials = {}, 6 | digits = [], 7 | π = Math.PI, 8 | delay_between_cols = 250, 9 | clock_anim_duration = 2500, 10 | timeout_between_animations = 7500, 11 | rotation = 0; 12 | 13 | $(document).ready(function(){ 14 | //La magia aquí 15 | scene = new THREE.Scene(); 16 | 17 | renderer = new THREE.WebGLRenderer({ 18 | antialias:true 19 | }); 20 | renderer.setSize( window.innerWidth, window.innerHeight ); 21 | renderer.setClearColor( new THREE.Color( 0x323232 ), 1); 22 | document.body.appendChild( renderer.domElement ); 23 | 24 | materials.grey = new THREE.MeshBasicMaterial({ 25 | color: 0x666666, 26 | side: THREE.DoubleSide 27 | }); 28 | materials.white = new THREE.MeshBasicMaterial({ 29 | color: 0xffffff, 30 | side: THREE.DoubleSide 31 | }); 32 | materials.red = new THREE.MeshBasicMaterial({ 33 | color: 0x990000, 34 | side: THREE.DoubleSide 35 | }); 36 | 37 | // var geometry = new THREE.BoxGeometry( 1, 1, 1 ); 38 | // cube = new THREE.Mesh( geometry, material ); 39 | // scene.add( cube ); 40 | 41 | var 42 | digits_num = 4, 43 | rows = 5, 44 | cols = 3, 45 | radius = 1, 46 | stroke = 0.1, 47 | xy_offset = ( radius * 2 ) + ( stroke * 2 ); 48 | 49 | for (var d = 0; d < digits_num; d++) { 50 | var digit = []; 51 | for (var i = 0; i < rows; i++) { 52 | var row = []; 53 | for (var j = 0; j < cols; j++) { 54 | row.push( new Clock3D( radius, stroke ) ); 55 | row[ row.length -1 ].position.x = ( d * xy_offset * cols ) + ( j * xy_offset ); 56 | row[ row.length -1 ].position.y = i * xy_offset * -1; 57 | } 58 | digit.push( row ); 59 | } 60 | digits.push( digit ); 61 | } 62 | 63 | var clock_w = (xy_offset * cols * digits_num) + xy_offset; 64 | var fov = 5; 65 | // Adjust camera to fit clock in screen width 66 | var dist = clock_w / 2 / (window.innerWidth / window.innerHeight) / Math.tan(Math.PI * fov / 360); 67 | console.log( dist ); 68 | camera = new THREE.PerspectiveCamera( fov, window.innerWidth / window.innerHeight, 0.1, 1000 ); 69 | camera.position.x = xy_offset * cols * digits_num / 2 - xy_offset / 2; 70 | camera.position.y = ( (rows - 1) * xy_offset ) / 2 * -1; 71 | camera.position.z = dist; 72 | 73 | // Preprocess chars from human readeable to radians 74 | convertCharsToRad(); 75 | 76 | // Initial clock setting 77 | updateSeconds(); 78 | setCurrentTimeAnalog(); 79 | 80 | setInterval( function() { 81 | updateSeconds(); 82 | }, 500 ); 83 | 84 | render(); 85 | // renderer.render( scene, camera ); 86 | 87 | }); 88 | 89 | // Scene render loop 90 | function render() { 91 | requestAnimationFrame( render ); 92 | renderer.render( scene, camera ); 93 | } 94 | 95 | // Clock 3D Object constructor 96 | function Clock3D ( rad, stk ) { 97 | 98 | var 99 | g, // geometry 100 | clock = new THREE.Object3D(); 101 | 102 | // Clock sphere 103 | g = new THREE.RingGeometry( rad, rad + (stk/2), 64 ); 104 | sp = new THREE.Mesh( g, materials.grey ); 105 | clock.add( sp ); 106 | 107 | // sec 108 | g = new THREE.PlaneGeometry( rad - (stk*1), 0.025, 1 ); 109 | g.translate( (rad - (stk*1)) / 2, 0, -0.1 ); 110 | sec = new THREE.Mesh( g, materials.red ); 111 | sec.geometry.dynamic = true; 112 | sec.rotateZ( π / 2 ); 113 | clock.add( sec ); 114 | 115 | // Min 116 | g = new THREE.PlaneGeometry( rad - (stk*1.5), stk, 1 ); 117 | g.translate( (rad - (stk*1.5)) / 2, 0, 0 ); 118 | min = new THREE.Mesh( g, materials.white ); 119 | min.rotateZ( π / 2 ); 120 | clock.add( min ); 121 | 122 | // hour 123 | g = new THREE.PlaneGeometry( rad - (stk*1.5), stk, 1 ); 124 | g.translate( (rad - (stk*1.5)) / 2, 0, 0 ); 125 | hour = new THREE.Mesh( g, materials.white ); 126 | hour.rotateZ( π / 2 ); 127 | clock.add( hour ); 128 | 129 | // Clock axis 130 | g = new THREE.CircleGeometry( stk/2, 32 ); 131 | g.translate( 0, 0, 0.1 ); 132 | ax = new THREE.Mesh( g, materials.white ); 133 | clock.add( ax ); 134 | 135 | scene.add( clock ); 136 | return clock; 137 | } 138 | 139 | // Updates clock's seconds to current time 140 | function updateSeconds() { 141 | var 142 | d = new Date(), 143 | s = d.getSeconds(); 144 | // console.log(s); 145 | for (var d = 0; d < digits.length; d++) { 146 | for (var i = 0; i < digits[d].length; i++) { 147 | for (var j = 0; j < digits[d][i].length; j++) { 148 | digits[d][i][j].children[1].rotation.z = timeToRad ( s, 60 ); 149 | } 150 | } 151 | } 152 | } 153 | 154 | // Updates clock's matrix to current time in digital format 155 | function setCurrentTimeDigital() { 156 | var 157 | current = getCurrentHourCharArray(); 158 | for (var d = 0; d < digits.length; d++) { 159 | for (var i = 0; i < digits[d].length; i++) { 160 | for (var j = 0; j < digits[d][i].length; j++) { 161 | // digits[d][i][j].children[2].rotation.z = chars[ current[d] ][i][j][0]; 162 | // digits[d][i][j].children[3].rotation.z = chars[ current[d] ][i][j][1]; 163 | var clock_delay = 1000 + ( ( d * (digits[d].length - 1) + j - i ) * delay_between_cols ); 164 | createjs.Tween.get( digits[d][i][j].children[2].rotation ).wait( clock_delay ).to({ z: chars[ current[d+''] ][i][j][0] }, clock_anim_duration, createjs.Ease.quintInOut); 165 | createjs.Tween.get( digits[d][i][j].children[3].rotation ).wait( clock_delay ).to({ z: chars[ current[d+''] ][i][j][1] }, clock_anim_duration, createjs.Ease.quintInOut); 166 | } 167 | } 168 | } 169 | setTimeout( function(){ 170 | setCurrentTimeAnalog(); 171 | }, timeout_between_animations ); 172 | } 173 | 174 | // Updates clock's matrix to current time in analog format 175 | function setCurrentTimeAnalog() { 176 | var 177 | d = new Date(), 178 | m = d.getMinutes(); 179 | h = d.getHours() % 12 || 0; 180 | for (var d = 0; d < digits.length; d++) { 181 | for (var i = 0; i < digits[d].length; i++) { 182 | for (var j = 0; j < digits[d][i].length; j++) { 183 | 184 | var clock_delay = 1000 + ( ( d * (digits[d].length - 1) + j - i ) * delay_between_cols ); 185 | 186 | createjs.Tween.get( digits[d][i][j].children[2].rotation ).wait( clock_delay ).to({ z: timeToRad ( m, 60 ) }, clock_anim_duration, createjs.Ease.quintInOut); 187 | createjs.Tween.get( digits[d][i][j].children[3].rotation ).wait( clock_delay ).to({ z: timeToRad ( h, 12 ) }, clock_anim_duration, createjs.Ease.quintInOut); 188 | // Min 189 | //digits[d][i][j].children[2].rotation.z = timeToRad ( m, 60 ); 190 | // Hour 191 | //digits[d][i][j].children[3].rotation.z = timeToRad ( h, 12 ); 192 | } 193 | } 194 | } 195 | setTimeout( function(){ 196 | setCurrentTimeDigital(); 197 | }, timeout_between_animations ); 198 | } 199 | 200 | // Converts a time value to its radian equivalency 201 | function timeToRad ( value, base ) { 202 | return ( value / base ) * 2 * π * -1 + ( π / 2); 203 | } 204 | 205 | // Converts current time to an array of chars 206 | // 13:45 -> ['1','3','4','5'] 207 | function getCurrentHourCharArray () { 208 | var 209 | d = new Date(), 210 | h = d.getHours(), 211 | m = d.getMinutes(); 212 | return ('' + zeroPad(h) + zeroPad(m) ).split(''); 213 | } 214 | 215 | // Pad an string with zeros 216 | function zeroPad(i) { 217 | return (i < 10) ? '0' + i : i ; 218 | } 219 | 220 | // Pre-process the char values below to radians, much easier to rotate 221 | // the clock's hands 222 | function convertCharsToRad () { 223 | $.each(chars, function(index, char) { 224 | for (var i = 0; i < char.length; i++) { 225 | for (var j = 0; j < char[i].length; j++) { 226 | char[i][j][0] = timeToRad ( char[i][j][0], 12 ); 227 | char[i][j][1] = timeToRad ( char[i][j][1], 12 ); 228 | } 229 | } 230 | }); 231 | } 232 | 233 | // The digital characters are defined using the hour positions of the 234 | // clocks hands 0 - 12 235 | var chars = { 236 | '0': [ 237 | [ [3,6], [3,9], [6,9] ], 238 | [ [0,6], [6,6], [0,6] ], 239 | [ [0,6], [0,6], [0,6] ], 240 | [ [0,6], [0,0], [0,6] ], 241 | [ [0,3], [3,9], [0,9] ] 242 | ], 243 | '1': [ 244 | [ [3,6], [3,9], [6,9] ], 245 | [ [0,3], [6,9], [0,6] ], 246 | [ [7.5,7.5], [0,6], [0,6] ], 247 | [ [7.5,7.5], [0,6], [0,6] ], 248 | [ [7.5,7.5], [0,3], [0,9] ] 249 | ], 250 | '2': [ 251 | [ [3,6], [3,9], [6,9] ], 252 | [ [0,3], [6,9], [0,6] ], 253 | [ [3,6], [0,9], [0,9] ], 254 | [ [0,6], [0,3], [6,9] ], 255 | [ [0,3], [3,9], [0,9] ] 256 | ], 257 | '3': [ 258 | [ [3,6], [3,9], [6,9] ], 259 | [ [0,3], [6,9], [0,6] ], 260 | [ [3,3], [6,9], [0,6] ], 261 | [ [3,6], [0,9], [0,6] ], 262 | [ [0,3], [3,9], [0,9] ] 263 | ], 264 | '4': [ 265 | [ [3,6], [6,9], [6,9] ], 266 | [ [0,6], [0,6], [0,6] ], 267 | [ [0,6], [0,3], [0,6] ], 268 | [ [0,3], [6,9], [0,6] ], 269 | [ [7.5,7.5], [0,3], [0,9] ] 270 | ], 271 | '5': [ 272 | [ [3,6], [3,9], [6,9] ], 273 | [ [0,6], [3,6], [0,9] ], 274 | [ [0,3], [0,3], [6,9] ], 275 | [ [3,6], [0,9], [0,6] ], 276 | [ [0,3], [3,9], [0,9] ] 277 | ], 278 | '6': [ 279 | [ [3,6], [3,9], [6,9] ], 280 | [ [0,6], [3,6], [0,9] ], 281 | [ [0,6], [0,3], [6,9] ], 282 | [ [0,6], [0,6], [0,6] ], 283 | [ [0,3], [3,9], [0,9] ] 284 | ], 285 | '7': [ 286 | [ [3,6], [3,9], [6,9] ], 287 | [ [0,3], [6,9], [0,6] ], 288 | [ [7.5,7.5], [6,9], [0,6] ], 289 | [ [7.5,7.5], [0,6], [0,6] ], 290 | [ [7.5,7.5], [0,3], [0,9] ] 291 | ], 292 | '8': [ 293 | [ [3,6], [3,9], [6,9] ], 294 | [ [0,6], [6,9], [0,6] ], 295 | [ [0,6], [6,9], [0,6] ], 296 | [ [0,6], [0,0], [0,6] ], 297 | [ [0,3], [3,9], [0,9] ] 298 | ], 299 | '9': [ 300 | [ [3,6], [3,9], [6,9] ], 301 | [ [0,6], [6,9], [0,6] ], 302 | [ [0,3], [6,9], [0,6] ], 303 | [ [7.5,7.5], [0,6], [0,6] ], 304 | [ [7.5,7.5], [0,3], [0,9] ] 305 | ], 306 | 'L': [ 307 | [ [3,6], [6,9], [1.5,1.5] ], 308 | [ [0,6], [0,6], [1.5,1.5] ], 309 | [ [0,6], [0,6], [1.5,1.5] ], 310 | [ [0,6], [0,3], [6,9] ], 311 | [ [0,3], [3,9], [0,9] ] 312 | ], 313 | 'V': [ 314 | [ [3,6], [3,6], [6,9] ], 315 | [ [0,6], [0,6], [0,6] ], 316 | [ [0,6], [0,6], [0,6] ], 317 | [ [0,4.5], [0,0], [0,7.5] ], 318 | [ [7.5,7.5], [1.5,10.5], [7.5,7.5] ] 319 | ], 320 | 'E': [ 321 | [ [3,6], [3,9], [6,9] ], 322 | [ [0,6], [3,6], [0,9] ], 323 | [ [0,6], [3,6], [9,9] ], 324 | [ [0,6], [0,3], [6,9] ], 325 | [ [0,3], [3,9], [0,9] ] 326 | ], 327 | }; 328 | --------------------------------------------------------------------------------