Browse Source

make primitives article use threejsLessonUtils

Gregg Tavares 7 years ago
parent
commit
f9031f02fc

+ 41 - 1
threejs/lessons/resources/lesson.css

@@ -24,6 +24,37 @@ pre.prettyprint li {
     white-space: pre;
 }
 
+
+div[data-diagram] {
+  height: 100%;
+}
+.spread {
+  display: flex;
+  text-align: center;
+  margin: 2em auto 3em;
+}
+.spread div[data-diagram] {
+    height: 150px;
+}
+.spread>div {
+  flex: 1 1 auto;
+}
+.spread .code {
+  font-family: monospace;
+}
+.spread .code>div {
+  text-align: left;
+}
+#c {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100vw;
+  height: 100vh;
+  z-index: -100;
+}
+
+
 .threejs_navbar>div,
 .lesson-title,
 .lesson-comments,
@@ -94,7 +125,16 @@ pre.prettyprint li {
 .home-lang select {
     font-size: large;
 }
-
+.checkerboard {
+  background-color: #404040;
+  background-image: 
+     linear-gradient(45deg, #808080 25%, transparent 25%), 
+     linear-gradient(-45deg, #808080 25%, transparent 25%), 
+     linear-gradient(45deg, transparent 75%, #808080 75%), 
+     linear-gradient(-45deg, transparent 75%, #808080 75%);
+  background-size: 20px 20px;
+  background-position: 0 0, 0 10px, 10px -10px, -10px 0px;
+}
 .fullscreen {
     position: fixed !important;
     left: 0;

+ 168 - 0
threejs/lessons/resources/threejs-lesson-utils.js

@@ -0,0 +1,168 @@
+'use strict';
+
+window.threejsLessonUtils = {
+  init() {
+    if (this.renderer) {
+      return;
+    }
+    const canvas = document.querySelector('#c');
+    const renderer = new THREE.WebGLRenderer({canvas: canvas, alpha: true});
+    this.pixelRatio = Math.max(2, window.devicePixelRatio);
+
+    this.renderer = renderer;
+    this.renderFuncs = [];
+
+    const resizeRendererToDisplaySize = (renderer) => {
+      const canvas = renderer.domElement;
+      const width = canvas.clientWidth * this.pixelRatio;
+      const height = canvas.clientHeight * this.pixelRatio;
+      const needResize = canvas.width !== width || canvas.height !== height;
+      if (needResize) {
+        renderer.setSize(width, height, false);
+      }
+      return needResize;
+    };
+
+    // Three r93 needs to render at least once for some reason.
+    const scene = new THREE.Scene();
+    const camera = new THREE.Camera();
+
+    const render = (time) => {
+      time *= 0.001;
+
+      resizeRendererToDisplaySize(renderer);
+
+      renderer.setScissorTest(false);
+
+      // Three r93 needs to render at least once for some reason.
+      renderer.render(scene, camera);
+
+      renderer.setScissorTest(true);
+
+      // maybe there is another way. Originally I used `position: fixed`
+      // but the problem is if we can't render as fast as the browser
+      // scrolls then our shapes lag. 1 or 2 frames of lag isn't too
+      // horrible but iOS would often been 1/2 a second or worse.
+      // By doing it this way the canvas will scroll which means the
+      // worse that happens is part of the shapes scrolling on don't
+      // get drawn for a few frames but the shapes that are on the screen
+      // scroll perfectly.
+      //
+      // I'm using `transform` on the voodoo that it doesn't affect
+      // layout as much as `top` since AFAIK setting `top` is in
+      // the flow but `transform` is not though thinking about it
+      // the given we're `position: absolute` maybe there's no difference?
+      const transform = `translateY(${window.scrollY}px)`;
+      renderer.domElement.style.transform = transform;
+
+      this.renderFuncs.forEach((fn) => {
+          fn(renderer, time);
+      });
+
+      requestAnimationFrame(render);
+    };
+
+    requestAnimationFrame(render);
+  },
+  addDiagrams(diagrams) {
+    [...document.querySelectorAll('[data-diagram]')].forEach((elem) => {
+      const name = elem.dataset.diagram;
+      const info = diagrams[name];
+      if (!info) {
+        throw new Error(`no diagram: ${name}`);
+      }
+      this.addDiagram(elem, info);
+    });
+  },
+  addDiagram(elem, info) {
+    this.init();
+
+    const scene = new THREE.Scene();
+    const fov = 60;
+    const aspect = 1;
+    const zNear = 0.1;
+    const zFar = 50;
+    const camera = new THREE.PerspectiveCamera(fov, aspect, zNear, zFar);
+    camera.position.z = 15;
+    scene.add(camera);
+
+    const obj3D = info.create({scene, camera});
+    const promise = (obj3D instanceof Promise) ? obj3D : Promise.resolve(obj3D);
+
+    const root = new THREE.Object3D();
+    scene.add(root);
+
+    const controls = new THREE.TrackballControls(camera, elem);
+    controls.noZoom = true;
+    controls.noPan = true;
+
+    // add the lights as children of the camera.
+    // this is because TrackbacllControls move the camera.
+    // We really want to rotate the object itself but there's no
+    // controls for that so we fake it by putting all the lights
+    // on the camera so they move with it.
+    camera.add(new THREE.HemisphereLight(0xaaaaaa, 0x444444, .5));
+    const light = new THREE.DirectionalLight(0xffffff, 1);
+    light.position.set(-1, 2, 4 - 15);
+    camera.add(light);
+
+    let updateFunction;
+
+    promise.then((result) => {
+      const info = result instanceof THREE.Object3D ? {
+        obj3D: result,
+      } : result;
+      const { obj3D, update } = info;
+      root.add(obj3D);
+      updateFunction = update;
+    });
+
+    let oldWidth = -1;
+    let oldHeight = -1;
+
+    const render = (renderer, time) => {
+      root.rotation.x = time * .1;
+      root.rotation.y = time * .11;
+
+      const rect = elem.getBoundingClientRect();
+      if (rect.bottom < 0 || rect.top  > renderer.domElement.clientHeight ||
+          rect.right  < 0 || rect.left > renderer.domElement.clientWidth) {
+        return;
+      }
+
+      const width  = (rect.right - rect.left) * this.pixelRatio;
+      const height = (rect.bottom - rect.top) * this.pixelRatio;
+      const left   = rect.left * this.pixelRatio;
+      const top    = rect.top * this.pixelRatio;
+
+      if (width !== oldWidth || height !== oldHeight) {
+        oldWidth = width;
+        oldHeight = height;
+        controls.handleResize();
+      }
+      controls.update();
+
+      if (updateFunction) {
+        updateFunction(time);
+      }
+
+      const aspect = width / height;
+      const targetFov = THREE.Math.degToRad(60);
+      const fov = aspect >= 1
+        ? targetFov
+        : (2 * Math.atan(Math.tan(targetFov * .5) / aspect));
+
+      camera.fov = THREE.Math.radToDeg(fov);
+      camera.aspect = aspect;
+      camera.updateProjectionMatrix();
+
+      renderer.setViewport(left, top, width, height);
+      renderer.setScissor(left, top, width, height);
+      renderer.render(scene, camera);
+    };
+
+    this.renderFuncs.push(render);
+  },
+};
+
+

+ 16 - 144
threejs/lessons/resources/threejs-primitives.js

@@ -1,11 +1,9 @@
 'use strict';
 
-function main() {
+/* global threejsLessonUtils */
 
-  // even on low-res we want hi-res rendering so the lines are small
-  const pixelRatio = 2;
-
-  const primitives = {
+{
+  const diagrams = {
     BoxBufferGeometry: {
       create() {
         const width = 8;
@@ -368,9 +366,6 @@ function main() {
     },
   };
 
-  const canvas = document.querySelector('#c');
-  const renderer = new THREE.WebGLRenderer({canvas: canvas, alpha: true});
-
   function addLink(parent, name) {
     const a = document.createElement('a');
     a.href = `https://threejs.org/docs/#api/geometries/${name}`;
@@ -395,14 +390,12 @@ function main() {
     return addElem(parent, 'div', className);
   }
 
-  const primRenderFuncs = [
-    ...[...document.querySelectorAll('[data-primitive]')].map(createPrimitiveDOM),
-    ...[...document.querySelectorAll('[data-primitive-diagram]')].map(createPrimitiveDiagram),
-  ];
+  [...document.querySelectorAll('[data-diagram]')].forEach(createDiagram);
+  [...document.querySelectorAll('[data-primitive]')].forEach(createPrimitiveDOM);
 
   function createPrimitiveDOM(base) {
     const name = base.dataset.primitive;
-    const info = primitives[name];
+    const info = diagrams[name];
     if (!info) {
       throw new Error(`no primitive ${name}`);
     }
@@ -420,48 +413,21 @@ function main() {
     }
     addDiv(right, '.note').innerHTML = text;
 
-    return createPrimitive(elem, info);
+    return createLiveImage(elem, info);
   }
 
-  function createPrimitiveDiagram(base) {
-    const name = base.dataset.primitiveDiagram;
-    const info = primitives[name];
+  function createDiagram(base) {
+    const name = base.dataset.diagram;
+    const info = diagrams[name];
     if (!info) {
       throw new Error(`no primitive ${name}`);
     }
-    return createPrimitive(base, info);
+    return createLiveImage(base, info);
   }
 
-  function createPrimitive(elem, info) {
+  function createLiveImage(elem, info) {
     const geometry = info.create();
     const promise = (geometry instanceof Promise) ? geometry : Promise.resolve(geometry);
-    const scene = new THREE.Scene();
-
-    const root = new THREE.Object3D();
-    scene.add(root);
-
-    const fov = 60;
-    const aspect = 1;
-    const zNear = 0.1;
-    const zFar = 50;
-    const camera = new THREE.PerspectiveCamera(fov, aspect, zNear, zFar);
-    camera.position.z = 15;
-
-    const controls = new THREE.TrackballControls(camera, elem);
-    controls.noZoom = true;
-    controls.noPan = true;
-
-    // add the lights as children of the camera.
-    // this is because TrackbacllControls move the camera.
-    // We really want to rotate the object itself but there's no
-    // controls for that so we fake it by putting all the lights
-    // on the camera so they move with it.
-    camera.add(new THREE.HemisphereLight(0xaaaaaa, 0x444444, .5));
-    const light = new THREE.DirectionalLight(0xffffff, 1);
-    light.position.set(-1, 2, 4 - 15);
-    camera.add(light);
-    scene.add(camera);
-
     promise.then((geometryInfo) => {
       if (geometryInfo instanceof THREE.BufferGeometry ||
           geometryInfo instanceof THREE.Geometry) {
@@ -471,6 +437,8 @@ function main() {
         };
       }
 
+      const root = new THREE.Object3D();
+
       const boxGeometry = geometryInfo.geometry || geometryInfo.lineGeometry;
       boxGeometry.computeBoundingBox();
       const centerOffset = new THREE.Vector3();
@@ -497,104 +465,8 @@ function main() {
         lineMesh.position.copy(centerOffset);
         root.add(lineMesh);
       }
-    });
-
-    let oldWidth = -1;
-    let oldHeight = -1;
-
-    function render(renderer, time) {
-      root.rotation.x = time * .1;
-      root.rotation.y = time * .11;
-
-      const rect = elem.getBoundingClientRect();
-      if (rect.bottom < 0 || rect.top  > renderer.domElement.clientHeight ||
-          rect.right  < 0 || rect.left > renderer.domElement.clientWidth) {
-        return;
-      }
 
-      const width  = (rect.right - rect.left) * pixelRatio;
-      const height = (rect.bottom - rect.top) * pixelRatio;
-      const left   = rect.left * pixelRatio;
-      const top    = rect.top * pixelRatio;
-
-      if (width !== oldWidth || height !== oldHeight) {
-        oldWidth = width;
-        oldHeight = height;
-        controls.handleResize();
-      }
-      controls.update();
-
-      const aspect = width / height;
-      const targetFov = THREE.Math.degToRad(60);
-      const fov = aspect >= 1
-        ? targetFov
-        : (2 * Math.atan(Math.tan(targetFov * .5) / aspect));
-
-      camera.fov = THREE.Math.radToDeg(fov);
-      camera.aspect = aspect;
-      camera.updateProjectionMatrix();
-
-      renderer.setViewport(left, top, width, height);
-      renderer.setScissor(left, top, width, height);
-      renderer.render(scene, camera);
-    }
-
-    return render;
-  }
-
-  function resizeRendererToDisplaySize(renderer) {
-    const canvas = renderer.domElement;
-    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;
-  }
-
-  // Three r93 needs to render at least once for some reason.
-  const scene = new THREE.Scene();
-  const camera = new THREE.Camera();
-
-  function render(time) {
-    time *= 0.001;
-
-    resizeRendererToDisplaySize(renderer);
-
-    renderer.setScissorTest(false);
-
-    // Three r93 needs to render at least once for some reason.
-    renderer.render(scene, camera);
-
-    renderer.setScissorTest(true);
-
-    // maybe there is another way. Originally I used `position: fixed`
-    // but the problem is if we can't render as fast as the browser
-    // scrolls then our shapes lag. 1 or 2 frames of lag isn't too
-    // horrible but iOS would often been 1/2 a second or worse.
-    // By doing it this way the canvas will scroll which means the
-    // worse that happens is part of the shapes scrolling on don't
-    // get drawn for a few frames but the shapes that are on the screen
-    // scroll perfectly.
-    //
-    // I'm using `transform` on the voodoo that it doesn't affect
-    // layout as much as `top` since AFAIK setting `top` is in
-    // the flow but `transform` is not though thinking about it
-    // the given we're `position: absolute` maybe there's no difference?
-    const transform = `translateY(${window.scrollY}px)`;
-    renderer.domElement.style.transform = transform;
-
-    primRenderFuncs.forEach((fn) => {
-        fn(renderer, time);
+      threejsLessonUtils.addDiagram(elem, {create: () => root});
     });
-
-    requestAnimationFrame(render);
   }
-
-  requestAnimationFrame(render);
-}
-
-main();
-
-
+}

+ 10 - 24
threejs/lessons/threejs-primitives.md

@@ -1,7 +1,7 @@
 Title: Three.js Primitives
 Description: A tour of three.js primitives.
 
-This article one in a series of articles about three.js.
+This article is one 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.
 
@@ -289,9 +289,9 @@ might be the sphere geometries. Spheres take parameters for
 how many divisions to make around and how many top to bottom. For example
 
 <div class="spread">
-<div data-primitive-diagram="SphereBufferGeometryLow"></div>
-<div data-primitive-diagram="SphereBufferGeometryMedium"></div>
-<div data-primitive-diagram="SphereBufferGeometryHigh"></div>
+<div data-diagram="SphereBufferGeometryLow"></div>
+<div data-diagram="SphereBufferGeometryMedium"></div>
+<div data-diagram="SphereBufferGeometryHigh"></div>
 </div>
 
 The first sphere has 5 segments around and 3 high which is 15 segments
@@ -303,9 +303,9 @@ look like you need a high number of segments but remove the lines
 and the flat shading and we get this
 
 <div class="spread">
-<div data-primitive-diagram="SphereBufferGeometryLowSmooth"></div>
-<div data-primitive-diagram="SphereBufferGeometryMediumSmooth"></div>
-<div data-primitive-diagram="SphereBufferGeometryHighSmooth"></div>
+<div data-diagram="SphereBufferGeometryLowSmooth"></div>
+<div data-diagram="SphereBufferGeometryMediumSmooth"></div>
+<div data-diagram="SphereBufferGeometryHighSmooth"></div>
 </div>
 
 It's now not so clear that the one on the right with 5000 triangles
@@ -322,8 +322,8 @@ Sometimes it's easy to choose. For example you can also choose
 to subdivide a plane.
 
 <div class="spread">
-<div data-primitive-diagram="PlaneBufferGeometryLow"></div>
-<div data-primitive-diagram="PlaneBufferGeometryHigh"></div>
+<div data-diagram="PlaneBufferGeometryLow"></div>
+<div data-diagram="PlaneBufferGeometryHigh"></div>
 </div>
 
 The plane on the left is 2 triangles. The plane on the right
@@ -343,15 +343,9 @@ to use it](threejs-scenegraph.html).
 <canvas id="c"></canvas>
 <script src="../resources/threejs/r94/three.min.js"></script>
 <script src="../resources/threejs/r94/js/controls/TrackballControls.js"></script>
+<script src="resources/threejs-lesson-utils.js"></script>
 <script src="resources/threejs-primitives.js"></script>
 <style>
-.spread {
-  display: flex;
-}
-.spread>div {
-  flex: 1 1 auto;
-  height: 150px;
-}
 .primitives {
 }
 .primitives>div {
@@ -381,14 +375,6 @@ to use it](threejs-scenegraph.html).
 .primitives .desc {
   flex: 1 1 auto;
 }
-#c {
-  position: absolute;
-  top: 0;
-  left: 0;
-  width: 100vw;
-  height: 100vh;
-  z-index: -100;
-}
 </style>