Get started with Three.js

In this fourth tu­to­rial, you’ll learn how to in­ter­act with 3D ob­jects in WEBGL us­ing Three.js

Web Designer - - Contents -

The lat­est in the se­ries shows you how to in­ter­act with 3D ob­jects in WEBGL.

Webgl 3D lever­ages the graph­ics pipe­line, for highly op­ti­mised, in­ter­ac­tive ex­pe­ri­ences. Nearuni­ver­sal browser and de­vice sup­port makes it a per­fect ap­proach for real-time ren­der­ing. No plug­ins are re­quired and you can start learn­ing these tech­nolo­gies right away. WEBGL is based on the pow­er­ful Opengl lan­guage.

You can cre­ate com­plex mod­els with high lev­els of de­tail, reflections, en­vi­ron­ment maps, and shad­ows. Users can in­ter­act with your ex­pe­ri­ences in real-time and in this tu­to­rial you’ll learn how to do just that!

You’ll be us­ing the pop­u­lar 3D li­brary Three.js to dive into cre­at­ing scenes, and an­i­mat­ing ob­jects. Three.js is free, open source and light­weight, and has been used by count­less award-win­ning web­sites. Even Face­book 3D ob­jects are also now pow­ered by this 3D li­brary, so you’re in ex­cel­lent com­pany.

Con­tin­u­ing from pre­vi­ous tu­to­ri­als, you will move onto learn­ing about in­ter­ac­tion with ob­jects in 3D space. You’ll learn how to de­tect clicks (or touches on touch­screens) and mouse-over events. You’ll learn about ray­cast­ing from 2D space to 3D space. You’ll also learn some cool tricks for han­dling mul­ti­ple ob­jects and some maths to plot or­bits. Other than hav­ing a Javascript back­ground, you can dive into this tu­to­rial with no prior knowl­edge and get some great re­sults. The goal is to de­mys­tify 3D web pro­gram­ming and get you in­spired.

1. Cre­ate a ba­sic HTML file

To get started, you need to set up a ba­sic HTML file. You can setup ex­ter­nal CSS and Javascript files or in­clude them in­line for sim­plic­ity. Three.js’s ‘ren­derer’ class will cre­ate a <can­vas> el­e­ment for you. Add the fol­low­ing code to your ‘in­dex.html’ file:

<!DOCTYPE html>

<html>

<head> <style> html, body { mar­gin: 0; pad­ding:0; over­flow: hid­den; } </style> </head> <body> <script> </script> </body> </html>

2. In­clude the Three.js li­brary

In­clude a link to the Three.js li­brary in the head of your file, ei­ther hosted ex­ter­nally or download it from the Three.js repos­i­tory. You can find the li­brary and mini­fied Javascript at github.com/mr­doob/three.js/. Note: The code in this tu­to­rial has been tested on Three.js v91. <script src=”libs/three.min.js”></script>

3. Add global vari­ables

Be­tween your script tags, add the fol­low­ing global vari­ables to keep track of your mouse, ray­caster and ob­ject col­lec­tion. You’ll use many ob­jects in this tu­to­rial, to help demon­strate the in­ter­ac­tiv­ity: // global vars var ob­jects=[]; // col­lec­tion of ob­jects var num=20; // num­ber of ob­jects var ray­caster = new Three.ray­caster(); var mouse = new THREE.VECTOR2();

4. Cre­ate a 3D scene

You’re go­ing to add a ba­sic 3D scene, which will be the con­tainer for your ob­jects. The scene is the stage that will ren­der with the cam­era. All 3D pre­sen­ta­tions will have a scene or stage in some form. What’s in that stage, and in view of the cam­era, is what the user will see. Add the fol­low­ing code to add a scene:

// cre­ate a scene ob­ject var scene = new THREE.SCENE();

5. Add a per­spec­tive cam­era

Next, you need to add a cam­era. You’ll use the per­spec­tive cam­era, meant for 3D scenes. The first at­tribute is the field of view of the cam­era. The se­cond is the as­pect ra­tio (width:height). Then you in­di­cate the near-clip­ping plane and far-clip­ping plane dis­tances, which de­fine what is to be vis­i­ble to the cam­era. You will also push the cam­era back in ‘Z’ space a lit­tle to make things eas­ier to see.

