Browse Source

add responsive article

Gregg Tavares 7 years ago
parent
commit
c9a6c418ea

File diff suppressed because it is too large
+ 1 - 0
3rdparty/split.min.js


BIN
threejs/lessons/resources/images/resize-correct-aspect.png


BIN
threejs/lessons/resources/images/resize-incorrect-aspect.png


BIN
threejs/lessons/resources/images/resize-low-res.png


+ 34 - 4
threejs/lessons/threejs-fundamentals.md

@@ -140,7 +140,7 @@ 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});
+const material = new THREE.MeshPhongMaterial({color: 0x44aa88});
 ```
 
 We then create a `Mesh`. A `Mesh` in three represents the combination
@@ -218,7 +218,37 @@ 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.
+It's still hard to see the 3d. What would help is to add some lighting
+so let's add a light. There are many kinds of lights in three.js which
+we'll go over in a future article. For now let's create a directional
+light.
+
+```
+const light = new THREE.DirectionalLight(0xffffff, 1);
+light.position.set(-1, 2, 4);
+scene.add(light);
+```
+
+Directional lights have a position and a target. Both default to 0, 0, 0. In our
+case we're setting the light's position to -1, 2, 4 so it's slightly on the left
+above and behind our camera. The target is still 0, 0, 0 so it will shine
+toward the origin.
+
+We also need to change the material. The `MeshBasicMaterial` is not affected by
+lights. Let's change it to a `MeshPhongMaterial` which is affected by lights.
+
+```
+-const material = new THREE.MeshBasicMaterial({color: 0x44aa88});  // greenish blue
++const material = new THREE.MeshPhongMaterial({color: 0x44aa88});  // greenish blue
+```
+
+And here it is working.
+
+{{{example url="../threejs-fundamentals-with-light.html" }}}
+
+It should now be pretty clearly 3D.
+
+Just for the fun of it 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.
@@ -230,7 +260,7 @@ sets it's X position.
 
 ```
 function makeInstance(geometry, color, x) {
-  const material = new THREE.MeshBasicMaterial({color});
+  const material = new THREE.MeshPhongMaterial({color});
 
   const cube = new THREE.Mesh(geometry, material);
   scene.add(cube);
@@ -280,5 +310,5 @@ 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).
+making our code responsive so it is adaptable to multiple situations](threejs-responsive.html).
 

+ 271 - 0
threejs/lessons/threejs-responsive.md

