Thursday, September 12, 2013

What Did That Set-Up Code Do? (part 1)

So, how did those 11 lines of set-up code magically produce a green square (actually a cube as we will see) in our browser?  I will now explain each line of the code we just blindly copied so that we know what's going on.  Here is tempGame.js, printed again, but with my added line numbers so that we can examine each line  (p.s. Don't try to run the project with the exact code below, because it won't work with my added line numbers; this is just for explaining purposes).

01 var scene = new THREE.Scene();
02 var camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000);

03 var renderer = new THREE.WebGLRenderer();
04 renderer.setSize(window.innerWidth, window.innerHeight);
05 document.body.appendChild(renderer.domElement);

06 var geometry = new THREE.CubeGeometry(1,1,1);
07 var material = new THREE.MeshBasicMaterial({ color: 'rgb(0,255,0)' });
08 var cube = new THREE.Mesh(geometry, material);
09 scene.add(cube);

10 camera.position.z = 5;
11 renderer.render(scene, camera);

If you are new to JavaScript, I will try to be as gentle as possible.  Here goes the code explanation:
01 var scene = new THREE.Scene();
01 All Three.js projects need 3 things to display graphics in your browser: a scene, a camera, and a renderer.  Line 01 declares a variable (using the 'var' keyword) called scene.  The 'new' keyword following the = sign means that your variable 'scene' is about to be filled with something.  And that something is a call to THREE.Scene().  If we want to use the Three.js library for our games, typing THREE.something() is how we access the library helper code that the Three.js developers have written for us.  In this case we want a new scene, so we type THREE.Scene().  Under the hood, this call sets up hundreds of lines of initialization code that the Three.js team has nicely wrapped up for us in this single line of code - nice, eh?!  Lastly, the semicolon (;) tells the browser that this is the end of this statement.  Think of semicolons as periods at the end of sentences.  They could have used periods for this purpose in computer programming languages I guess, but it would be confused with functions that have periods in them already, like THREE.Scene() - the semicolon clearly differentiates a statement-ending, from a function-call (where the period or 'dot' is used).
02 var camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000);
02 All Three.js apps need a camera as well, and this is what line 02 initializes.  Think of the camera as your eyes.  The camera can have different properties such as field of view, aspect ratio, range of depth, as well as position and orientation (where you are in the game world and where you are looking).  Just like before we use the 'var' keyword and a user-defined name for our camera - in this case it's just named 'camera'.  Then we call the THREE.PerspectiveCamera() function which fills our named camera with info about how it should view the world.  For this blog series, I will always use THREE.PerspectiveCamera rather than THREE.OrthographicCamera.  Perspective is what 99% of 3D games use.  Perspective means that objects nearer to you appear bigger and objects farther away appear smaller, just like in real life.  Orthographic means everything is the same size, no matter how far away.  This is useful for architecture and CAD software, but not really for 3D games.  Next you'll notice that there are bits of info inside the parentheses after the call to PerspectiveCamera.  These are called parameters, or the classical name is 'arguments' for the function.  These user-defined parameters (75, window.innerWidth/window.innerHeight, 0.1,1000, etc.) will make our camera behave differently.  The first is the field of view (or FOV for short).  This number represents degrees (0-360).  If we use a much smaller number, like 20, we will have a narrow FOV and you will not be able to see as much out of your peripheral vision.  If we use a larger number, like 80, we will have a wider FOV and we will be able to see a lot more around us.  An extra large number, like 300, will give you a bent, fish-eye lens effect, similar to the output of an expensive wide-angle panoramic camera.  We'll just leave this number at 75 for now.  After that is what is known as the aspect ratio (hence the division).  2/1 is an example ratio, 4/3 is another example ratio, and in this case window width/height is the ratio we want.  It takes the current browser window width and divides it by the current browser window height (whatever that may be, on a per-user basis) and gives a nice aspect ratio.  We could just use static numbers here, but using the variables window.innerWidth and window.innerHeight is preferred, because if the user suddenly changes the window dimensions of their browser, this will automatically update the aspect ratio so that your game will not look incorrectly 'squished' or 'stretched'.  Finally we have the parameters 0.1 and 1000, which are the values for the near clipping plane and the far clipping plane.  For performance reasons we will look at later, game objects closer than '0.1' units to our camera will not be displayed, or 'clipped'.  Similarly, objects farther than '1000' units will not be displayed, or 'clipped'.  It's unnecessary work for your graphics display to try and draw things that are either inside your camera, or too far away to even see.  These clip-plane numbers can be changed, and on a per-game basis we might have to fine-tune them, but for now we will keep them the same. 
03 var renderer = new THREE.WebGLRenderer();
03 Finally, all Three.js projects need a renderer, or way of actually drawing pixels on your display screen.  We again use the 'var' keyword and name it 'renderer' and fill it with a call to THREE.WebGLRenderer().  This sets up our game to use WebGL in the browser, which utilizes our hardware accelerated graphics card to speed up the drawing, if it is available.  You could use the THREE.CanvasRenderer() but it will not be hardware optimized like WebGL and you will probably experience a slower, 'choppy' frame rate.
04 renderer.setSize(window.innerWidth, window.innerHeight);
04 Next we use the variable name for renderer that we chose ('renderer' in our case), and put a dot(.) after it to call the setSize function, which takes 2 parameters - width and height.  The name setSize is a little misleading because it does not make the game window smaller or larger, but rather the drawing resolution smaller or larger.  Again, we will just use the stock window.innerWidth and window.innerHeight values to give a crisp, clear look to our games.  You could use lower numbers in place of these variables, and it would maybe run faster on older systems - but it will look blurry. We will just leave it the same for now.

05 document.body.appendChild(renderer.domElement);
05 This next line attaches our newly created renderer to our webpage (example01.html) by using a canvas element.  Don't worry if you don't quite understand what this is doing, because we will never change this line in our games.  It just needs to be there so that you can see the graphics output on your webpage.  

Continued in part 2...