Sources
Main html file: | dhtml/fractal-3D-spheres.html |
ge1doot mini library: | /library/ge1doot.js |
CSS
html {
overflow: hidden;
-ms-touch-action: none;
-ms-content-zooming: none;
}
body {
position: absolute;
margin: 0;
padding: 0;
background: #000;
width: 100%;
height: 100%;
}
#screen {
position: absolute;
width: 100%;
height: 100%;
}
#sphere {
visibility: hidden;
}
HTML
<canvas id="screen">HTML5 CANVAS Fractal Spheres demo</canvas>
<img id="sphere" src="../images/sphere-hr.png" alt="">
JS
/* =======================================================
* ---- HTML5 CANVAS 3D spheres ----
* script: Gerard Ferrandez - 3 February 2013
* Released under the MIT license
* http://www.dhteumeuleu.com/LICENSE.html
* ======================================================= */
"use strict";
(function () {
var scr, ctx, pointer, over, img, spheres = [], divide = [];
// ==== Easing function ====
var Ease = function () {
this.target = 0;
this.position = 0;
};
Ease.prototype.move = function (speed) {
this.position += (this.target - this.position) * (speed || 0.05);
};
// ==== angles ====
var angle = {
x: new Ease(),
y: new Ease()
};
// ==== sphere constructor ====
var Sphere = function (x, y, z, w, l, root) {
this.x = x;
this.y = y;
this.z = z;
this.w = w;
this.xp = 0;
this.yp = 0;
this.zi = 0;
this.root = root;
this.level = l;
this.visible = true;
// ---- lighten image ----
this.img = document.createElement('canvas');
this.img.width = img.width / l;
this.img.height = img.height / l;
var ict = this.img.getContext('2d');
ict.drawImage(img, 0, 0, this.img.width, this.img.height);
var imageData = ict.getImageData(0, 0, this.img.width, this.img.height);
var d = imageData.data;
var dist = (x * x + y * y + z * z) / 100000;
for (var i = 0, l = d.length; i < l; i += 4) {
d[i] *= dist;
d[i+1] *= dist;
d[i+2] *= dist;
}
ict.putImageData(imageData, 0, 0);
}
// ==== projection 2D to 3D ====
Sphere.prototype.projection = function () {
this.visible = true;
// ---- rotation ----
var xy = angle.cx * this.y - angle.sx * this.z;
var xz = angle.sx * this.y + angle.cx * this.z;
var yz = angle.cy * xz - angle.sy * this.x;
var yx = angle.sy * xz + angle.cy * this.x;
// ---- projection ----
this.pers = 300 / (600 + yz);
this.xp = (scr.width * 0.5) + yx * this.pers;
this.yp = (scr.height * 0.5) + xy * this.pers;
this.zi = yz;
}
// ==== draw sphere ====
Sphere.prototype.draw = function () {
var w = this.pers * this.w;
// ---- pointer over detection ----
ctx.beginPath();
ctx.arc(this.xp, this.yp, w, 0, Math.PI * 2, false);
if (
ctx.isPointInPath(
pointer.X,
pointer.Y
)
) over = this;
// ---- draw sphere ----
ctx.drawImage(this.img, this.xp - w, this.yp - w, w * 2, w * 2);
}
// ==== divide sphere ====
var divideSphere = function (parent, x, y, z, w) {
var d = w * 0.5;
if (parent) {
var level = parent.level + 0.5;
var root = (parent.level === 1) ? parent : parent.root;
// ---- delete parent ----
var i = 0, o;
while ( o = spheres[i++] ) {
if (o === parent) {
spheres.splice(--i, 1);
break;
}
}
} else {
var level = 1;
var root = false;
}
// ---- create children spheres ----
var dx = [ 1,-1, 1,-1, 1,-1, 1,-1];
var dy = [-1,-1, 1, 1,-1,-1, 1, 1];
var dz = [-1,-1,-1,-1, 1, 1, 1, 1];
for (var i = 0; i < 8; i++) {
spheres.push(
new Sphere(
x + d * dx[i],
y - d * dy[i],
z - d * dz[i],
d, level, root
)
);
}
}
// ==== init script ====
var init = function () {
// ---- screen ----
scr = new ge1doot.Screen({
container: "screen"
});
ctx = scr.ctx;
// ---- pointer events ----
pointer = new ge1doot.Pointer({
tap: function () {
if (over) {
if (over.level > 1 && Math.random() > 0.97) {
// ---- 3% chance to destroy a bloc ----
var root = over.root;
var i = 0, o;
while ( o = spheres[i++] ) {
if (o.root == root) {
spheres.splice(--i, 1);
}
}
spheres.push(root);
} else {
// ---- push division ----
divide.push(over);
}
}
},
move: function () {
angle.x.target = (pointer.Yi - (scr.height * 0.5)) * .008;
angle.y.target = (pointer.Xi - (scr.width * 0.5)) * .008;
}
});
// ---- original png image ----
img = document.getElementById("sphere");
// ---- engine start ----
divideSphere(false, 0, 0, 0, 300);
run();
}
// ======== main loop ========
var run = function () {
ctx.clearRect(0, 0, scr.width, scr.height);
// ---- camera ----
angle.x.move();
angle.y.move();
angle.cx = Math.cos(angle.x.position);
angle.sx = Math.sin(angle.x.position);
angle.cy = Math.cos(angle.y.position);
angle.sy = Math.sin(angle.y.position);
// ---- 3D to 2D projection ----
var i = 0, o;
while ( o = spheres[i++] ) o.projection();
// ---- zIndex ----
spheres.sort(function (p0, p1) {
return p1.zi - p0.zi;
});
// ---- drawing ----
over = false;
i = 0;
while ( o = spheres[i++] ) o.draw();
// ---- divide ----
if (divide.length) {
var o = divide.shift();
divideSphere(o, o.x, o.y, o.z, o.w);
}
// ---- cursor ----
if (over) {
scr.setCursor("pointer");
} else {
scr.setCursor("default");
}
// ---- animation loop ----
requestAnimFrame(run);
}
return {
// ---- onload event ----
load : function () {
window.addEventListener('load', function () {
init();
}, false);
}
}
})().load();