// cre­ate cam­era var cam­era = new Three.per­spec­tive­cam­era( 45, win­dow.in­ner­width/win­dow.in­ner­height, 0.1, 1000 ); cam­era.po­si­tion.set(0.0,-1.0,10.0); cam­era.ro­ta­tion.y=.5;

6. Add a ren­derer and can­vas

The ren­derer han­dles the draw­ing of the ob­jects in your scene that are vis­i­ble to the cam­era. Set the ‘an­tialias’ prop­erty to ‘true’ to get smooth edges on our ob­ject. You can also de­fine the size of the draw area to full screen. The ren­derer cre­ates a ‘domele­ment’ – which is ac­tu­ally an HTML <can­vas> el­e­ment – that you can then ap­pend to the body. Op­tion­ally, you could spec­ify an ex­ist­ing can­vas el­e­ment to draw to if you pre­fer, via the ‘can­vas’ at­tribute of the ren­derer.

// cre­ate ren­derer var ren­derer = new Three.we­bglren­derer({ an­tialias:true}); ren­derer.set­pix­el­ra­tio( win­dow. de­vi­cepix­el­ra­tio ); ren­derer.set­size( win­dow.in­ner­width, win­dow. in­ner­height ); doc­u­ment.body.ap­pend­child( ren­derer.domele­ment );

7. Load the en­vi­ron­ment map and set ‘sky­box’

Phys­i­cal ma­te­ri­als work best with en­vi­ron­ment maps ap­plied. These maps are ‘sky­boxes’ that sur­round the ob­ject so they can af­fect it from all di­rec­tions ac­cu­rately, im­pact­ing the colour and in­ten­sity of the colour on the sur­face tex­ture. A great re­source for cube maps can be found at hu­mus.name/in­dex. php?page=tex­tures. Add this code to load your map:

// cre­ate en­vi­ron­ment map var en­vmap = new Three.cu­be­tex­tureloader() .set­path( ‘as­sets/’)

.load( [ ‘posx.jpg’, ‘negx.jpg’, ‘posy.jpg’, ‘negy.jpg’, ‘posz.jpg’, ‘negz.jpg’ ] );

// set as sky­box scene.back­ground = en­vmap;

Note: The or­der of the cube map im­ages is im­por­tant, so fol­low that pat­tern when set­ting up your own.

8. Cre­ate a loop for ob­ject cre­ation

You will be us­ing a col­lec­tion (ar­ray) of ob­jects in this tu­to­rial, to demon­strate the in­ter­ac­tiv­ity in a more dy­namic way than us­ing a sin­gle ob­ject. Set up a sim­ple loop de­fined by the global ‘num’ vari­able that you cre­ated pre­vi­ously:

// cre­ate col­lec­tion of for (i=0;i<=num;i++){

} ob­jects

9. Cre­ate a 3D ob­ject

Next, you need to cre­ate your ob­ject. Try us­ing a sphere or another ob­ject you like. As with pre­vi­ous tu­to­ri­als in this se­ries, you use geom­e­try and a ma­te­rial to­gether to cre­ate a mesh, which can then be added to the scene. Add the fol­low­ing code in­side your ‘for’ loop:

// cre­ate new mesh var geom­e­try = new Three.sphere­buffer­ge­om­e­try(

1,30,30 ); var ma­te­rial = new Three.mesh­phys­i­cal­ma­te­rial( { en­vmap:en­vmap, met­al­ness:1.0,rough­ness:0.0 }); var ob­ject = new THREE.MESH( geom­e­try, ma­te­rial );

10. Po­si­tion the ob­ject ran­domly

To spread the ob­jects out in 3D space, you can ap­ply ran­dom x, y and z po­si­tion val­ues. 3D co-or­di­nates have an ori­gin of 0,0,0. Ob­jects placed there would be in the ex­act cen­tre of the scene. To dis­trib­ute evenly we want the ran­dom­ness to range from -10 to 10 in each di­rec­tion. Try this code:

// set ran­dom po­si­tion ob­ject.po­si­tion.set(math.ran­dom() * 20.0 10.0 , Math.ran­dom() * 20.0 - 10.0, Math. ran­dom() * 20.0 - 10.0 );

11. Cal­cu­late the dis­tance from the ori­gin

