Sources
Main html file: | dhtml/scene-3D-room-menu.html |
ge1doot mini library: | /library/ge1doot.js |
CANVAS image projection: | /library/imageTransform.js |
CSS
html {
overflow: hidden;
-ms-touch-action: none;
-ms-content-zooming: none;
}
body {
position: absolute;
margin: 0px;
padding: 0px;
background: #000;
width: 100%;
height: 100%;
}
#canvas {
position: absolute;
width: 100%;
height: 100%;
background: #000;
}
HTML
<canvas id="canvas">HTML5 CANVAS IS REQUIRED</canvas>
JS
/*
* ======================================================
* CANVAS 3D experiment - you see
* http://www.dhteumeuleu.com/you-see
* Author Gerard Ferrandez - 14 Feb 2012
* ---------------------------------------------------
* Released under the MIT license
* http://www.dhteumeuleu.com/LICENSE.html
* Last updated: 19 Nov 2012
* ======================================================
*/
"use strict";
(function () {
// ======== private vars ========
var faces = [], tweens, camera;
var scr, ctx, pointer, target, targetold, faceOver, isMoving;
var globalRX = 0, globalRY = 0;
// ======== points constructor ========
var Point = function (parentFace, point, rotate) {
this.face = parentFace;
this.x = point[0];
this.y = point[1];
this.z = point[2];
this.scale = 0;
this.X = 0;
this.Y = 0;
if (rotate) {
this.x += rotate.x;
this.y += rotate.y;
this.z += rotate.z;
}
return this;
}
// ======== points projection ========
Point.prototype.project = function () {
// ---- 3D rotation ----
var p = camera.rotate(
this.x - camera.x.value,
this.y - camera.y.value,
this.z - camera.z.value
)
// ---- distance to the camera ----
if (this.face) {
var z = p.z + camera.focalLength;
var distance = Math.sqrt(p.x * p.x + p.y * p.y + z * z);
if (distance > this.face.distance) this.face.distance = distance;
}
// --- 2D projection ----
this.scale = (camera.focalLength / (p.z + camera.focalLength)) * camera.zoom.value;
this.X = (scr.width * 0.5) + (p.x * this.scale);
this.Y = (scr.height * 0.5) + (p.y * this.scale);
// --- project next point ---
this.next && this.next.project();
}
// ======= faces constructor ========
var Face = function (path, f) {
this.f = f;
var w = f.w * 0.5;
var h = f.h * 0.5;
var ax = f.rx * Math.PI * 0.5;
var ay = f.ry * Math.PI * 0.5;
this.locked = false;
this.hidden = f.hidden || null;
this.visible = true;
this.distance = 0;
// ---- center point ----
this.pc = new Point(this, [f.x, f.y, f.z]);
// ---- 3D rotation ----
var rotate = function (x, y, z, ax, ay) {
var tz = z * Math.cos(ay) + x * Math.sin(ay);
var ty = y * Math.cos(ax) + tz * Math.sin(ax);
return {
x: x * Math.cos(ay) - z * Math.sin(ay),
y: ty,
z: tz * Math.cos(ax) - y * Math.sin(ax)
}
}
// ---- quad points ----
this.p0 = new Point(this, [f.x, f.y, f.z], rotate(-w, -h, 0, ax, ay));
this.p1 = new Point(this, [f.x, f.y, f.z], rotate( w, -h, 0, ax, ay));
this.p2 = new Point(this, [f.x, f.y, f.z], rotate( w, h, 0, ax, ay));
this.p3 = new Point(this, [f.x, f.y, f.z], rotate(-w, h, 0, ax, ay));
// ---- corner points ----
this.c0 = new Point(false, [f.x, f.y, f.z], rotate(-w, -h, -15, ax, ay));
this.c1 = new Point(false, [f.x, f.y, f.z], rotate( w, -h, -15, ax, ay));
this.c2 = new Point(false, [f.x, f.y, f.z], rotate( w, h, -15, ax, ay));
this.c3 = new Point(false, [f.x, f.y, f.z], rotate(-w, h, -15, ax, ay));
// ---- target angle ----
var r = rotate(ax, ay, 0, ax, ay, 0);
this.ax = r.x + Math.PI / 2;
this.ay = r.y + Math.PI / 2;
// ---- create 3D image ----
this.img = new ge1doot.transform.Image(path + f.src, f.tl || 2, {
isLoaded: function(img) {
// --- disable borders ---
img.stroke = false;
}
});
this.img.stroke = "RGB(128,128,128)";
}
// ======== face projection ========
Face.prototype.project = function () {
this.visible = true;
this.distance = -99999;
// ---- points projection ----
this.p0.project();
this.p1.project();
this.p2.project();
this.p3.project();
// ---- 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) < 0) ^
(this.p0.X <= this.p1.X == this.p0.X > this.p2.X)
) || this.hidden) {
this.visible = false;
this.distance = -99999;
if (!this.locked && this.hidden === false) this.hidden = true;
}
}
// ======== face border ========
Face.prototype.border = function () {
this.c0.project();
this.c1.project();
this.c2.project();
this.c3.project();
this.pc.project();
ctx.beginPath();
ctx.moveTo(this.c0.X, this.c0.Y);
ctx.lineTo(this.c1.X, this.c1.Y);
ctx.lineTo(this.c2.X, this.c2.Y);
ctx.lineTo(this.c3.X, this.c3.Y);
ctx.closePath();
ctx.lineWidth = this.pc.scale * this.f.w / 30;
ctx.strokeStyle = "rgb(255,255,255)";
ctx.lineJoin = "round";
ctx.stroke();
}
// ======== is pointer inside ? =========
var selectFace = function () {
isMoving = false;
target = false;
for (var i = 0, f; f = faces[i++];) {
if (f.visible) {
if (
f.img.isPointerInside(
pointer.X,
pointer.Y,
f.p0, f.p1, f.p2, f.p3
)
) target = f;
} else break;
}
if (target && target.f.select != false && !pointer.isDraging) {
faceOver = target;
scr.setCursor("pointer");
} else scr.setCursor("move");
}
// ======== onclick ========
var click = function () {
selectFace();
// ---- target image ----
if (target && target.f.select != false) {
if (target == targetold) {
// ---- reset scene ----
camera.center();
targetold = false;
} else {
targetold = target;
target.locked = false;
// ---- target redirection ----
if (target.f.target != "") {
for (var i = 0, f; f = faces[i++];) {
if (f.f.id && f.f.id == target.f.target) {
target = f;
targetold = f;
if (f.hidden) {
f.hidden = false;
f.locked = true;
targetold = false;
}
break;
}
}
}
// ---- move camera ----
target.pc.project();
camera.setTarget(target);
}
}
}
var init = function (json) {
// ---- init script ----
scr = new ge1doot.Screen({
container: "canvas"
});
ctx = scr.ctx;
pointer = new ge1doot.Pointer({
tap: click,
move: function () {
isMoving = true;
}
});
// ---- tweens engine ----
tweens = new ge1doot.Tweens();
// ---- init camera ----
camera = {
x: new tweens.Add(100),
y: new tweens.Add(100),
z: new tweens.Add(100, 0,0),
rx: new tweens.Add(100, 0,0, true),
ry: new tweens.Add(100, 0,0, true),
zoom: new tweens.Add(100, 0.1, 1),
focalLength: 500,
centered: false,
cosX: 0,
cosY: 0,
sinX: 0,
sinY: 0,
setTarget: function (target) {
// ---- set position ----
this.x.setTarget(target.pc.x);
this.y.setTarget(target.pc.y);
this.z.setTarget(target.pc.z);
// ---- set view angles ----
this.rx.setTarget((Math.PI * 0.5) - target.ax - globalRX);
this.ry.setTarget((Math.PI * 0.5) - target.ay - globalRY);
// ---- zoom ----
this.zoom.setTarget(target.f.zoom ? target.f.zoom : 2);
this.centered = false;
},
center: function () {
this.x.setTarget(0);
this.y.setTarget(0);
this.z.setTarget(0);
this.zoom.setTarget(1);
this.centered = true;
},
move: function () {
// ---- easing camera position and view angle ----
tweens.iterate();
// ---- additional drag/touch rotations ----
globalRX += (((-pointer.Yi * 0.01) - globalRX) * 0.1);
globalRY += (((-pointer.Xi * 0.01) - globalRY) * 0.1);
if (!this.centered && pointer.isDraging) {
// ---- reset zoom & position ----
this.center();
targetold = false;
}
// ---- pre calculate trigo ----
this.cosX = Math.cos(this.rx.value + globalRX);
this.sinX = Math.sin(this.rx.value + globalRX);
this.cosY = Math.cos(this.ry.value + globalRY);
this.sinY = Math.sin(this.ry.value + globalRY);
},
rotate: function (x, y, z) {
// ---- 3D rotation ----
var r = this.cosY * z + this.sinY * x;
return {
x: this.cosY * x - this.sinY * z,
y: this.sinX * r + this.cosX * y,
z: this.cosX * r - this.sinX * y
}
}
}
// ---- create faces ----
for (var i = 0, f; f = json.faces[i++];) {
faces.push(
new Face(json.path, f)
);
}
// ---- engine start ----
run();
}
// ===== main loop =====
var run = function () {
var i, f;
// ---- clear screen ----
ctx.clearRect(0,0, scr.width, scr.height);
// ---- 3D projection ----
for (i = 0; f = faces[i++];) {
f.project();
}
// ---- faces depth sorting ----
faces.sort(function (p0, p1) {
return p1.distance - p0.distance;
});
// ---- drawing ----
for (i = 0; f = faces[i++];) {
if (f.visible) {
// ---- draw image ----
f.img.transform(f.p0, f.p1, f.p2, f.p3);
if (f.locked && pointer.isDraging) f.locked = false;
if (f === faceOver) faceOver.border();
} else break;
}
// ---- pointer over ----
isMoving && selectFace();
// ---- camera ----
camera.move();
// ---- loop ----
requestAnimFrame(run);
}
return {
// ---- onload event ----
load : function (json) {
window.addEventListener('load', function () {
// --- load additional lib components ---
ge1doot.loadJS([
"../library/imageTransform.js",
"../library/ease.js"
], init, json);
}, false);
}
}
})().load({
path: "../images/",
faces: [
// ---- main images ----
{id: "1", src:"N3.jpg", x:0, y:0, z:200, rx:0, ry:0, w: 300, h: 200, select: false},
{id: "2", src:"go21.jpg", x:200, y:0, z:0, rx:0, ry:-1, w: 300, h: 200},
{id: "3", src:"sf42.jpg", x:0, y:150, z:0, rx:1, ry:0, w: 300, h: 200},
{id: "4", src:"go26.jpg", x:0, y:-150, z:0, rx:-1, ry:0, w: 300, h: 200},
{id: "5", src:"ct133.jpg", x:-200, y:0, z:0, rx:0, ry:1, w: 300, h: 200},
{id: "6", src:"ct132.jpg", x:0, y:0, z:-200, rx:0, ry:-2, w: 300, h: 200},
// ---- special hidden image :) ----
{id: "7", target: "1", src:"ct15.jpg", x:0, y:0, z:200, rx:0, ry:-2, w: 300, h: 200, hidden: true},
// ---- small targets ----
{src:"ct132.jpg", target: "6", x:0, y:-40, z:170, rx:0, ry:0, w: 80, h: 60, tl: 1},
{src:"ct133.jpg", target: "5", x:-100, y:-40, z:170, rx:0, ry:0, w: 80, h: 60, tl: 1},
{src:"go26.jpg", target: "4", x:100, y:-40, z:170, rx:0, ry:0, w: 80, h: 60, tl: 1},
{src:"sf42.jpg", target: "3", x:0, y:40, z:170, rx:0, ry:0, w: 80, h: 60, tl: 1},
{src:"go21.jpg", target: "2", x:-100, y:40, z:170, rx:0, ry:0, w: 80, h: 60, tl: 1},
{src:"N3.jpg", target: "7", x:100, y:40, z:170, rx:0, ry:0, w: 80, h: 60, tl: 1}
]
});