- Author:
- Marcus van Houdt
- Twitter:
- @
- GitHub:
- Facebook:
- Google+:
- +
- Reddit:
- /r/
- Pouet:
- Website:
- Compo:
- classic
- Demo link:
- https://js1k.com/2010-first/demo/720
- Shortlink:
- https://js1k.com/720
- Blog post:
- please update here!
- Bytes:
- 1024
- Chars:
- 1024
- Submission
`_=Math;X=Y=Z=150;V=3;U=2;d=.1;v=_.sin,s=_.cos,m=66,n=250,t=g=16,w=document.getElementById("c"),z=_.PI/g,F=" u[m*s(h=_.PI/2-h*z)*s(q=q*z+rX,l*(p=m*s*v(q)k*m*vY,l*j-k*p+Z]} y{bb<4;)]=[n-(]-n(x=3007+180-])7-(p=@]-nA-n)A)/(x7-pA-j)/nn+(xA+p7)/j,j];j>.1&&o.push}X+=X<m|XU=-U):U;Y+=Y<m|YV=-V):V;Z+=W=Z<m?-W:W-1.5;l=s(a-=.04k=v(ar-=d;o=[];t=@t%69;ccFc@)eeFe@){c<g&&B+1B+1B-1B-1(c|1(e|1)&2?'red':'#fff'][i=c*=e*,0 i++!t-c-33]>>e,0;[D+g D + +e-32+t]>>c}o.sort( q-h}with(w.getContext('2d')){b=w.width=w.height=550;'#999';Rect(0,0,b,bc E o){(f=o[c])[4]||'#90C';begEPath(movDe E f)lE[e%4]D[e%4](()}}";for(b in C="F<32;EinD,fB,eA*k@++7*leTo(fy([[1]*.3[2]=0;(j=),)+strokeg,0 i+0,iu(ch[b(h)-9)*+b*g);fill>436?(d=-d,Style= ],[g,f(h,q){return],b&&'#30C'])for([0]function(b=1&[5495,5444,3444,5396,5492][".split(""))F=F.replace(RegExp(C[b][W=a=r=0],"g"),C[b].slice(1));setInterval(F,m)`

- Description
- A homage to the Amiga Boing Ball, inspired by http://www.youtube.com/watch?v=-ga41edXw3A . Original source included although already heavily adapted so that Google's closure minifier can have a go at it. Further hand edited for final byte savings. Thanks @aivopaas for some final byte savings that allowed for extra features at the cost of some performance. Seems to perform best in Safari, worst in Firefox. Version 1: Initial version Version 2: - smoothed ball - added camera pitch rotation - improved grid Version 3: - added perspective - removed pitch rotation, replaced with actual camera rotation - added bounce in x direction within x,y-plane Final Version: - fixed a bug - added JS1K scrolling text - added extra rotation of ball around x-axis
- Base64 encoded
`Xz1NYXRoO1g9WT1aPTE1MDtWPTM7VT0yO2Q9LjE7dj1fLnNpbixzPV8uY29zLG09NjYsbj0yNTAsdD1nPTE2LHc9ZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoImMiKSx6PV8uUEkvZyxGPSIDIHUHW20qcyhoPV8uUEkvMi1oKnopKnMocT1xKnorchdYLGwqKHA9bSpzESp2KHEpF2sqGW0qdhEXWSxsKmotaypwK1pdfQMgeRF7BWIaYjw0OykSXT1bbi0oGRJdGy1uFyh4PTMwMDcrMTgwLRJdBCk3HC0ocD0SQF0dLW5BLW4pQRwpLxkoeDctcEEtahwpL24YbisoeEErcDcpL2osal07aj4uMSYmby5wdXNoEX1YKz1YPG18WAxVPS1VKTpVO1krPVk8bXxZDFY9LVYpOlY7Wis9Vz1aPG0/LVc6Vy0xLjU7bD1zKGEtPS4wNA9rPXYoYQ9yLT1kO289W107dD1AdCU2OTsFYxpjRmNAKQVlGmVGZUApe2M8ZyYmHhNCGBMrMUIYEysxQi0xGBNCLTEYKGN8MRcoZXwxKSYyPydyZWQnOicjZmZmJ10PHltpPWMqCD1lKhUILDAJaSsIKxUhAnQtYy0zM10+PmUQLDAGOx5bFEQrZwkURAkUKwgJFCsCZS0zMit0XT4+YxAGfW8uc29ydCgDByBxBBstaAQbfQ93aXRoKHcuZ2V0Q29udGV4dCgnMmQnKSl7Yj13LndpZHRoPXcuaGVpZ2h0PTU1MDsOCycjOTk5JzsOUmVjdCgwLDAsYixiDwVjIEUgbyl7FgsOCyhmPW9bY10pWzRdfHwnIzkwQyc7YmVnRVBhdGgoD21vdh8EHUQEBA8FZSBFIGYpbEUfW2UlNF0dRFtlJTRdBA8WKA8OKCl9fSI7Zm9yKGIgaW4gQz0iRjwzMjsBRWluAUQsZgFCLGUBQSprAUArKwE3KmwBH2VUbyhmAR55KFsBHVsxXQEcKi4zARtbMl0BGj0wOwEZKGo9ARgpLAEXKSsBFnN0cm9rZQEVZywwCWkrARQwLGkBE3UoYwESaFtiAREoaCkBEC05KSoIK2IqZwEPKTsBDmZpbGwBDD40MzY/KGQ9LWQsAQtTdHlsZT0BCV0sWwEIZyxmAQcoaCxxKXtyZXR1cm4BBl0sYiYmJyMzMEMnXSkBBWZvcigBBFswXQEDZnVuY3Rpb24BAihiPTEmWzU0OTUsNTQ0NCwzNDQ0LDUzOTYsNTQ5Ml1bIi5zcGxpdCgiASIpKUY9Ri5yZXBsYWNlKFJlZ0V4cChDW2JdW1c9YT1yPTBdLCJnIiksQ1tiXS5zbGljZSgxKSk7c2V0SW50ZXJ2YWwoRixtKQ==`