To cre­ate some use­ful mo­tion, you will cre­ate or­bits in 3D space for each ob­ject. This will give them a pre­dictable path to move around the scene over time. To do this we sim­ply need a dis­tance to store as a con­stant on our ob­ject. Us­ing the ori­gin and the ran­dom po­si­tion you just set, it is easy to do this.

Add this code next:

// calc dis­tance as con­stant and as­sign to ob­ject var a = new THREE.VECTOR3( 0, 0, 0 ); var b = ob­ject.po­si­tion; var d = a.dis­tanceto( b ); ob­ject.dis­tance = d;

12. De­fine ini­tial an­gles for or­bits

To cre­ate a sim­ple or­bit over time, you need a con­stant dis­tance and an an­gle (in ra­di­ans). You will set two start­ing an­gles for your or­bits, to add ran­dom­ness to your scene. The two an­gles will en­able you to or­bit in three di­men­sions. Add this code next:

// de­fine 2 ran­dom but con­stant an­gles in ra­di­ans ob­ject.ra­di­ans = Math.ran­dom()*360 * Math.

PI/180; // ini­tial an­gle ob­ject.ra­di­ans2 = Math.ran­dom()*360 * Math.

PI/180; // ini­tial an­gle

13. Add the ob­ject to the scene and ar­ray

The last bit of code you need in the ob­ject cre­ation loop is to add the ob­ject to the scene and to the ar­ray you cre­ated. This will en­able you to eas­ily it­er­ate over the ob­jects later for check­ing for in­ter­ac­tions and for an­i­ma­tion. Add this code next:

// add ob­ject to scene scene.add( ob­ject );

// add to col­lec­tion ob­jects.push( ob­ject );

14. Ren­der the scene for each ‘re­ques­tani­ma­tion­frame’

Next, you call the ren­derer’s ‘ren­der’ func­tion in­side a loop bound to the ‘re­ques­tani­ma­tion­frame’ func­tion. You also add a lit­tle ro­ta­tion an­i­ma­tion to the ob­ject to see it spin. Add this new code, and run your scene:

// ren­der the scene

var an­i­mate = func­tion () { re­ques­tani­ma­tion­frame( an­i­mate ); for (i=0;i<=num;i++){ var o = ob­jects[i]; } ren­derer.ren­der(scene, cam­era); }; an­i­mate();

15. Up­date ob­ject’s po­si­tion

In­side your ‘for’ loop, up­date the an­gle of ro­ta­tion for your ob­ject, which will cause it to or­bit around the ori­gin over time. You can use some sim­ple trigonom­e­try to cal­cu­late the new po­si­tions us­ing dis­tance and an­gle (in ra­di­ans). For va­ri­ety you will or­bit odd ob­jects in one di­rec­tion and even ob­jects in the other. Add this code: if( i % 2 == 0) { o.ra­di­ans+=.01; o.ra­di­ans2+=.01; } else {

o.ra­di­ans-=.01; o.ra­di­ans2-=.01; } o.po­si­tion.x = (Math.cos(o.ra­di­ans) * o. dis­tance); o.po­si­tion.z = (Math.sin(o.ra­di­ans) * o. dis­tance); o.po­si­tion.y = (Math.sin(o.ra­di­ans2) * o. dis­tance*.5);

16. Add a click event lis­tener and han­dler func­tion

In­ter­act­ing with 3D ob­jects in WEBGL re­quires a few steps. First, you need to add an event lis­tener to the doc­u­ment of the WEBGL can­vas. Then you’ll need to as­sign a han­dler func­tion to it. Add the fol­low­ing code: doc­u­ment.ad­de­ventlis­tener( ‘mouse­down’, on­doc­u­ment­mouse­down, false ); func­tion on­doc­u­ment­mouse­down( event ) {

}

17. Get the mouse po­si­tion and ray cast

Next, you need to get the 2D po­si­tion of the mouse. You set up a vec­tor ‘2’ (x,y) to use for mouse po­si­tion. You then need to trans­late this 2D po­si­tion into 3D space by draw­ing a ray or line. You can also pre­vent the de­fault click be­hav­iour to bet­ter con­trol the event in your own code. Add the fol­low­ing code in your event han­dler: event.pre­vent­de­fault(); mouse.x = ( event.clientx / ren­derer. domele­ment.clien­twidth ) * 2 - 1; mouse.y = - ( event.clienty / ren­derer. domele­ment.clien­theight ) * 2 + 1; ray­caster.set­from­cam­era( mouse, cam­era );

