three.js Wisdom Collector

This page is an in-progress collector of resources and notes that are helping me to start learning about creating and displaying interactive graphics with the 3D JavaScript library three.js for rendering in WebGL. If you’ve never used three.js before, this collector can help you get set up and started.

What’s in this collector?

Getting Started

  • Walk through Getting started at three.js
    Download three.js, follow the instructions here to make an HTML file with a <script> tag for holding three.js code, and create and render a scene to make your first simple three.js application. You can walk through this process as an absolute beginner and get a feeling for the key elements that make up a three.js application. Start here!
  • Watch the Intro to WebGL and Three.js video by David Lyons
    This video walks through the basics of three.js in less than twenty minutes. This video is good for a first overview, but we’ll also be returning to it later as we flesh out our template document with options for different materials, lighting, textures, interaction, and so on. Lyons’ slides are available separately at davidscottlyons.com, and include this very informative node map:
    scenegraph
  • Check out some basic three.js examples
    Stemkoski’s example pages are a great place to get started, with basic examples of three.js features. View the source code of each example to get an idea for how to use each feature. Get started with a commented “hello world” example, or a basic, streamlined template. Of particular use for 3D printing are his examples of shapes, extrusion, text, and booleans. When you’re ready to move on to mathy stuff, check out his examples for graphing surfaces, parametric surfaces, and parametric curves. He’s even got a beautiful polyhedra viewer and an interactive Minecraft-style voxel painter.
  • Know how to use the three.js documentation
    The documentation pages at three.js provide information on all the basic features:lights, materials, textures, math, curves, renderers, and so on. Many of the objects in three.js come with associated methods and properties, and knowing their names can help you figure out how they can be modified. For example, look at the Mesh class documentation, and suppose that you are defining an object “mesh” with the Three.Mesh class. At the top of the documentation page you can see that Mesh inherits properties from its parent class Object3D; click on that. Object3D comes with various properties, such as “.position”. Clicking there, we see that position comes with a property “.x” that controls the x-coordinate. This tells us that the code “mesh.position.x = 2;” will set the x-coordinate position of the mesh to be 2. (Thank you, chsymann, for your clear explanation of this!)

Boots on the Ground

  • Get the Sublime text editor for coding
    Sublime is a beautiful, powerful editor with many features that make writing code easier and faster. You can configure Sublime so that webpage output is automatically refreshed when HTML code is saved, and set up a linter for catching simple errors in your code as you type:

  • Or… get the Brackets text editor for web design
    Another clean, pretty editor. I don’t always like the auto-complete options in Brackets, but it comes ready-made to auto-refresh with Chrome; it’s a quick way to get started without a fuss and a good option if you don’t want to pay for Sublime.
  • Set up a folder structure
    Create a folder for your HTML and JavaScript files that also contains:

    • A subfolder for the mrdoob-three.js library downloaded from threejs.org, and
    • A subfolder called “js” to hold copies of the specific scripts you will be using, which at this point are “three.min.js”, “OrbitControls.js”, and “dat.gui.min.js”, all of which are in subfolders of the mrdoob-three.js library directory.
  • Open the HTML in your browser and the code in your editor 
    If you’re using Sublime with the packages described above, you should open your HTML file in your browser first, and then open your JavaScript file in Sublime. Then when you press Command-Shift-R in Sublime, the active browser tab should automatically refresh. See the next section for template code that you can start with.
  • Use the Linter and Inspect Element to clean up and debug your code
    If you have SublimeLinter set up, then lines with suspected errors or sloppy formatting will be highlighted in the left column of your Sublime code editor. If you use Chrome as your browser, you can right-click (or use a menu) to “Inspect Element” or choose the menu selection View/Developer/DeveloperTools to open the Chrome debugger.

