Sources
Main html file: | dhtml/cube-rotate-3D.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%;
}
#canvas {
position: absolute;
width: 100%;
height: 100%;
background: #000;
cursor:default;
-webkit-user-select:none;
-moz-user-select:none;
-ms-user-select: none;
}
#info {
position: absolute;
text-align: left;
bottom: 0px;
right: 0px;
width: 60px;
height: 120px;
margin-top:-60px;
color: #666;
font-size: 11px;
font-family: Segoe UI, Verdana, Arial, Sans-Serif;
-webkit-user-select:none;
-moz-user-select:none;
-ms-user-select: none;
padding:0;
}
#info .background {
position: absolute;
width: 100%;
height: 100%;
background: #000;
opacity: 0.3;
}
#info .content {
position: absolute;
padding: 3px;
width: 100%;
height: 100%;
}
#info .w {
color: #fff;
}
#info hr {
width: 90%;
border: none;
background-color: #666;
height: 1px;
}
#info h1 {
color: #fff;
text-align: center;
}
#info input[type="button"] {
font-size:11px;
background:#666;
color:#FFF;
border:none;
}
.search {
background:#0065CB !important;
}
HTML
<canvas id="canvas">HTML5 CANVAS IS REQUIRED</canvas>
<div id="info">
<div class="background"></div>
<div class="content">
<input type="checkbox" id="white"><label for="white"> WB</label><br>
<input type="checkbox" id="alpha"><label for="alpha"> TR</label><br>
<input type="checkbox" id="autor"><label for="autor"> AR</label><br>
<input type="checkbox" id="destroy"><label for="destroy"> DC</label><br><br>
<input type="button" value="RESET" id="reset"></input>
</div>
</div>
JS
/*
* ======================================================
* CANVAS 3D experiment - 3D cubes HTML5 engine
* http://www.dhteumeuleu.com/fascinating
* Author Gerard Ferrandez - 2 Jan 2012
* ---------------------------------------------------
* Released under the MIT license
* http://www.dhteumeuleu.com/LICENSE.html
* Last updated: 29 Nov 2012
* ======================================================
*/
"use strict";
(function () {
// ======== private vars ========
var scr, ctx, pointer, cubes, faces, cz = 0;
var white, alpha, ncube, faceOver, lastOver;
var cosY, sinY, cosX, sinX, cosZ, sinZ, minZ, angleY = 0, angleX = 0, angleZ = 0;
var bkgColor1 = "rgba(0,0,0,0.1)";
var bkgColor2 = "rgba(32,32,32,1)";
var autorotate = false, destroy = false;
// ---- fov ----
var fl = 250;
var zoom = 0;
// ======== vertex constructor ========
var Point = function (parent, xyz, project) {
this.project = project;
this.xo = xyz[0];
this.yo = xyz[1];
this.zo = xyz[2];
this.cube = parent;
};
Point.prototype.projection = function () {
// ---- 3D rotation ----
var u = sinZ * this.yo + cosZ * this.xo;
var t = cosZ * this.yo - sinZ * this.xo;
var s = cosY * this.zo + sinY * u;
this.x = cosY * u - sinY * this.zo;
this.y = sinX * s + cosX * t;
this.z = cosX * s - sinX * t;
if (this.project) {
// ---- point visible ----
if (this.z < minZ) minZ = this.z;
this.visible = (zoom + this.z > 0);
// ---- 3D to 2D projection ----
this.X = (scr.width * 0.5) + this.x * (fl / (this.z + zoom));
this.Y = (scr.height * 0.5) + this.y * (fl / (this.z + zoom));
}
};
// ======= polygon constructor ========
var Face = function (cube, index, normalVector) {
// ---- parent cube ----
this.cube = cube;
// ---- coordinates ----
this.p0 = cube.points[index[0]];
this.p1 = cube.points[index[1]];
this.p2 = cube.points[index[2]];
this.p3 = cube.points[index[3]];
// ---- normal vector ----
this.normal = new Point(this, normalVector, false)
};
Face.prototype.faceVisible = function () {
// ---- points visible ----
if (this.p0.visible && this.p1.visible && this.p2.visible && this.p3.visible) {
// ---- back face culling ----
if (
(this.p1.Y - this.p0.Y) / (this.p1.X - this.p0.X) < (this.p2.Y - this.p0.Y) / (this.p2.X - this.p0.X)
^ this.p0.X < this.p1.X == this.p0.X > this.p2.X
) {
// ---- face visible ----
this.visible = true;
return true;
}
}
// ---- face hidden ----
this.visible = false;
this.distance = -99999;
return false;
};
Face.prototype.distanceToCamera = function () {
// ---- distance to camera ----
var dx = (this.p0.x + this.p1.x + this.p2.x + this.p3.x ) * 0.25;
var dy = (this.p0.y + this.p1.y + this.p2.y + this.p3.y ) * 0.25;
var dz = (zoom + fl) + (this.p0.z + this.p1.z + this.p2.z + this.p3.z ) * 0.25;
this.distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
};
Face.prototype.draw = function () {
// ---- shape face ----
ctx.beginPath();
ctx.moveTo(this.p0.X, this.p0.Y);
ctx.lineTo(this.p1.X, this.p1.Y);
ctx.lineTo(this.p2.X, this.p2.Y);
ctx.lineTo(this.p3.X, this.p3.Y);
ctx.closePath();
// ---- detection pointer inside ----
if (ctx.isPointInPath(pointer.X, pointer.Y)) lastOver = this;
// ---- light ----
if (this === faceOver) {
var r = 256;
var g = 0;
var b = 0;
} else {
// ---- flat (lambert) shading ----
this.normal.projection();
var light = (
white ?
this.normal.y + this.normal.z * 0.5 :
this.normal.z
) * 256;
var r = g = b = light;
}
// ---- fill ----
ctx.fillStyle = "rgba(" +
Math.round(r) + "," +
Math.round(g) + "," +
Math.round(b) + "," + this.cube.alpha + ")";
ctx.fill();
};
// ======== Cube constructor ========
var Cube = function(parent, nx, ny, nz, x, y, z, w) {
if (parent) {
// ---- translate parent points ----
this.w = parent.w;
this.points = [];
var i = 0, p;
while (p = parent.points[i++]) {
this.points.push(
new Point(
parent,
[p.xo + nx, p.yo + ny, p.zo + nz],
true
)
);
}
} else {
// ---- create points ----
this.w = w;
this.points = [];
var p = [
[x-w, y-w, z-w],
[x+w, y-w, z-w],
[x+w, y+w, z-w],
[x-w, y+w, z-w],
[x-w, y-w, z+w],
[x+w, y-w, z+w],
[x+w, y+w, z+w],
[x-w, y+w, z+w]
];
for (var i in p) this.points.push(
new Point(this, p[i], true)
);
}
// ---- faces coordinates ----
var f = [
[0,1,2,3],
[0,4,5,1],
[3,2,6,7],
[0,3,7,4],
[1,5,6,2],
[5,4,7,6]
];
// ---- faces normals ----
var nv = [
[0,0,1],
[0,1,0],
[0,-1,0],
[1,0,0],
[-1,0,0],
[0,0,-1]
];
// ---- cube transparency ----
this.alpha = alpha ? 0.5 : 1;
// ---- push faces ----
for (var i in f) {
faces.push(
new Face(this, f[i], nv[i])
);
}
ncube++;
};
var reset = function () {
// ---- create first cube ----
cubes = [];
faces = [];
ncube = 0;
cubes.push(
new Cube(false,0,0,0,0,0,0,50)
);
};
var click = function () {
// ---- click cube ----
if (faceOver) {
if (destroy) {
if (ncube > 1) {
var c = faceOver.cube;
faceOver.clicked = false;
// ---- destroy faces ----
var i = 0, f;
while ( f = faces[i++] ) {
if (f.cube == c) {
faces.splice(--i, 1);
}
}
// ---- destroy cube ----
var i = 0, o;
while ( o = cubes[i++] ) {
if (o == c) {
cubes.splice(--i, 1);
ncube--;
break;
}
}
}
} else {
if (!faceOver.clicked) {
// ---- create new cube ----
faceOver.clicked = true;
var w = -2.25 * faceOver.cube.w;
cubes.push(
new Cube(
faceOver.cube,
w * faceOver.normal.xo,
w * faceOver.normal.yo,
w * faceOver.normal.zo
)
);
}
}
}
};
var init = function () {
// ---- init script ----
scr = new ge1doot.Screen({
container: "canvas"
});
ctx = scr.ctx;
pointer = new ge1doot.Pointer({
tap: click,
wheel: function () {
// ---- Z axis rotation
cz += pointer.wheelDelta;
}
});
pointer.Xi = 50;
pointer.Yi = 50;
// ---- some options ----
document.getElementById("white").onchange = function () {
white = this.checked;
if (white) {
bkgColor1 = "rgba(256,256,256,0.1)";
bkgColor2 = "rgba(192,192,192,1)";
} else {
bkgColor1 = "rgba(0,0,0,0.1)";
bkgColor2 = "rgba(32,32,32,1)";
}
}
document.getElementById("alpha").onchange = function () {
alpha = this.checked;
}
document.getElementById("autor").onchange = function () {
autorotate = this.checked;
}
document.getElementById("destroy").onchange = function () {
destroy = this.checked;
}
document.getElementById("reset").onclick = function () {
reset();
}
// ---- engine start ----
reset();
run();
}
// ======== main loop ========
var run = function () {
// ---- screen background ----
ctx.fillStyle = bkgColor1;
ctx.fillRect(0, Math.floor(scr.height * 0.15), scr.width, Math.ceil(scr.height * 0.7));
ctx.fillStyle = bkgColor2;
ctx.fillRect(0, 0, scr.width, Math.ceil(scr.height * 0.15));
ctx.fillStyle = bkgColor2;
ctx.fillRect(0, Math.floor(scr.height * 0.85), scr.width, Math.ceil(scr.height * 0.15));
// ---- easing rotations ----
angleX += ((pointer.Yi - angleX) * 0.05);
angleY += ((pointer.Xi - angleY) * 0.05);
angleZ += ((cz - angleZ) * 0.05);
if (autorotate) cz += 1;
// ---- pre-calculating trigo ----
cosY = Math.cos(angleY * 0.01);
sinY = Math.sin(angleY * 0.01);
cosX = Math.cos(angleX * 0.01);
sinX = Math.sin(angleX * 0.01);
cosZ = Math.cos(angleZ * 0.01);
sinZ = Math.sin(angleZ * 0.01);
// ---- points projection ----
minZ = 0;
var i = 0, c;
while ( c = cubes[i++] ) {
var j = 0, p;
while ( p = c.points[j++] ) {
p.projection();
}
}
// ---- adapt zoom ----
var d = -minZ + 100 - zoom;
zoom += (d * ((d > 0) ? 0.05 : 0.01));
// ---- faces light ----
var j = 0, f;
while ( f = faces[j++] ) {
if ( f.faceVisible() ) {
f.distanceToCamera();
}
}
// ---- faces depth sorting ----
faces.sort(function (p0, p1) {
return p1.distance - p0.distance;
});
// ---- painting faces ----
lastOver = false;
j = 0;
while ( f = faces[j++] ) {
if (f.visible) {
f.draw();
} else break;
}
if (lastOver) faceOver = lastOver;
// ---- animation loop ----
requestAnimFrame(run);
}
return {
// ---- onload event ----
load : function () {
window.addEventListener('load', function () {
init();
}, false);
}
}
})().load();