- Original source
`// Compiled using Google's closure compiler. // Handedits made: // - setInterval with a single string expression (saves a function keyword, some curly braces etc.) // - removed extraneous spaces/new lines still left // - removed last semicolon // - replaced any 0.x with .x // - used @aivopaas' JS crusher on the expression, eval replaced by setInterval of course and removed unnecessary initializations // X, Y, Z are the centre of the ball // U, V and W are the velocity in x, y and z directions repsectively. // a is the yaw, r the rotation of the ball in theta direction and d the directional change in rotation // p is the pitch, and e is the directional change in pitch (not supported any longer) // counter causes JS1K to scroll _ = Math,X=Y=Z=150,V=3,W=a=r=0,d=.15,U=2,counter=15; // counter is really compiled as a parameter below function R(sin, cos, radius, dw, sixteen, canvas, fact, sinYaw, cosYaw, sphere, i, j, tmp, iGrid, jGrid, x, y, z) { // Performs a translation from spherical to cartesian coordinates, together with an origin shift and some rotations. function T(phi, theta) { // It gets rather unreadable, but the following "inline" intialization saves 4 bytes (2 variables (minified) and ; characters). // Theta and phi are in parts 1/16th parts of PI, thus the multiplication with fact = PI / 16. // Also performs a rotation in the y,z-plane by an angle of yaw, together with a rotation in theta direction (which makes the ball spin). // As we start, in spherical coords, at the top of the ball, we subtract from PI/2. return [radius * cos(phi = _.PI / 2 - phi * fact) * cos(theta = theta * fact + r) + X, cosYaw * (y = radius * cos(phi) * sin(theta)) + sinYaw * (z = radius * sin(phi)) + Y, cosYaw * z - sinYaw * y + Z]; } // Performs the camera translation given yaw and pitch, after first updating the camera's position which moves according to an ellipse (of angle yaw, conveniently). // Finally performs the shift needed to translate our Cartesian to "Canvas" coordinates. function cameraTranslateAndPush(poly) { // Pitch rotation values were hardcoded and both negative, this - sign has been factored in below to save the 2 bytes. To really get rid of the -, x was also negated. // reuse tmp variable, inline asignments to x, y and z, and a nasty post increment. for(tmp = 0; tmp < 4;) // Performs: lookup of x,y,z with camera origin translation (camera is being rotated through an ellipse in x,y-plane, then moved to origin). Camera is rotated to match yaw. // dw-, and dw+ are canvas translations, other use of dw is simply "convenient", the number fits, and avoids a "big" number in its place. // sinPitch is 1, so taken out completely, .3 is the cosPitch (both negated). // yaw rotation is negative, cos(-a) = cos(a), sin(-a) = -sin(a): negation factored through. // Pitch values are hardcoded, but really negative, so both negations factored through. // 1/z/250 is the perspective division which seems to work well enough and doesn't look too skewed I find. poly[tmp] = [ dw - ((z = poly[tmp][2] - dw) /* * sinPitch */ + (x = 300 * cosYaw + 180 - poly[tmp][0]) * cosYaw * .3 - (y = poly[tmp++][1] - dw * sinYaw - dw) * sinYaw * .3) / (z = (x * cosYaw /* * sinPitch */ - y * sinYaw /* * sinPitch */ - z * .3) / dw), // < NOTE tmp++ nasty increment saving 1 byte dw + (x * sinYaw + y * cosYaw) / z, z ]; // Draw if in front of camera only. z > .1 && sphere.push(poly); // Google closure minifier doesn't always seem to do the minification if(..)->&&, so do it here. } // Handle bounce off invisible "walls", changing rotation direction upon every bounce. X += X < radius | X > 436 ? (d = -d, U = -U) : U; Y += Y < radius | Y > 436 ? (d = -d, V = -V) : V; // Approximation of a Z bounce. Z += W = Z < radius ? -W : W - 1.5; // Update camera yaw which equals the angle of the elipse in which the camera moves. // Actual rotation is by -yaw, but this has been factored through. cosYaw = cos(a -= .04); sinYaw = sin(a); // Update camera pitch - can't fit in 1k. // p += p < 4 || p > 5 ? e = -e : e)); // Update ball rotation, which changes on every y-bounce. r -= d; // Create a model of our sphere. This went through some iterations, starting with Math.floor and then a right shift as an integer division by 2. // (floor(i/2)+floor(j/2)) % 2 became (i>>1)+(j>>1)&1 became 0|i/2 + (j>>1) & 1 became 2 & (i|1) + (j|1). Can I do better? sphere = []; counter = ++counter % (64 + 5); for(i = 0; i < 32; i++) for(j = 0; j < 32; j++) // Only draw over PI (divided in 16 sections) i < sixteen && cameraTranslateAndPush([T(i, j), T(i + 1, j), T(i + 1, j - 1), T(i, j - 1), (i|1) + (j|1) & 2 ? 'red' : '#fff']), // unfortunate operator precedence for or: parentheses needed // Piggyback on for loops for the sphere. // iGrid and jGrid are initialized "inline", saves a few bytes // Drawing the grid this way simplifies our later canvas painting greatly, and does not require a conditional fill. // The below array is 'crushed' by the jscrusher, so 2 appearances have little effect on size. cameraTranslateAndPush([[iGrid = i * sixteen, jGrid = j * sixteen, 0], [iGrid + sixteen, jGrid, 0], [iGrid + sixteen, jGrid + sixteen, 0], [iGrid + !(tmp = 1 & [5495,5444,3444,5396,5492][counter - i - 33] >> j - 9) * sixteen, jGrid + tmp * sixteen, 0], tmp && "#30C"]), // Draw the grid in the y,z plane, and draw JS1K text at the same time. // "ᕷᕄ൴ᔔᕴ".charCodeAt(j - 14) cameraTranslateAndPush([[0, iGrid, jGrid + sixteen], [0, iGrid, jGrid], [0, iGrid + sixteen, jGrid], [0, iGrid + (tmp = 1 & [5495,5444,3444,5396,5492][j - 32 + counter] >> i - 9) * sixteen, jGrid + tmp * sixteen], tmp && "#30C"]); // Painter's algorithm (or z-sort): sort on distance from camera, for brevity, pick first z coordinate. Which is ok anyway if the polies are small. // This ensures that objects that are further away are drawn first so will be hidden by anything in front of it. sphere.sort(function(a, b) { return b[0][2] - a[0][2] }); // Draw, starting with objects furthest away. with(canvas.getContext('2d')) // sorry for the with { // Store the width/height in order to clear the "screen". // Doing this in every "paint loop" doesn't seem problematic. tmp = canvas.width = canvas.height = 550; // Clear and colour background. 999 seems the closest gray in 3 rgb digits. fillStyle = '#999'; fillRect(0, 0, tmp, tmp); for(i in sphere) { // Colour is either defined as part of the model or defaults to the line colour. strokeStyle = fillStyle = (jGrid = sphere[i])[4] || "#90C"; beginPath(); // Actual canvas displacement calculations were moved to cameraTranslateAndPush, thus contained in model. moveTo(jGrid[0][1], jGrid[0][0]); for(j in jGrid) lineTo(jGrid[j % 4][1], jGrid[j % 4][0]); // uses mod to draw back to starting point stroke(), fill(); } } } // Initialize and repeat. Fact is set to PI / 16, making the ball consist of 7 coloured "squares" per PI. // 75 is the radius of the ball, 250 is canvas offset and radius of circle (ellipse really) in which camera moves. // #90C is the grid line colour. 550 the width of the canvas. 16 is distance between grid lines. setInterval('R(_.sin,_.cos,75,250,16,document.getElementById("c"),_.PI/16)',66)`