Browse Source

add three-fundamentals article

Gregg Tavares 7 years ago
parent
commit
f017be56f3

+ 84 - 0
threejs/lessons/resources/frustum-3d.svg

@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="100%" height="100%" viewBox="0 0 500 568" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;">
+    <g transform="matrix(0.228829,0,0,0.228829,0,1.30074e-14)">
+        <path d="M794,893L1112.96,91.025L2137,533L1433,1180L794,893Z" style="fill:rgb(251,200,255);"/>
+    </g>
+    <g transform="matrix(0.228829,0,0,0.228829,0,0)">
+        <path d="M2137,537L2139,1265L1437,1639L1435,1177L2137,537Z" style="fill:rgb(250,189,255);"/>
+    </g>
+    <g transform="matrix(0.228829,0,0,0.228829,0,0)">
+        <path d="M1118,94L2137,537L2141,1271L1118.7,827.696L1118,94Z" style="fill:rgb(169,212,255);"/>
+    </g>
+    <g transform="matrix(0.228829,0,0,0.228829,0,0)">
+        <path d="M1116.43,831.476L2140,1269.21L1438.26,1632.75L791.467,1358.91L1116.43,831.476Z" style="fill:rgb(246,136,255);fill-opacity:0.701961;"/>
+    </g>
+    <g transform="matrix(0.228829,0,0,0.228829,0,1.30074e-14)">
+        <path d="M1435,1177L1440,1636L790,1359L794,893L1435,1177Z" style="fill:rgb(166,251,182);fill-opacity:0.498039;"/>
+    </g>
+    <g transform="matrix(0.253567,0,0,0.253567,-181.919,-73.1583)">
+        <path d="M1808,158.717L924,2351" style="fill:none;stroke:rgb(146,146,146);stroke-width:1.88px;"/>
+    </g>
+    <g transform="matrix(0.263021,0,0,0.263021,-190.655,-95.3853)">
+        <path d="M2831.34,599L924,2351" style="fill:none;stroke:rgb(146,146,146);stroke-width:1.81px;"/>
+    </g>
+    <g transform="matrix(0.270819,0,0,0.270819,-197.86,-113.719)">
+        <path d="M2831.34,1333L924,2351" style="fill:none;stroke:rgb(146,146,146);stroke-width:1.76px;"/>
+    </g>
+    <g transform="matrix(0.383111,0,0,0.383111,-301.618,-377.718)">
+        <path d="M1808,896L924,2351" style="fill:none;stroke:rgb(146,146,146);stroke-width:1.24px;"/>
+    </g>
+    <g transform="matrix(0.182724,0,0,0.165128,58.3248,64.5264)">
+        <path d="M1479,1237.24L670,852L670,1494L1479,1879.24L1479,1237.24Z" style="fill:none;stroke:black;stroke-width:5.47px;"/>
+    </g>
+    <g transform="matrix(0.289457,0,0,0.261584,60.7254,-201.55)">
+        <path d="M1479,1237.24L670,852L670,1494L1479,1879.24L1479,1237.24Z" style="fill:none;stroke:black;stroke-width:3.46px;"/>
+    </g>
+    <g transform="matrix(0.228829,0,0,0.228829,27.4343,-30.5599)">
+        <path d="M670,1030.36L993,226.717" style="fill:none;stroke:black;stroke-width:4.17px;"/>
+    </g>
+    <g transform="matrix(0.228829,0,0,0.228829,27.4343,-30.5599)">
+        <path d="M1316,1311L2016.34,667" style="fill:none;stroke:black;stroke-width:4.17px;"/>
+    </g>
+    <g transform="matrix(0.228829,0,0,0.228829,27.4343,-30.5599)">
+        <path d="M1316,1771.64L2016.34,1401" style="fill:none;stroke:black;stroke-width:4.17px;"/>
+    </g>
+    <g transform="matrix(0.228829,0,0,0.228829,27.4343,-30.5599)">
+        <path d="M670,1490L993,964" style="fill:none;stroke:black;stroke-width:4.17px;"/>
+    </g>
+    <g transform="matrix(0.125222,0,0,0.14216,-53.3157,216.672)">
+        <g transform="matrix(1.06285,0,0,1.06285,207.062,1284.72)">
+            <ellipse cx="444.5" cy="895.5" rx="52.5" ry="50.5" style="stroke:rgb(3,3,3);stroke-width:3.35px;"/>
+        </g>
+        <g transform="matrix(1,0,0,1,246.5,1652.65)">
+            <path d="M268.458,558.348C288.79,525.842 354.8,502 433,502C511.2,502 577.21,525.842 597.542,558.348C552.951,545.022 495.572,537 433,537C370.428,537 313.049,545.022 268.458,558.348Z" style="stroke:rgb(146,146,146);stroke-width:3.56px;"/>
+        </g>
+        <g transform="matrix(1,0,0,-1,246.5,2820.35)">
+            <path d="M268.458,558.348C288.79,525.842 354.8,502 433,502C511.2,502 577.21,525.842 597.542,558.348C552.951,545.022 495.572,537 433,537C370.428,537 313.049,545.022 268.458,558.348Z" style="stroke:rgb(146,146,146);stroke-width:3.56px;"/>
+        </g>
+    </g>
+    <g transform="matrix(0.451826,0.00669686,-0.00669686,0.451826,-756.233,-852.788)">
+        <text x="1932.37px" y="2074.34px" style="font-family:'Arial-BoldMT', 'Arial', sans-serif;font-weight:700;font-size:101.279px;fill:rgb(0,59,255);">zFar</text>
+    </g>
+    <g transform="matrix(0.451826,0.00669686,-0.00669686,0.451826,-831.517,-741.628)">
+        <text x="1932.37px" y="2074.34px" style="font-family:'Arial-BoldMT', 'Arial', sans-serif;font-weight:700;font-size:101.279px;fill:rgb(42,170,74);">zNear</text>
+    </g>
+    <g transform="matrix(0.451826,0.00669686,-0.00669686,0.451826,-585.526,-533.656)">
+        <text x="1932.37px" y="2074.34px" style="font-family:'Arial-BoldMT', 'Arial', sans-serif;font-weight:700;font-size:101.279px;fill:rgb(255,0,48);">fov</text>
+    </g>
+    <g transform="matrix(0.137985,0.0581395,-0.0386589,0.0917509,118.911,120.502)">
+        <path d="M212,957.03L212,908.97L594.049,908.97L594.049,864L672,933L594.049,1002L594.049,957.03L212,957.03Z" style="fill:rgb(94,194,126);"/>
+    </g>
+    <g transform="matrix(0.137985,0.0581395,-0.0393575,0.0934091,195.2,7.85882)">
+        <path d="M212,957.03L212,908.97L592.64,908.97L592.64,864L672,933L592.64,1002L592.64,957.03L212,957.03Z" style="fill:rgb(0,59,255);"/>
+    </g>
+    <g transform="matrix(0.147102,0,0,0.185936,91.5132,71.9773)">
+        <path d="M1057,1535C1138.34,1631.7 1161.96,1683.82 1099.06,1822" style="fill:none;stroke:rgb(255,0,34);stroke-width:42.65px;"/>
+    </g>
+    <g transform="matrix(-0.223056,-0.0984935,0.0924323,-0.20933,289.707,836.839)">
+        <path d="M842.5,1602L873,1674L812,1674L842.5,1602Z" style="fill:rgb(255,0,48);"/>
+    </g>
+    <g transform="matrix(0.197704,-0.16228,0.145183,0.176874,-159.117,202.268)">
+        <path d="M842.5,1602L873,1674L812,1674L842.5,1602Z" style="fill:rgb(255,0,48);"/>
+    </g>
+</svg>

