Direct Link
Source code
| <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> |
| <html> |
| <head> |
| <title>VML 3D walls - interactive DHTML</title> |
| <meta name="Author" content="Gerard Ferrandez at http://www.dhteumeuleu.com"> |
| <meta http-equiv="imagetoolbar" content="no"> |
| <style type="text/css"> |
| html { |
| overflow: hidden; |
| } |
| body { |
| position: absolute; |
| margin: 0px; |
| padding: 0px; |
| background: #ccc; |
| width: 100%; |
| height: 100%; |
| } |
| #screen { |
| position: absolute; |
| left: 0px; |
| top: 0px; |
| width: 100%; |
| height: 100%; |
| background: #ccc; |
| } |
| #floor { |
| position: absolute; |
| top: 50%; |
| width: 100%; |
| left:0px; |
| height:50%; |
| background:#999; |
| } |
| </style> |
| <script type="text/javascript" src="library/svgvml.js"></script> |
| <script type="text/javascript"> |
| // ===================================================================== |
| // written by Gerard Ferrandez - May 8, 2009 |
| // http://www.dhteumeuleu.com - CC-BY-NC |
| // ===================================================================== |
| // |
| /* ==== script ==== */ |
| var m3D = function () { |
| /* ---- private vars ---- */ |
| var svg; |
| var scr; |
| var xm = 0; |
| var ym = 0; |
| var nx = 0; |
| var ny = 0; |
| var zx = 0; |
| var zy = 0; |
| var nw = 0; |
| var nh = 0; |
| var xd = 0; |
| var yd = 0; |
| var walls = []; |
| var cam = { |
| x : 0, |
| y : 0, |
| z : 0, |
| rx : 0, |
| ry : 0, |
| rz : 0, |
| focal : 500 |
| }; |
| //////////////////////////////////////////////////////////////////////////// |
| /* ==== DOM events ==== */ |
| function addEvent (o, e, f) { |
| if (window.addEventListener) o.addEventListener(e, f, false); |
| else if (window.attachEvent) r = o.attachEvent('on' + e, f); |
| } |
| /* ==== screen dimensions ==== */ |
| function resize() { |
| nw = scr.offsetWidth * .5 || 0; |
| nh = scr.offsetHeight * .5 || 0; |
| zx = nw / 600; |
| zy = nh / 300; |
| var o = scr; |
| for (nx = 0, ny = 0; o != null; o = o.offsetParent) { |
| nx += o.offsetLeft; |
| ny += o.offsetTop; |
| } |
| } |
| var initEvents = function () { |
| /* ---- mouse move event ---- */ |
| addEvent(document, 'mousemove', function (e) { |
| if (window.event) e = window.event; |
| xm = e.clientX * .5; |
| ym = e.clientY * .5; |
| }); |
| /* ---- screen resize event ---- */ |
| resize(); |
| addEvent(window, 'resize', resize); |
| } |
| //////////////////////////////////////////////////////////////////////////// |
| /* ==== wall constructor ==== */ |
| var Wall = function (x,y,z,strokeColor,fillColor) { |
| this.o = svg.createPolyline(1, "", fillColor, fillColor); |
| this.x = x; |
| this.y = y; |
| this.z = z; |
| this.n = x.length; |
| } |
| Wall.prototype.anim = function () { |
| var points = ""; |
| for (var i = 0; i < this.n; i++) { |
| /* ---- 3D coordinates ---- */ |
| var x = zx * this.x[i] - cam.x; |
| var y = zy * this.y[i] - cam.y; |
| var z = this.z[i] - cam.z; |
| /* ==== rotations ==== */ |
| var xy = cam.cx * y - cam.sx * z; |
| var xz = cam.sx * y + cam.cx * z; |
| var yz = cam.cy * xz - cam.sy * x; |
| var yx = cam.sy * xz + cam.cy * x; |
| /* ==== 2D transform ==== */ |
| var scale = cam.focal / (cam.focal + yz); |
| x = (yx * scale) + nw; |
| y = (xy * scale) + nh; |
| if (i == 0) { |
| var x0 = x; |
| var y0 = y; |
| } |
| points += Math.round(x) + "," + Math.round(y) + ","; |
| } |
| points += Math.round(x0) + "," + Math.round(y0); |
| this.o.move(points); |
| } |
| //////////////////////////////////////////////////////////////////////////// |
| /* ==== main loop ==== */ |
| var run = function () { |
| xd += (xm - xd) * .2; |
| yd += (ym + nh - yd) * .2; |
| cam.x = -(xd - nw * .5 - nx); |
| cam.y = -(yd - nh * 1.5 - ny); |
| cam.z = -200 |
| cam.ry = cam.x / 1000; |
| cam.rx = -cam.y / 2000; |
| /* ==== angles sin and cos ==== */ |
| cam.cx = Math.cos(cam.rx); |
| cam.sx = Math.sin(cam.rx); |
| cam.cy = Math.cos(cam.ry); |
| cam.sy = Math.sin(cam.ry); |
| /* ==== walls ==== */ |
| for (var i = 0, o; o = walls[i++]; ) o.anim(); |
| setTimeout(run, 16); |
| } |
| return { |
| //////////////////////////////////////////////////////////////////////////// |
| /* ==== PUBLIC parameters setup ==== */ |
| /* ==== initialize script ==== */ |
| init : function () { |
| addEvent(window, 'load', function () { |
| /* ---- init SVG/VML canvas ---- */ |
| scr = document.getElementById("screen"); |
| initEvents(); |
| svg = new vectorGraphics(scr, true); |
| /* ---- create walls ---- */ |
| walls.push(new Wall([-500,-500,-500,-500],[300,-300,-300,300],[-100,-100,500,500],"","#e0e0e0")); |
| walls.push(new Wall([-500,-500,-500,-500],[-100,-200,-200,-100],[-100,-100,500,500],"","#d60")); |
| walls.push(new Wall([500,500,500,500],[300,-300,-300,300],[0,0,500,500],"","#ccc")); |
| walls.push(new Wall([500,500,500,500],[-100,-200,-200,-100],[0,0,500,500],"","#ca5000")); |
| walls.push(new Wall([-500,-500,-100,-100],[300,-300,-300,300],[500,500,500,500],"","#e0e0e0")); |
| walls.push(new Wall([-500,-500,-490,-490],[300,-300,-300,300],[500,500,500,500],"","#ddd")); |
| walls.push(new Wall([-490,-490,-480,-480],[300,-300,-300,300],[500,500,500,500],"","#dedede")); |
| walls.push(new Wall([-100,-100,500,500],[300,-300,-300,300],[500,500,500,500],"","#d8d8d8")); |
| walls.push(new Wall([-500,-500,-90,-90],[-200,-100,-100,-200],[500,500,500,500],"","#d60")); |
| walls.push(new Wall([-100,-100,500,500],[-200,-100,-100,-200],[500,500,500,500],"","#da6100")); |
| walls.push(new Wall([-100,500,500],[-302,-302,-302,-302],[500,500,0,0],"", "#c6c6c6")); |
| walls.push(new Wall([-100,500,500],[302,302,302,302],[500,500,0],"", "#888")); |
| walls.push(new Wall([-500,-500,-100,500],[-302,-302,-302,-302],[200,500,500,0],"", "#cacaca")); |
| walls.push(new Wall([-1000,-1000,-500,-500],[300,-300,-300,300],[-100,-100,-100,-100],"","#d8d8d8")); |
| walls.push(new Wall([-1000,-1000,-500,-500],[-200,-100,-100,-200],[-100,-100,-100,-100],"","#d60")); |
| walls.push(new Wall([1000,1000,500,500],[300,-300,-300,300],[0,0,0,0],"","#e0e0e0")); |
| walls.push(new Wall([1000,1000,500,500],[-200,-100,-100,-200],[0,0,0,0],"","#d60")); |
| /* ---- start ---- */ |
| run(); |
| }); |
| } |
| } |
| }(); |
| /* ==== start script ==== */ |
| m3D.init(); |
| </script> |
| </head> |
| <body> |
| <div id="screen"> |
| <div id="floor"></div> |
| </div> |
| </body> |
| </html> |
| /* ==== Easing function ==== */ |
| var Library = {}; |
| Library.ease = function () { |
| this.target = 0; |
| this.position = 0; |
| this.move = function (target, speed) { |
| this.position += (target - this.position) * speed; |
| } |
| } |
| var tv = { |
| /* ==== variables ==== */ |
| O : [], |
| fps : 0, |
| screen : {}, |
| angle : { |
| x : new Library.ease(), |
| y : new Library.ease() |
| }, |
| camera : { |
| x : new Library.ease(), |
| y : new Library.ease() |
| }, |
| create3DHTML : function (i, x, y, z, sw, sh) { |
| /* ==== create HTML image element ==== */ |
| var o = document.createElement('img'); |
| o.src = i.src; |
| tv.screen.obj.appendChild(o); |
| /* ==== 3D coordinates ==== */ |
| o.point3D = { |
| x : x, |
| y : y, |
| z : z, |
| sw : sw, |
| sh : sh, |
| w : i.width, |
| h : i.height |
| }; |
| o.point3D.z.target = z; |
| /* ==== push object ==== */ |
| o.point2D = {}; |
| tv.O.push(o); |
| /* ==== on mouse over event ==== */ |
| /*o.onmouseover = function () { |
| if (this != tv.o) { |
| this.point3D.z.target = tv.mouseZ; |
| tv.camera.x.target = this.point3D.x; |
| tv.camera.y.target = this.point3D.y; |
| if (tv.o) tv.o.point3D.z.target = 0; |
| tv.o = this; |
| } |
| return false; |
| }*/ |
| /* ==== on mousedown event ==== */ |
| /*o.onmousedown = function () { |
| if (this == tv.o) { |
| if (this.point3D.z.target == tv.mouseZ) this.point3D.z.target = 0; |
| else { |
| tv.o = false; |
| this.onmouseover(); |
| } |
| } |
| }*/ |
| /* ==== main 3D function ==== */ |
| o.animate = function () { |
| /* ==== 3D coordinates ==== */ |
| var x = this.point3D.x - tv.camera.x.position; |
| var y = this.point3D.y - tv.camera.y.position; |
| var z = this.point3D.z; |
| //this.point3D.z.move(this.point3D.z.target, this.point3D.z.target ? .15 : .08); |
| /* ==== rotations ==== */ |
| var xy = tv.angle.cx * y - tv.angle.sx * z; |
| var xz = tv.angle.sx * y + tv.angle.cx * z; |
| var yz = tv.angle.cy * xz - tv.angle.sy * x; |
| var yx = tv.angle.sy * xz + tv.angle.cy * x; |
| /* ==== 2D transform ==== */ |
| var scale = tv.camera.focalLength / (tv.camera.focalLength + yz); |
| x = yx * scale; |
| y = xy * scale; |
| var w = Math.round(Math.max(0, this.point3D.w * scale * this.point3D.sw)); |
| var h = Math.round(Math.max(0, this.point3D.h * scale * this.point3D.sh)); |
| /* ==== HTML rendering ==== */ |
| var o = this.style; |
| o.left = Math.round(x + tv.screen.w - w * .5) + 'px'; |
| o.top = Math.round(y + tv.screen.h - h * .5) + 'px'; |
| o.width = w + 'px'; |
| o.height = h + 'px'; |
| o.zIndex = 10000 + Math.round(scale * 1000); |
| } |
| }, |
| /* ==== init script ==== */ |
| init : function (structure, FL, mouseZ, rx, ry) { |
| this.screen.obj = document.getElementById('screen'); |
| this.screen.obj.onselectstart = function () { return false; } |
| this.screen.obj.ondrag = function () { return false; } |
| this.mouseZ = mouseZ; |
| this.camera.focalLength = FL; |
| this.angle.rx = rx; |
| this.angle.ry = ry; |
| /* ==== create objects ==== */ |
| var i = 0, o; |
| while( o = structure[i++] ) |
| this.create3DHTML(o.img, o.x, o.y, o.z, o.sw, o.sh); |
| /* ==== start script ==== */ |
| this.resize(); |
| mouse.y = this.screen.y + this.screen.h; |
| mouse.x = this.screen.x + this.screen.w; |
| /* ==== loop ==== */ |
| setInterval(tv.run, 16); |
| setInterval(tv.dFPS, 1000); |
| }, |
| /* ==== resize window ==== */ |
| resize : function () { |
| var o = tv.screen.obj; |
| if (o) { |
| tv.screen.w = o.offsetWidth / 2; |
| tv.screen.h = o.offsetHeight / 2; |
| for (tv.screen.x = 0, tv.screen.y = 0; o != null; o = o.offsetParent) { |
| tv.screen.x += o.offsetLeft; |
| tv.screen.y += o.offsetTop; |
| } |
| } |
| }, |
| /* ==== main loop ==== */ |
| run : function () { |
| tv.fps++; |
| /* ==== motion ease ==== */ |
| tv.angle.x.move(-(mouse.y - tv.screen.h - tv.screen.y) * tv.angle.rx, .5); |
| tv.angle.y.move( (mouse.x - tv.screen.w - tv.screen.x) * tv.angle.ry, .5); |
| // tv.camera.y.target = mouse.y; |
| tv.camera.y.move((mouse.x - tv.screen.w - tv.screen.x), .5); |
| tv.camera.y.move((mouse.y - tv.screen.h - tv.screen.y), .5); |
| //tv.camera.y.move(tv.camera.y.target, .5); |
| /* ==== angles sin and cos ==== */ |
| tv.angle.cx = Math.cos(tv.angle.x.position); |
| tv.angle.sx = Math.sin(tv.angle.x.position); |
| tv.angle.cy = Math.cos(tv.angle.y.position); |
| tv.angle.sy = Math.sin(tv.angle.y.position); |
| /* ==== loop through images ==== */ |
| var i = 0, o; |
| while( o = tv.O[i++] ) o.animate(); |
| }, |
| /* ==== trace frames per seconds ==== */ |
| dFPS : function () { |
| document.getElementById('FPS').innerHTML = tv.fps + ' FPS'; |
| tv.fps = 0; |
| } |
| } |
| /* ==== global mouse position ==== */ |
| var mouse = { |
| x : 0, |
| y : 0 |
| } |
| document.onmousemove = function(e) { |
| if (window.event) e = window.event; |
| mouse.x = e.clientX; |
| mouse.y = e.clientY; |
| return false; |
| } |
| /* ==== starting script ==== */ |
| onload = function() { |
| scr = document.getElementById("screen"); |
| svg = new vectorGraphics(scr, true); |
| o = svg.createPolyline(2,"10,10,200,10,200,200,10,200,10,10","#F00","#A00"); |
| onresize = tv.resize; |
| /* ==== build grid ==== */ |
| var img = document.getElementById('bankImages').getElementsByTagName('img'); |
| var structure = []; |
| for (var i = -300; i <= 300; i += 120) |
| for (var j = -300; j <= 300; j += 120) |
| structure.push({ img:img[0], x:i, y:j, z:0, sw:.5, sh:.5 }); |
| /* ==== let's go ==== */ |
| tv.init(structure, 350, -200, .002, .001); |
| } |



Cool walls, but where’s the menu?
Glurp… Arghhh… euh… where’s that menu… last time I saw it, it was such an awesome menu, then… gone!
http://wheresthatmenu.com/
Ola, é possível inserior texto sobre as paredes acompanhando o movimento?
Parabéns pelo trabalho!
Hello, can inserior text on the walls following the trend?
Congratulations for your job!
Is it possible to slightly modify the code to display “a dungeon” from a defined map? I mean no textures, simple, just filled polygons? If I wanted to display some text on the walls, I’d have to convert it to vectors first?
Hi Gerard great demo! I’m trying to learn the math better behind 3D one-point perspective so I can understand your source code more. Do you know of any good references?
Best,
Brad Neuberg
Hi Brad,
Paradoxally enough, I’ve learned most of my 3D knowledge from Flash coders. A good tutorial (for the basics) can be found here : http://www.kirupa.com/develope...dindex.htm – credits Senocular. ActionScript sources should be easy to translate in JS.
cheers Gerard
LOL
great showcase, thanks
best
ron