Template Code

  • Create an HTML container for your three.js code
    The three.js Getting started document consisted of three.js code typed directly into a <script> tag in an HTML file. We’re going to split up the JavaScript and HTML code into two separate files so that we will be able to run the SublimeLinter plugin on our JavaScript code. We’ll also load Orbit Controls and a GUI Controller library. Make an HTML file called “template_basic.html” with this code:

    <html>
    	<head>
    		<title>A Basic Three.js app</title>
    		<style>
    			body { margin: 0; }
    			canvas { width: 100%; height: 100% }
    		</style>
    	</head>
    	<body>
    		<script src="js/three.min.js"></script>		
    		<script src="js/OrbitControls.js"></script> 
    		<script src="js/dat.gui.min.js"></script> 
    		<script src="template_basic.js"></script>
    	</body>
    </html>
    
  • Create a very basic JavaScript file with three.js code
    Now create a file “template_basic.js” to hold the code listed below. This is the three.js first example code with two lines added for orbit controls. With orbit controls, you will be able to rotate and pan scenes in your browser with your mouse (left-drag to rotate, middle-scroll to zoom, right-drag to pan).

    var scene = new THREE.Scene();
    var camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 );
    
    var renderer = new THREE.WebGLRenderer();
    renderer.setSize( window.innerWidth, window.innerHeight );
    document.body.appendChild( renderer.domElement );
    
    var geometry = new THREE.BoxGeometry( 1, 1, 1 );
    var material = new THREE.MeshBasicMaterial( { color: 0x0000ff } );
    var cube = new THREE.Mesh( geometry, material );
    scene.add( cube );
    
    camera.position.z = 6;
    controls = new THREE.OrbitControls( camera );  
    
    var render = function () {
    
    	requestAnimationFrame( render );
            cube.rotation.x += 0.01;
    	cube.rotation.y += 0.03;
    
            controls.update(); 
    
    	renderer.render(scene, camera);
    };
    
    render();

    Below is the very basic result of this very basic template code. You can use your mouse to zoom, pan, and rotate, but not much else happens. Notice that the cube doesn’t look particularly nice; that’s because we don’t have lights or more advanced materials in the basic template code.

  • Or start from a basic three.js template with lights, materials, and sliders
    The template below gives examples of the three most basic geometries in three.js, and a few of the basic material options. It also includes sliders to control some of the features that seem like they would be useful when working with 3D-printable models, namely, the ability to move, scale, and hide objects.

    // mathgrrl three.js template
    
    /////////////////////////////////////////////////////////////////////////////
    // CAMERA
    
    var camera = new THREE.PerspectiveCamera(
    	75,			                // field of view
    	window.innerWidth/window.innerHeight,	// aspect ratio
    	0.1,					// near clipping plane
    	1000					// far clipping plane	
    	);
    
    camera.position.z = 6;             // move camera position to the viewer
    
    /////////////////////////////////////////////////////////////////////////////
    // LIGHT
    
    var ambientLight = new THREE.AmbientLight( 0xffffff );	// overall light
    
    var light = new THREE.DirectionalLight( 0xffffff, 0.8);	// point light
    light.position.set( -1000, 1200, 100 );			// place the light
    
    /////////////////////////////////////////////////////////////////////////////
    // RENDERER
    
    var renderer = new THREE.WebGLRenderer( { antialias: true } );
    
    renderer.setSize( window.innerWidth, window.innerHeight ); // defaults
    
    document.body.appendChild( renderer.domElement );	   // add to the HTML
    
    /////////////////////////////////////////////////////////////////////////////
    // CONTROLS
    
    controls = new THREE.OrbitControls(   // controls update in the render loop
    	camera,			      // rotate and pan camera w/mouse
    	renderer.domElement	      // mouse controls don't bleed into menus
    	);
    
    var gui = new dat.GUI();	      // needs dat.gui.mi.js
    
    var effectController = {};	      // will add to this as things are made
    	
    
    /////////////////////////////////////////////////////////////////////////////
    // SCENE
    
    var scene = new THREE.Scene();	      // the overall scene to add things to
    
    scene.add( camera );		      // add camera and light elements
    scene.add( ambientLight );
    scene.add( light );
    
    /////////////////////////////////////////////////////////////////////////////
    // CUBE
    // made all at once, basic wireframe, hex color, moved left, slider to scale
    
    var myCube = new THREE.Mesh(
    	new THREE.BoxGeometry( 
    		1, 1, 1,	      // x=width, y=height, z=depth
    		1, 1, 4		      // optional x, y, z segments
    		),
    	new THREE.MeshBasicMaterial( {
    		color: 0x00bb00,      // hex RGB color
    		wireframe: true,      // delete this for flat faces
    		wireframeLinewidth: 2 // thicker lines
    		} )
    	);
    
    myCube.position.x = -3;		      // move cube to the left
    
    scene.add( myCube );		      // add mesh object to the origin
    
    effectController.zScaleCube = 1.5;    // initialize controller value
    
    gui.add(effectController,	      // add a control to the GUI
    	"zScaleCube",		      // controller value
    	0.1, 10, 0.05		      // slider with lowest, highest, step
    	).name("scale the box");      // title of slider
    
    /////////////////////////////////////////////////////////////////////////////
    // SPHERE
    // geometry and Lambert material made first, slider to translate
    
    var mySphereGeometry = new THREE.SphereGeometry(
    	0.8,			      // radius
    	16, 8			      // widthSegments, heightSegments
    	);
    
    var mySphereMaterial = new THREE.MeshLambertMaterial( {
    	color: 0x1280FF,	      // hex RGB color
    	ambient: 0x1280FF,	      // usually best if this matches color
    	shading: THREE.FlatShading    // remove this for a smooth look
    	} );
    
    var mySphere = new THREE.Mesh(	      // make mesh from geometry and material
    	mySphereGeometry,
    	mySphereMaterial
    	);
    
    scene.add( mySphere );		      // add mesh object to the origin
    
    effectController.yPosSphere = 0;      // initialize controller value
    
    gui.add(effectController,	      // add a control to the GUI
    	"yPosSphere",		      // controller value
    	-3, 3, 0.05		      // slider with lowest, highest, step
    	).name("move the sphere");    // title of slider
    
    //////////////////////////////////////////////////////////////////////////////
    // CYLINDER
    // geometry and Phong material made first, slider for opacity
    
    var myCylinderGeometry = new THREE.CylinderGeometry(
    	0.3, 0.8,	              // radiusTop, radiusBottom
    	1.2,			      // height
    	16,	1	      	      // radiusSegments, heightSegments
    	);
    
    var myCylinderMaterial = new THREE.MeshPhongMaterial({
        color: 0xFF6699,	              // hex RGB color
        ambient: 0xFF6699,	              // usually best if this matches color
        specular: 0x080808,	              // gray is good
        shininess: 80,		      // higher is sharper reflection of lights
        transparent: true,	              // enable transparency
        opacity: 1			      // set opacity from 0 to 1
    	});
    
    var myCylinder = new THREE.Mesh(      // make mesh from geometry and material
    	myCylinderGeometry,
    	myCylinderMaterial );
    
    myCylinder.position.x = 3;	      // move cube to the right
    
    scene.add( myCylinder );	      // add mesh object to the origin
    
    effectController.oTransCylinder = 1;  // initialize controller value
    
    gui.add(effectController,	      // add a control to the GUI
    	"oTransCylinder",	      // controller value
    	0, 1.0, 0.05		      // slider with lowest, highest, step
    	).name("fade the frustum");   // title of slider
    
    /////////////////////////////////////////////////////////////////////////////
    // RENDER
    // create a render loop to draw the scene 60 times per second
    
    var render = function () {
    
    	requestAnimationFrame( render ); // pauses when browser tab switched
    
    	myCube.rotation.x += 0.005;      // set animation parameters
    	myCube.rotation.y += 0.002;
    	myCube.rotation.z += 0.001;
    	mySphere.rotation.y += 0.01;
    	myCylinder.rotation.x += 0.01;
    
    	myCube.scale.z = effectController.zScaleCube; 	// set GUI params
    	mySphere.position.y = effectController.yPosSphere;
    	myCylinderMaterial.opacity = effectController.oTransCylinder;
    	
    	controls.update();	         // update orbit controls in the loop
    
    	renderer.render(scene, camera);	 // render the scene with the camera
    };
    
    render();
    

    Below is the result of this more in-depth template code. Use the mouse to move the camera, and change the shapes with sliders.