+ 92 - 0
threejs/lessons/resources/scene-down.svg

@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="100%" height="100%" viewBox="0 0 650 512" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;">
+    <g transform="matrix(1.30527,4.23642e-18,0,4.39451,101.309,-502.263)">
+        <path d="M394.725,136L-60.761,136L166.621,195L177.911,195L394.725,136Z" style="fill:rgb(255,228,254);stroke:black;stroke-width:0.03px;"/>
+    </g>
+    <g transform="matrix(1,0,0,1,69,0)">
+        <g transform="matrix(1,0,0,1,266,-2.22045e-16)">
+            <path d="M256,0L256,512" style="fill:none;stroke:black;stroke-width:0.1px;"/>
+        </g>
+        <g transform="matrix(1,0,0,1,212.913,-2.22045e-16)">
+            <path d="M256,0L256,512" style="fill:none;stroke:black;stroke-width:0.1px;"/>
+        </g>
+        <g transform="matrix(1,0,0,1,159.826,-2.22045e-16)">
+            <path d="M256,0L256,512" style="fill:none;stroke:black;stroke-width:0.1px;"/>
+        </g>
+        <g transform="matrix(1,0,0,1,106.739,-2.22045e-16)">
+            <path d="M256,0L256,512" style="fill:none;stroke:black;stroke-width:0.1px;"/>
+        </g>
+        <g transform="matrix(1,0,0,1,53.6526,-2.22045e-16)">
+            <path d="M256,0L256,512" style="fill:none;stroke:black;stroke-width:0.1px;"/>
+        </g>
+        <g transform="matrix(1,0,0,1,0.565757,-2.22045e-16)">
+            <path d="M256,0L256,512" style="fill:none;stroke:black;stroke-width:1px;"/>
+        </g>
+        <g transform="matrix(1,0,0,1,-52.5211,-2.22045e-16)">
+            <path d="M256,0L256,512" style="fill:none;stroke:black;stroke-width:0.1px;"/>
+        </g>
+        <g transform="matrix(1,0,0,1,-105.608,-2.22045e-16)">
+            <path d="M256,0L256,512" style="fill:none;stroke:black;stroke-width:0.1px;"/>
+        </g>
+        <g transform="matrix(1,0,0,1,-158.695,-2.22045e-16)">
+            <path d="M256,0L256,512" style="fill:none;stroke:black;stroke-width:0.1px;"/>
+        </g>
+        <g transform="matrix(1,0,0,1,-211.695,-2.22045e-16)">
+            <path d="M256,0L256,512" style="fill:none;stroke:black;stroke-width:0.1px;"/>
+        </g>
+        <g transform="matrix(1,0,0,1,-264.782,-2.22045e-16)">
+            <path d="M256,0L256,512" style="fill:none;stroke:black;stroke-width:0.1px;"/>
+        </g>
+    </g>
+    <g transform="matrix(6.12323e-17,-1,1.26953,7.77364e-17,8.52951e-14,512)">
+        <g transform="matrix(1,0,0,1,212.913,-2.22045e-16)">
+            <path d="M256,0L256,512" style="fill:none;stroke:black;stroke-width:0.09px;"/>
+        </g>
+        <g transform="matrix(1,0,0,1,159.826,-2.22045e-16)">
+            <path d="M256,0L256,512" style="fill:none;stroke:black;stroke-width:0.09px;"/>
+        </g>
+        <g transform="matrix(1,0,0,1,106.739,-2.22045e-16)">
+            <path d="M256,0L256,512" style="fill:none;stroke:black;stroke-width:0.09px;"/>
+        </g>
+        <g transform="matrix(1,0,0,1,53.6526,-2.22045e-16)">
+            <path d="M256,0L256,512" style="fill:none;stroke:black;stroke-width:0.09px;"/>
+        </g>
+        <g transform="matrix(1,0,0,1,0.565757,-2.22045e-16)">
+            <path d="M256,0L256,512" style="fill:none;stroke:black;stroke-width:0.88px;"/>
+        </g>
+        <g transform="matrix(1,0,0,1,-52.5211,-2.22045e-16)">
+            <path d="M256,0L256,512" style="fill:none;stroke:black;stroke-width:0.09px;"/>
+        </g>
+        <g transform="matrix(1,0,0,1,-105.608,-2.22045e-16)">
+            <path d="M256,0L256,512" style="fill:none;stroke:black;stroke-width:0.09px;"/>
+        </g>
+        <g transform="matrix(1,0,0,1,-158.695,-2.22045e-16)">
+            <path d="M256,0L256,512" style="fill:none;stroke:black;stroke-width:0.09px;"/>
+        </g>
+        <g transform="matrix(1,0,0,1,-211.782,-2.22045e-16)">
+            <path d="M256,0L256,512" style="fill:none;stroke:black;stroke-width:0.09px;"/>
+        </g>
+    </g>
+    <g transform="matrix(1,0,0,1,69,0)">
+        <text x="278.451px" y="492.641px" style="font-family:'Arial-BoldMT', 'Arial', sans-serif;font-weight:700;font-size:36px;">+Z</text>
+    </g>
+    <g transform="matrix(1,0,0,1,69,-457)">
+        <text x="278.451px" y="492.641px" style="font-family:'Arial-BoldMT', 'Arial', sans-serif;font-weight:700;font-size:36px;">-Z</text>
+    </g>
+    <g transform="matrix(1,0,0,1,-270,-200)">
+        <text x="278.451px" y="492.641px" style="font-family:'Arial-BoldMT', 'Arial', sans-serif;font-weight:700;font-size:36px;">-X</text>
+    </g>
+    <g transform="matrix(1,0,0,1,318,-200)">
+        <text x="278.451px" y="492.641px" style="font-family:'Arial-BoldMT', 'Arial', sans-serif;font-weight:700;font-size:36px;">+X</text>
+    </g>
+    <g transform="matrix(-0.999975,-0.00701169,0.00701169,-0.999975,580.462,723.793)">
+        <path d="M257,335L270,361L244,361L257,335Z" style="fill:rgb(170,170,170);stroke:black;stroke-width:1px;"/>
+    </g>
+    <g transform="matrix(1,0,0,1,68.9088,18)">
+        <rect x="237" y="361" width="40" height="40" style="fill:rgb(170,170,170);stroke:black;stroke-width:1px;"/>
+    </g>
+    <g transform="matrix(1,0,0,1,69,0)">
+        <rect x="221.909" y="221" width="70" height="70" style="fill:rgb(68,170,136);stroke:black;stroke-width:1px;"/>
+    </g>
+</svg>