18. Check to see if the ray in­ter­sects with ob­jects

The ray­caster’s ‘in­ter­sec­to­b­jects’ method re­turns an ar­ray of ob­jects that it in­ter­sects. If it’s empty, then noth­ing was in­ter­sected. Oth­er­wise you have ob­jects to check. The se­cond pa­ram­e­ter in­di­cates it should also check nested ob­jects. Add the fol­low­ing code in your event han­dler: var in­ter­sects = ray­caster.in­ter­sec­to­b­jects( ob­jects , true); if ( in­ter­sects.length > 0 ) {

}

19. Change the clicke­don ob­ject’s colour

Now that you have a ref­er­ence to an ob­ject that was clicked on, you can con­firm that in­ter­ac­tion visu­ally, or han­dle it in any way that you need. Try chain­ing the colour of the ob­ject to a new ran­dom colour, so you can see it work­ing. Try out this code in­side your ‘if’ block: ac­tive = in­ter­sects[ 0 ].ob­ject; // get the first ob­ject in­ter­sected

// change ma­te­rial to ran­dom color ac­tive.ma­te­rial.color.sethex( Math.ran­dom() * 0xffffff ); Turn­ing the mouse cur­sor into a pointer when it’s over an ob­ject that can be clicked is a use­ful UI tech­nique

20. Add a mouse-over han­dler

Another use­ful in­ter­ac­tion is mouse move­ment, specif­i­cally mouse-over and mouse-out events. You can use a sim­i­lar tech­nique to the click-han­dler you just made, with a cou­ple of small ad­just­ments for mouse move­ment. Try this code out to make the cur­sor icon change to a ‘pointer’ when you mouse over an ob­ject: doc­u­ment.ad­de­ventlis­tener( ‘mouse­move’, on­doc­u­ment­mouse­move, false ); func­tion on­doc­u­ment­mouse­move( event ) {

mouse.x = ( event.clientx / ren­derer. domele­ment.clien­twidth ) * 2 - 1;

mouse.y = - ( event.clienty / ren­derer.domele­ment.clien­theight ) * 2 + 1; ray­caster.set­from­cam­era( mouse,

cam­era ); var in­ter­sects = ray­caster. in­ter­sec­to­b­jects( ob­jects , true); if ( in­ter­sects.length > 0 ) { doc­u­ment.body.style.cur­sor = “pointer”; } else { doc­u­ment.body.style.cur­sor =

“de­fault”; } }

21. Mo­bile click-event han­dler

Another es­sen­tial in­ter­ac­tion is cap­tur­ing ‘touch’ events for mo­bile and touch screens. This works ex­actly like the ‘click’ event, but uses the ‘touches’ ar­ray. First, check for touches, then use the ‘tar­get­touches’ ar­ray to grab ‘pagex’ and ‘pagey’ val­ues, which are sim­i­lar to the ‘mouse.clientx’ and ‘Y’ val­ues. Add the fol­low­ing code to check it out:

// add mo­bile/touch event lis­tener doc­u­ment.ad­de­ventlis­tener( ‘touch­start’, on­doc­u­ment­touch­start, false ); func­tion on­doc­u­ment­touch­start( event ) { if ( event.touches.length === 1 ) { event.pre­vent­de­fault(); mouse.x = +(event. tar­get­touches[0].pagex / win­dow.in­ner­width) * 2 -1;

mouse.y = -(event. tar­get­touches[0].pagey / win­dow.in­ner­height) * 2 + 1; ray­caster.set­from­cam­era(

mouse, cam­era );

var in­ter­sects = ray­caster. in­ter­sec­to­b­jects( ob­jects , true); if ( in­ter­sects.length > 0 ) {

ac­tive = in­ter­sects[ 0 ].ob­ject; ac­tive.ma­te­rial. color.sethex( Math.ran­dom() * 0xffffff );

} } }

You can see your col­lec­tion of spheres with en­vi­ron­ment map re­flect­ing and as a sky­box for your scene

A ray or line is cast from the 2D space at the cam­era into 3D space. If this in­ter­sects your ob­jects you can de­tect it

Ap­ply­ing your code to mo­bile and touch screens is straight­for­ward once you have the ba­sics down

Newspapers in English

Newspapers from UK

© PressReader. All rights reserved.