@@ -0,0 +1,271 @@
+Title: Three.js Responsive Design
+Description: How to make your three.js fit different sized displays.
+
+This is the second article in a series of articles about three.js.
+The first article was [about fundamentals](threejs-fundamentals.html).
+If you haven't read that yet you might want to start there.
+
+This article is about how to make your three.js app be responsive
+to any situation. Making a webpage responsive generally refers
+to the page displaying well on different sized displays from
+desktops to tablets to phones.
+
+For three.js there are even more situations to consider. For
+example a 3D editor with controls on the left, right, top, or
+bottom is something we might want to handle. A live diagram
+in the middle of a document is another example.
+
+The last sample we had used a plain canvas with no css and
+no size
+
+```
+<canvas id="c"></canvas>
+```
+
+That canvas defaults to 300x150 css pixels in size.
+
+In the web platform the recommend way to size set the size
+of something is to use CSS.
+
+Let's make the canvas fill the page by adding CSS
+
+```
+<style>
+html, body {
+   margin: 0;
+   height: 100%;
+}
+#c {
+   width: 100%;
+   height: 100%;
+   display: block;
+}
+</style>
+```
+
+In HTML the body has a margin of 5px pixels by default so setting the
+margin to 0 removes margin. Setting the html and body height to 100%
+makes them fill the window. Otherwise they are only as large
+as the content that fills them.
+
+Next we tell the `id=c` element to be
+100% the size of it's container which in this case is the body of
+the document.
+
+Finally we set it's `display` mode to `block`. The canvas's
+default display mode is `inline`. Inline
+elements can end up adding whitespace to what is displayed. By
+setting the canvas to `block` that issue goes away.
+
+Here's the result
+
+{{{example url="../threejs-responsive-no-resize.html" }}}
+
+You can see the canvas is now filling the page but there are 2
+problems. One our cubes are stretched. They are not cubes they
+are more like boxes. Too tall or too wide. Open the
+example in it's own window and resize it. You'll see how
+the cubes get stretched wide and tall.
+
+<img src="resources/images/resize-incorrect-aspect.png" width="407" class="threejs_center">
+
+The second problem is they look low resolution or blocky and
+blurry. Stretch the window really large and you'll really see
+the issue.
+
+<img src="resources/images/resize-low-res.png" class="threejs_center">
+
+Let's fix the stretchy problem first. To do that we need
+to set the aspect of the camera to the aspect of the canvas's
+display size. We can do that by looking at the canvas's
+`clientWidth` and `clientHeight` properties.
+
+We'll update our render loop like this
+
+```
+function render(time) {
+  time *= 0.001;
+
++  const canvas = renderer.domElement;
++  camera.aspect = client.clientWidth / client.clientHeight;
++  camera.updateProjectionMatrix();
+
+  ...
+```
+
+Now the cubes should stop being stretched.
+
+{{{example url="../threejs-responsive-update-camera.html" }}}
+
+Open the example in a separate window and resize the window
+and you should see the cubes are no longer stretched tall or wide.
+They stay the correct aspect regardless of window size.
+
+<img src="resources/images/resize-correct-aspect.png" width="407" class="threejs_center">
+
+Now let's fix the blockiness.
+
+Canvas's have 2 sizes. One size is the size the canvas is displayed
+in the page. That's what we set with CSS. The other size is the
+number of pixels in the canvas itself. This is no different than an image.
+For example we might have a 128x64 image and using
+css we might display as 400x200
+
+```
+<img src="some128x64image.jpg" style="width:400px; height:200px">
+```
+
+A canvas's internal size, its resolution, is often called it's drawingbuffer size.
+In three.js we can set the canvas's drawingbuffer size by calling `renderer.setSize`.
+What size should we pick? The most obvious answer is "the same size the canvas is displayed".
+Again, to do that we can look at the canvas's `clientWidth` and `clientHeight`
+attributes.
+
+Let's write a function that checks if the renderer's canvas is not
+already the size it is being displayed as and if so set its size.
+
+```
+function resizeRendererToDisplaySize(renderer) {
+  const canvas = renderer.domElement;
+  const width = canvas.clientWidth;
+  const height = canvas.clientHeight;
+  const needResize = canvas.width !== width || canvas.height !== height;
+  if (needResize) {
+    renderer.setSize(width, height, false);
+  }
+  return needResize;
+}
+```
+
+Notice we check if the canvas actually needs to be resized. Resizing the canvas
+is an interesting part of the canvas spec and it's best not to set the same
+size if it's already the size we want.
+
+Once we know if we need to resize or not we then call `renderer.setSize` and
+pass in the new width and height. It's important to pass `false` at the end.
+`render.setSize` by default sets the canvas's CSS size but doing so is not
+what we want. We want the browser to continue to work how it does for all other
+elements which is to use CSS to determine the size of the element. We don't
+want canvases used by three to be different.
+
+Note that our function returns true if the canvas was resized. We can use
+this to check if there are other things we should update. Let's modify
+our render loop to use the new function
+
+```
+function render(time) {
+  time *= 0.001;
+
++  if (resizeRendererToDisplaySize(renderer)) {
++    const canvas = renderer.domElement;
++    camera.aspect = client.clientWidth / client.clientHeight;
++    camera.updateProjectionMatrix();
++  }
+
+  ...
+```
+
+Since the apsect is only going to change if the canvas's display size
+changed we only set the camera's aspect if `resizeRendererToDisplaySize`
+returns `true`.
+
+{{{example url="../threejs-responsive.html" }}}
+
+It should now render with the a resolution that matches the display
+size of the canvas.
+
+To make the point about letting CSS handle the resizing let's take
+our code and put it in a [separate `.js` file](../resources/threejs-responsive.js).
+Here then are a few more examples where we let CSS choose the size and notice we had
+to change zero code for them to work.
+
+Let's put our cubes in the middle of a paragraph of text.
+
+{{{example url="../threejs-responsive-paragraph.html" startPane="html" }}}
+
+and here's our same code used in an editor style layout
+where the control area can be resized
+
+{{{example url="../threejs-responsive-editor.html" startPane="html" }}}
+
+The important part to notice is no code changed. Only our HTML and CSS
+changed.
+
+## Handling HD-DPI displays
+
+HD-DPI stands for high-density dot per inch displays.
+That's most Mac's now a days and many windows machines
+as well as pretty much all smartphones.
+
+The way this works in the browser is they use
+use CSS pixels to set the sizes which are suppose to be the same
+regardless of how high res the display is. The browser
+will the just render everything with more detail but the
+same physical size.
+
+There are various ways to handle HD-DPI with three.js.
+
+The first one is just not to do anything special. This
+is arguably the most common. Rendering 3D graphics
+takes a lot of GPU processing power. Mobile GPUs have
+less power than desktops, at least as of 2018, and yet
+mobile phones often have very high resolution displays.
+The current top of the line phones have a HD-DPI ratio
+of 3x meaning for every one pixel from a non-HD-DPI display
+those phones have 9 pixels. That means the have to do 9x
+the rendering.
+
+Computing 9x the pixels is a lot of work so if we just
+leave the code as it is we'll compute 1x the pixels and the
+browser will just draw it at 3x the size (3x by 3x = 9x pixels).
+
+For any heavy three.js app that's probably what you want
+otherwise you're likely to get a slow framerate.
+
+That said if you actually do want to render at the resolution
+of the device there are a couple of ways to do this in three.js
+
+One is to tell three.js a resolution multiplier using `renderer.setPixelRatio`.
+You ask the browser what the multiplier is from CSS pixels to device pixels
+and pass that to three.js
+
+     renderer.setPixelRatio(window.devicePixelRatio);
+
+After that any calls to `renderer.setSize` will magicially
+use the size you request multiplied by whatever pixel ratio
+you passed in.
+
+The other way is to do it yourself when you resize the canvas.
+
+```
+    function resizeRendererToDisplaySize(renderer) {
+      const canvas = renderer.domElement;
+      const pixelRatio = window.devicePixelRatio;
+      const width = canvas.clientWidth * pixelRatio;
+      const height = canvas.clientHeight * pixelRatio;
+      const needResize = canvas.width !== width || canvas.height !== height;
+      if (needResize) {
+        renderer.setSize(width, height, false);
+      }
+      return needResize;
+    }
+```
+
+I prefer this second way. Why? Because it means I get what I ask for.
+There are many cases when using three.js where we need to know the actual
+size of the canvas's drawingBuffer. For example when making a post processing filter,
+or if we are making a shader that accesses `gl_FragCoord`, etc...
+By doing it oursevles we always know the size uses is the size we requested.
+There is no special case where magic is happening behind the scenes.
+
+Here's an example using the code above.
+
+{{{example url="../threejs-responsive-hd-dpi.html" }}}
+
+It might be hard to see the difference but if you have an HD-DPI
+display and you compare this sample to those above you should
+notice the edges are more crisp.
+
+This article covered a very basic but fundamental topic. Next up lets quickly
+go over the basic primitives that three.js provides.
+