+ 275 - 3
threejs/lessons/threejs-fundamentals.md

@@ -1,12 +1,284 @@
 Title: Three.js Fundamentals
 Description: Your first Three.js lesson starting with the fundamentals
 
-Coming Soon
+This is the first article in a series of articles about three.js.
+[Three.js](http://threejs.org) is a 3D library that tries to make
+it as easy as possible to get 3D content on a webpage.
 
-    // code coming soon too
+Three.js is often confused with WebGL since more often than
+not, but not always, three.js uses WebGL to draw 3D.
+WebGL is a very low-level
+system that only draws points, lines, and triangles. To do
+anything useful with WebGL generally requires quite a bit of
+code and that is where three.js comes in. It handlings things
+like scenes, lights, shadows, materials, textures, all things that you'd
+have to write yourself if you were to use WebGL directly.
 
-And examples
+These tutorials assume you already know JavaScript and, for the
+most part they will use ES6 style JavaScript. Most browsers
+that support three.js are auto-updated so most users should
+be able to run this code. If you'd like to make this code run
+on older browsers look into a transpiler like [Babel](http://babel.io).
+
+When learning most programming languages the first thing people
+do is make the computer print `"Hello World!"`. For 3D one
+of the most common first things to do is to make a 3D cube.
+so let's start with "Hello Cube!"
+
+The first thing we need is a `<canvas>` tag so
+
+```
+<body>
+  <canvas id="c"></canvas>
+</body>
+```
+
+Three.js will draw into that canvas so we need to look it
+and pass it to three.js.
+
+```
+<script>
+'use strict';
+
+function main() {
+  const canvas = document.querySelector('#c');
+  const renderer = new THREE.WebGLRenderer({canvas: canvas});
+  ...
+</script>
+```
+
+Note there are some esoteric details here. If you don't pass a canvas
+into three.js it will create on for you but then you have to add it
+to your document. Where to add it may change depending on your use case
+and you'll have to change your code so I find that passing a canvas
+to three.js feels a little more flexible. I can put the canvas anywhere
+and the code will find it where as if I had code to insert the canvas
+into do the document I'd likely have to change that code if my usecase
+changed.
+
+After we look up the canvas we create a `WebGLRenderer`. The renderer
+is the thing responsible for actually taking all the data you provide
+and to render it to the canvas. In the past there have been other renderers
+like `CSSRenderer`, a `CanvasRenderer` and in the future there may be
+`WebGL2Renderer` or `WebGPURenderer`. For now there's the `WebGLRednerer`
+that uses WebGL to render 3D to the canvas.
+
+Next up we need a camera.
+
+```
+const fov = 75;
+const aspect = 2;  // the canvas default
+const zNear = 0.1;
+const zFar = 5;
+const camera = new THREE.PerspectiveCamera(fov, aspect, zNear, zFar);
+```
+
+`fov` is short for `field of view`. In this case 75 degrees in the vertical
+dimension. Note that most angles in three.js are in radians but for some
+reason the perspective camera takes degrees.
+
+`aspect` is the display aspect of the canvas. We'll go over the details
+in another article but by default a canvas is 300x150 pixels which makes
+the aspect 300/150 or 2.
+
+`zNear` and `zFar` represent the space in front of the camera
+that will be rendered. Anything before that range or after that range
+will be clipped (not drawn).
+
+Those 4 settings define a *"frustum"*. A *frustum* is the name of
+a 3d shape that is like a pyramid with the tip sliced off. In other
+words think of the word "frustum" as another 3D shape like sphere,
+cube, prism, frustum.
+
+<img src="resources/frustum-3d.svg" width="500" class="threejs_center"/>
+
+The height of the zNear and zFar planes are determined by the field of view.
+The width of both planes is determined by the field of view and the aspect.
+
+Anything inside the defined frustum will be be drawn. Anything outside
+will not.
+
+The camera defaults to looking down the -Z axis with +Y up. We'll put our cube
+at the origin so we need to move the camera back a litte from the origin
+in order to see anything.
+
+```
+camera.position.z = 2;
+```
+
+Here's what we're aiming for.
+
+<img src="resources/scene-down.svg" width="500" class="threejs_center"/>
+
+In the diagram above we can see our camera is at `z = 2`. It's looking
+down the -Z axis. Our frustum starts 0.1 units from the front of the camera
+and goes to 5 units in front of the camera. Because we are looking down
+the field of view is affected by the aspect. Our canvas is twice as wide
+as it is tall so across view the field of view will be much wider than
+our specified 75 degrees.
+
+Next we make a `Scene`. A `Scene` in three.js is a form of scene graph.
+Anything you want three.js to draw needs to be added to the scene. We'll
+cover more details of how scenes work in a future article.
+
+```
+const scene = new THREE.Scene();
+```
+
+Next up we create a `BoxGeometry` which contains the data for a box.
+Almost anything we want to display in Three.js needs geometry which defines
+the vertices that make up our 3D object.
+
+```
+const boxWidth = 1;
+const boxHeight = 1;
+const boxDepth = 1;
+const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);
+```
+
+We then create a basic material and set its color. Colors can
+be specified using standard CSS style 6 digit hex color values.
+
+```
+const material = new THREE.MeshBasicMaterial({color: 0x44aa88});
+```
+
+We then create a `Mesh`. A `Mesh` in three represents the combination
+of a `Geometry` (the shape of the object) and a `Material` (how to draw
+the object, shiny or flat, what color, what texture(s) to apply. Etc.)
+as well as the position, orientation, and scale of that
+object in the scene.
+
+```
+const cube = new THREE.Mesh(geometry, material);
+```
+
+And finally we add that mesh to the scene
+
+```
+scene.add(cube);
+```
+
+We can then render the scene by calling the renderer's render function
+and passing it the scene and the camera
+
+```
+renderer.render(scene, camera);
+```
+
+Here's a working exmaple
 
 {{{example url="../threejs-fundamentals.html" }}}
 
+It's kind of hard to tell that is a 3D box since we're viewing
+it directly down the -Z axis and the box itself is axis aligned
+so we're only seeing a single face.
+
+Let's animate it spinning and hopefully that will make
+it clear it's being drawn in 3D.
+
+To animate it we'll render inside a render loop using
+[`requestAnimationFrame`](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame).
+
+Here's our loop
+
+```
+function render(time) {
+  time *= 0.001;  // convert time to seconds
+
+  cube.rotation.x = time;
+  cube.rotation.y = time;
+
+  renderer.render(scene, camera);
+
+  requestAnimationFrame(render);
+}
+requestAnimationFrame(render);
+```
+
+`requestAnimationFrame` is a request to the browser that you want to animate something.
+You pass it a function to be called. In our case that function is `render`. The browser
+will call your function and if you update anything related to the display of the
+page the browser will re-render the page. In our case we are calling three's
+`renderer.render` function which will draw our scene.
+
+`requestAnimationFrame` passes the time since the page started rendering to
+the our function. That time is passed in milliseconds. I find it's much
+easier to work with seconds here we're converting that to seconds.
+
+We then set the cube's X and Y rotation to the current time. These rotations
+are in [radians](https://en.wikipedia.org/wiki/Radian). There are 2 pi radians
+in a circle so our cube should turn around once on each axis in about 6.28
+seconds.
+
+We then render the scene and request another animation frame to continue
+our loop.
+
+Outside the loop we call `requestAnimationFrame` one time to start the loop.
+
+{{{example url="../threejs-fundamentals-with-animation.html" }}}
+
+Let's add 2 more cubes.
+
+We'll use the same geometry for each cube but make a different
+material so each cube can be a different color.
+
+First we'll make a function that creates a new material
+with the specified color. Then it creates a mesh using
+the specified geometry and adds it to the scene and
+sets it's X position.
+
+```
+function makeInstance(geometry, color, x) {
+  const material = new THREE.MeshBasicMaterial({color});
+
+  const cube = new THREE.Mesh(geometry, material);
+  scene.add(cube);
+
+  cube.position.x = x;
+
+  return cube;
+}
+```
+
+Then we'll call it 3 times with 3 different colors and X positions
+saving the `Mesh` instances in an array.
+
+```
+const cubes = [
+  makeInstance(geometry, 0x44aa88,  0),
+  makeInstance(geometry, 0x8844aa, -2),
+  makeInstance(geometry, 0xaa8844,  2),
+];
+```
+
+Finally we'll spin all 3 cubes in our render function. We
+compute a slightly different rotation for each one.
+
+```
+function render(time) {
+  time *= 0.001;  // convert time to seconds
+
+  cubes.forEach((cube, ndx) => {
+    const speed = 1 + ndx * .1;
+    const rot = time * speed;
+    cube.rotation.x = rot;
+    cube.rotation.y = rot;
+  });
+
+  ...
+```
+
+and here's that.
+
+{{{example url="../threejs-fundamentals-3-cubes.html" }}}
+
+If you compare it to the top down diagram above you can see
+it matches our expectections. With cubes at X = -2 and X = +2
+they are partially outside our frustum. They are also
+somewhat exaggeratedly warped since the field of view
+across the canvas is so extreme.
+
+I hope this short intro helps to get things started. [Next up we'll cover
+making our code responsive so it is adaptable to multiple situations](three-responsive.html).
 

+ 73 - 0
threejs/threejs-fundamentals-3-cubes.html

@@ -0,0 +1,73 @@
+<!-- Licensed under a BSD license. See license.html for license -->
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
+    <title>Three.js - Fundamentals 3 cubes</title>
+  </head>
+  <body>
+    <canvas id="c"></canvas>
+  </body>
+<script src="resources/threejs/r93/three.min.js"></script>
+<script src="resources/threejs-lessons-helper.js"></script> <!-- you can and should delete this script. it is only used on the site to help with errors -->
+<script>
+'use strict';
+
+function main() {
+  const canvas = document.querySelector('#c');
+  const renderer = new THREE.WebGLRenderer({canvas: canvas});
+
+  const fov = 75;
+  const aspect = 2;  // the canvas default
+  const zNear = 0.1;
+  const zFar = 5;
+  const camera = new THREE.PerspectiveCamera(fov, aspect, zNear, zFar);
+  camera.position.z = 2;
+
+  const scene = new THREE.Scene();
+
+  const boxWidth = 1;
+  const boxHeight = 1;
+  const boxDepth = 1;
+  const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);
+
+  function makeInstance(geometry, color, x) {
+    const material = new THREE.MeshBasicMaterial({color});
+
+    const cube = new THREE.Mesh(geometry, material);
+    scene.add(cube);
+
+    cube.position.x = x;
+
+    return cube;
+  }
+
+  const cubes = [
+    makeInstance(geometry, 0x44aa88,  0),
+    makeInstance(geometry, 0x8844aa, -2),
+    makeInstance(geometry, 0xaa8844,  2),
+  ];
+
+  function render(time) {
+    time *= 0.001;  // convert time to seconds
+
+    cubes.forEach((cube, ndx) => {
+      const speed = 1 + ndx * .1;
+      const rot = time * speed;
+      cube.rotation.x = rot;
+      cube.rotation.y = rot;
+    });
+
+    renderer.render(scene, camera);
+
+    requestAnimationFrame(render);
+  }
+  requestAnimationFrame(render);
+
+}
+
+main();
+</script>
+</html>
+

+ 57 - 0
threejs/threejs-fundamentals-with-animation.html

@@ -0,0 +1,57 @@
+<!-- Licensed under a BSD license. See license.html for license -->
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
+    <title>Three.js - Fundamentals with animation</title>
+  </head>
+  <body>
+    <canvas id="c"></canvas>
+  </body>
+<script src="resources/threejs/r93/three.min.js"></script>
+<script src="resources/threejs-lessons-helper.js"></script> <!-- you can and should delete this script. it is only used on the site to help with errors -->
+<script>
+'use strict';
+
+function main() {
+  const canvas = document.querySelector('#c');
+  const renderer = new THREE.WebGLRenderer({canvas: canvas});
+
+  const fov = 75;
+  const aspect = 2;  // the canvas default
+  const zNear = 0.1;
+  const zFar = 5;
+  const camera = new THREE.PerspectiveCamera(fov, aspect, zNear, zFar);
+  camera.position.z = 2;
+
+  const scene = new THREE.Scene();
+
+  const boxWidth = 1;
+  const boxHeight = 1;
+  const boxDepth = 1;
+  const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);
+
+  const material = new THREE.MeshBasicMaterial({color: 0x44aa88});  // greenish blue
+
+  const cube = new THREE.Mesh(geometry, material);
+  scene.add(cube);
+
+  function render(time) {
+    time *= 0.001;  // convert time to seconds
+
+    cube.rotation.x = time;
+    cube.rotation.y = time;
+
+    renderer.render(scene, camera);
+
+    requestAnimationFrame(render);
+  }
+  requestAnimationFrame(render);
+
+}
+
+main();
+</script>
+</html>
+

+ 10 - 17
threejs/threejs-fundamentals.html

@@ -21,30 +21,23 @@ function main() {
   const fov = 75;
   const aspect = 2;  // the canvas default
   const zNear = 0.1;
-  const zFar = 1000;
+  const zFar = 5;
   const camera = new THREE.PerspectiveCamera(fov, aspect, zNear, zFar);
-  const scene = new THREE.Scene();
-
-  const geometry = new THREE.BoxGeometry(1, 1, 1);
-  const material = new THREE.MeshBasicMaterial({color: 0x44aa88});
-  const cube = new THREE.Mesh(geometry, material);
-  scene.add(cube);
-
   camera.position.z = 2;
 
-  function render(time) {
-    // convert time to seconds
-    time *= 0.001;
+  const scene = new THREE.Scene();
 
-    cube.rotation.x = time;
-    cube.rotation.y = time;
+  const boxWidth = 1;
+  const boxHeight = 1;
+  const boxDepth = 1;
+  const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);
 
-    renderer.render(scene, camera);
+  const material = new THREE.MeshBasicMaterial({color: 0x44aa88});  // greenish blue
 
-    requestAnimationFrame(render);
-  }
+  const cube = new THREE.Mesh(geometry, material);
+  scene.add(cube);
 
-  requestAnimationFrame(render);
+  renderer.render(scene, camera);
 }
 
 main();