Main Resources

  • Interactive 3D Graphics course on Udacity, by Eric Haines
    The free online Autodesk Udacity course Interactive 3D Graphics covers programming in JavaScript and three.js libraries for WebGL, and comes with online example code, a navigation syllabus, and links to demos. You also have access to code and updates at GitHub. Haines is a great instructor, and the interactive features in Udacity work seamlessly and really contribute to the learning process. Lesson 1 doesn’t have much coding in it, but things get going nicely starting in Lesson 2, so keep watching. The coding exercises at the end of each section are the most useful part of this course, and are where the rubber meets the road. You can extend code and run the results right inside Udacity! I recommend also trying to get pieces of that code to work locally, getting them to work in your own local standalone three.js file, in order to start growing your own reference/example code document.
  • Learning Three.js: The JavaScript 3D Library for WebGL, by Jos Dirksen
    Dirksen’s Learning Three.js book is well-written and easy to read, especially if you have some OpenSCAD under your belt. It comes with an example code repository which you can have sent to you from their support site, and online access to color versions of the screenshots in the book..

Lots More Resources

Maybe learn some JavaScript too

  • JavaScript is Easy videos by Source Decoded
    A “way back to the basics” foundation for learning JavaScript. Sounds like I should start here.
  • The art of the bug – Failure should be fun on Medium
    An article by Liza Daly about learning how to code by programming a videogame in JavaScript, and why that’s such a good place to start.
  • JavaScript course on CodeAcademy
    A 10-hour free online course for beginners.
  • Learning Object-Oriented JavaScript [Video], from Packt
    This one costs money, but after registering at Packt you get a 50% discount coupon for a video, so this is what I chose. It comes with downloadable example code. Once you have an account and have purchased a video, you find the video and code here.
  • JSHint online JavaScript checker
    An elegant in-browser tool that checks JavaScript code for syntax errors. JSHint is actually the default tool used by SublimeLinter. If you’re using Brackets instead of Sublime then you could cut/paste your code directly into JSHint to find syntax errors and check your code.
  • JavaScript basics on Udacity
    A resumé project, including JSON data structures. Probably a good idea to have a nice resumé anyway, right?
  • Object-oriented JavaScript on Udacity
    What is “this”, anyway? Find out here.