+ 73 - 0
threejs/threejs-responsive-editor.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 - Responsive Editor</title>
+    <style>
+    body {
+      margin: 0;
+      font-size: 16pt;
+    }
+    #editor {
+      display: flex;
+      width: 100vw;
+      height: 100vh;
+    }
+    #controls {
+    }
+    #c {
+      width: 100%;
+      height: 100%;
+    }
+    .gutter {
+        background-color: #eee;
+        background-repeat: no-repeat;
+        background-position: 50%;
+    }
+    .gutter.gutter-horizontal {
+        cursor: ew-resize;
+        background-image:  url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg==')
+    }
+    </style>
+  </head>
+  <body>
+    <div id="editor">
+      <div id="view"><canvas id="c"></canvas></div>
+      <div id="controls">
+        <div>
+          <p>various controls
+             would appear here</p>
+           <div>Drag this bar</div>
+           <div>⇐</div>
+        </div>
+      </div>
+    </div>
+  </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 src="threejs-responsive.js"></script>
+<script src="../3rdparty/split.min.js"></script>
+<script>
+'use strict';
+
+// This code is only related to handling the split.
+// Our three.js code has not changed
+Split(['#view', '#controls'], {
+  sizes: [75, 25],
+  minSize: 100,
+  elementStyle: (dimension, size, gutterSize) => {
+    return {
+      'flex-basis': `calc(${size}% - ${gutterSize}px)`,
+    };
+  },
+  gutterStyle: (dimension, gutterSize) => {
+    return {
+      'flex-basis': `${gutterSize}px`,
+    };
+  },
+});
+</script>
+</html>
+

+ 106 - 0
threejs/threejs-responsive-hd-dpi.html

@@ -0,0 +1,106 @@
+<!-- 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 - Responsive HD-DPI</title>
+    <style>
+    html, body {
+        margin: 0;
+        height: 100%;
+    }
+    #c {
+        width: 100%;
+        height: 100%;
+        display: block;
+    }
+    </style>
+  </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 light = new THREE.DirectionalLight(0xffffff, 1);
+  light.position.set(-1, 2, 4);
+  scene.add(light);
+
+  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.MeshPhongMaterial({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 resizeRendererToDisplaySize(renderer) {
+    const canvas = renderer.domElement;
+    const pixelRatio = window.devicePixelRatio;
+    const width = canvas.clientWidth * pixelRatio;
+    const height = canvas.clientHeight * pixelRatio;
+    const needResize = canvas.width !== width || canvas.height !== height;
+    if (needResize) {
+      renderer.setSize(width, height, false);
+    }
+    return needResize;
+  }
+
+  function render(time) {
+    time *= 0.001;
+
+    if (resizeRendererToDisplaySize(renderer)) {
+      const canvas = renderer.domElement;
+      camera.aspect = canvas.clientWidth / canvas.clientHeight;
+      camera.updateProjectionMatrix();
+    }
+
+    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>
+

+ 88 - 0
threejs/threejs-responsive-no-resize.html

@@ -0,0 +1,88 @@
+<!-- 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 - Responsive no resize</title>
+    <style>
+    html, body {
+        margin: 0;
+        height: 100%;
+    }
+    #c {
+        width: 100%;
+        height: 100%;
+        display: block;
+    }
+    </style>
+  </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 light = new THREE.DirectionalLight(0xffffff, 1);
+  light.position.set(-1, 2, 4);
+  scene.add(light);
+
+  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.MeshPhongMaterial({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;
+
+    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>
+

+ 36 - 0
threejs/threejs-responsive-paragraph.html

@@ -0,0 +1,36 @@
+<!-- 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 - Responsive</title>
+    <style>
+    body {
+      font-size: 16pt;
+    }
+    #c {
+        float: left;
+        padding: 5px;
+        width: 200px;
+        height: 150px;
+    }
+    </style>
+  </head>
+  <body>
+    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.  Donec laoreet
+maximus metus, a interdum massa faucibus id.  Pellentesque in mauris elit.
+Vestibulum quis consectetur nisi. <canvas id="c"></canvas> Nulla pellentesque, sapien in
+condimentum ullamcorper, mi nisl sollicitudin felis, a ullamcorper sapien
+dui vel metus.  Nam augue nisi, elementum id diam vel, blandit imperdiet
+nunc.  Vivamus facilisis imperdiet neque id porttitor.  Mauris sapien
+felis, mollis tempus orci vitae, sollicitudin varius augue.  Nullam non
+magna id sem faucibus sollicitudin.  Proin nunc mi, rutrum et elementum
+ut, auctor eget massa.
+</p>
+  </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 src="threejs-responsive.js"></script>
+</html>
+

+ 92 - 0
threejs/threejs-responsive-update-camera.html

@@ -0,0 +1,92 @@
+<!-- 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 - Responsive update camera</title>
+    <style>
+    html, body {
+        margin: 0;
+        height: 100%;
+    }
+    #c {
+        width: 100%;
+        height: 100%;
+        display: block;
+    }
+    </style>
+  </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 light = new THREE.DirectionalLight(0xffffff, 1);
+  light.position.set(-1, 2, 4);
+  scene.add(light);
+
+  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.MeshPhongMaterial({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;
+
+    const canvas = renderer.domElement;
+    camera.aspect = canvas.clientWidth / canvas.clientHeight;
+    camera.updateProjectionMatrix();
+
+    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>
+

+ 61 - 9
threejs/threejs-responsive.html

@@ -4,7 +4,17 @@
   <head>
     <meta charset="utf-8">
     <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
-    <title>Three.js - Fundamentals</title>
+    <title>Three.js - Responsive</title>
+    <style>
+    body {
+        margin: 0;
+    }
+    #c {
+        width: 100vw;
+        height: 100vh;
+        display: block;
+    }
+    </style>
   </head>
   <body>
     <canvas id="c"></canvas>
@@ -21,22 +31,64 @@ 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);
+  camera.position.z = 2;
+
   const scene = new THREE.Scene();
 
-  const geometry = new THREE.BoxGeometry(1, 1, 1);
-  const material = new THREE.MeshBasicMaterial({color: 0x00ff00});
-  const cube = new THREE.Mesh(geometry, material);
-  scene.add(cube);
+  const light = new THREE.DirectionalLight(0xffffff, 1);
+  light.position.set(-1, 2, 4);
+  scene.add(light);
 
-  camera.position.z = 2;
+  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.MeshPhongMaterial({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 resizeRendererToDisplaySize(renderer) {
+    const canvas = renderer.domElement;
+    const width = canvas.clientWidth;
+    const height = canvas.clientHeight;
+    const needResize = canvas.width !== width || canvas.height !== height;
+    if (needResize) {
+      renderer.setSize(width, height, false);
+    }
+    return needResize;
+  }
 
   function render(time) {
     time *= 0.001;
 
-    cube.rotation.x = time;
-    cube.rotation.y = time;
+    if (resizeRendererToDisplaySize(renderer)) {
+      const canvas = renderer.domElement;
+      camera.aspect = canvas.clientWidth / canvas.clientHeight;
+      camera.updateProjectionMatrix();
+    }
+
+    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);
 

+ 78 - 0
threejs/threejs-responsive.js

@@ -0,0 +1,78 @@
+'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 light = new THREE.DirectionalLight(0xffffff, 1);
+  light.position.set(-1, 2, 4);
+  scene.add(light);
+
+  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.MeshPhongMaterial({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 resizeRendererToDisplaySize(renderer) {
+    const canvas = renderer.domElement;
+    const width = canvas.clientWidth;
+    const height = canvas.clientHeight;
+    const needResize = canvas.width !== width || canvas.height !== height;
+    if (needResize) {
+      renderer.setSize(width, height, false);
+    }
+    return needResize;
+  }
+
+  function render(time) {
+    time *= 0.001;
+
+    if (resizeRendererToDisplaySize(renderer)) {
+      const canvas = renderer.domElement;
+      camera.aspect = canvas.clientWidth / canvas.clientHeight;
+      camera.updateProjectionMatrix();
+    }
+
+    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();
+

Some files were not shown because too many files changed in this diff