Fun Facts

  • RGB hexadecimal color values
    Colors can be set with a RGB/additive model using hexadecimal numbers (base 16) with the symbols 0,1,2,3,4,5,6,7,8,9,10,a,b,c,d,e,f, where a=11, b=12, …, and f=15.  A code of the form 0xRRGGBB identifies three numbers RR, GG, and BB, one for each color channel. The numbers RR, GG, and BB are expressed in hexidecimal with values from 00 (meaning ) to ff (meaning ). You can assign color many ways, including with color.setHex and color.setRGB; see the three.js Color documentation page. Here is a nice hex color picker.
  • HSL color values
    Color can also be chosen using a Hue/Saturation/Lightness (HSL) scale, and set with color.setHSL; again see the three.js Color documentation page. Here is a most excellent HSL Color Picker.
  • Finding the angle between two edges
    The dot product of two vectors is easy to compute; just take the sum of the products of the corresponding vector components. Geometrically, If two vectors and have angle between them, then . This means that the dot product of two normalized vectors is the cosine of the angle between them. Therefore, to find the angle between two edges you only need to find vectors for the edges, normalize them, take the dot product, and then apply inverse cosine.  For more information, look at this discussion on Haine’s Udacity course page and this narrative on Math Insight.
  • Gimbal lock and Euler anglespitch_roll_yaw
    Euler angles are a nested, hierarchical system of angles that can be used to rotate objects with pitch/tilt, yaw/pan, and roll. The fact that order matters when applying these angles can lead to gimbal lock, which is explained well in this Guerilla CG video.
  • Displaying polygons and meshes
    There are many object display options, including the ones in this excellent example from mrdoob. Being able to see the actual polygon mesh either transparently or opaquely could be useful when displaying 3D-printable models. We want things to be pretty, but we don’t want rendering to make the object seem smoother than it will actually be when printed. Also see the three.js Material documentation, and the list of materials when you filter the documentation for “material”.

Fail Collector

  • When in doubt, try finding the thing that you Should Have Capitalized.
  • Three.js always applies transformations by setting scale first, then rotation, then position — regardless of the order that you enter them in your code! Handy way to remember this: SRP is reverse-alphabetical. uSuRP?
  • Degrees are measured in radians, not degrees. In related news, Math.PI has a capital “P” and a capital “I”.
  • If you name something “location” then bad things might happen to you.

4 Responses

  1. Blender Man March 11, 2015 / 5:19 am

    This is just great! Any chances for a Blend4Web wisdom collector? ;)

    • mathgrrl March 19, 2015 / 8:47 pm

      Probably not – too much wisdom to collect, too little time! The current project is now OpenJSCAD…

Leave a Reply to Blender Man Cancel reply

Your email address will not be published. Required fields are marked *