Gregg Tavares 6 years ago
parent
commit
14227a4d10

+ 3 - 0
threejs/lessons/resources/lesson.js

@@ -370,6 +370,9 @@ $(document).ready(function($){
     window.location.href = this.value;
   });
 
+  if (window.threejsLessonUtils) {
+    window.threejsLessonUtils.afterPrettify();
+  }
 });
 }(jQuery));
 

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

@@ -2,6 +2,7 @@ import * as THREE from '../../resources/threejs/r114/build/three.module.js';
 import {TrackballControls} from '../../resources/threejs/r114/examples/jsm/controls/TrackballControls.js';
 
 export const threejsLessonUtils = {
+  _afterPrettifyFuncs: [],
   init() {
     if (this.renderer) {
       return;
@@ -206,6 +207,14 @@ export const threejsLessonUtils = {
 
     this.renderFuncs.push(render);
   },
+  onAfterPrettify(fn) {
+    this._afterPrettifyFuncs.push(fn);
+  },
+  afterPrettify() {
+    this._afterPrettifyFuncs.forEach((fn) => {
+      fn();
+    });
+  },
 };
 
 

+ 549 - 215
threejs/lessons/resources/threejs-primitives.js

@@ -13,52 +13,112 @@ import {threejsLessonUtils} from './threejs-lesson-utils.js';
   const isDarkMode = darkMatcher.matches;
   const colors = isDarkMode ? darkColors : lightColors;
 
+  const fontLoader = new THREE.FontLoader();
+  const fontPromise = new Promise((resolve) => {
+    fontLoader.load('../resources/threejs/fonts/helvetiker_regular.typeface.json', resolve);
+  });
+
   const diagrams = {
     BoxBufferGeometry: {
-      create() {
-        const width = 8;
-        const height = 8;
-        const depth = 8;
-        const geometry = new THREE.BoxBufferGeometry(width, height, depth);
-        return geometry;
+      ui: {
+        width: { type: 'range', min: 1, max: 10, precision: 1, },
+        height: { type: 'range', min: 1, max: 10, precision: 1, },
+        depth: { type: 'range', min: 1, max: 10, precision: 1, },
+        widthSegments: { type: 'range', min: 1, max: 10, },
+        heightSegments: { type: 'range', min: 1, max: 10, },
+        depthSegments: { type: 'range', min: 1, max: 10, },
+      },
+      create(width = 8, height = 8, depth = 8) {
+        return new THREE.BoxBufferGeometry(width, height, depth);
+      },
+      create2(width = 8, height = 8, depth = 8, widthSegments = 4, heightSegments = 4, depthSegments = 4) {
+        return new THREE.BoxBufferGeometry(
+            width, height, depth,
+            widthSegments, heightSegments, depthSegments);
       },
     },
     CircleBufferGeometry: {
-      create() {
-        const radius = 7;
-        const segments = 24;
-        const geometry = new THREE.CircleBufferGeometry(radius, segments);
-        return geometry;
+      ui: {
+        radius: { type: 'range', min: 1, max: 10, precision: 1, },
+        segments: { type: 'range', min: 1, max: 50, },
+        thetaStart: { type: 'range', min: 0, max: 2, mult: Math.PI },
+        thetaLength: { type: 'range', min: 0, max: 2, mult: Math.PI },
+      },
+      create(radius = 7, segments = 24) {
+        return new THREE.CircleBufferGeometry(radius, segments);
+      },
+      create2(radius = 7, segments = 24, thetaStart = Math.PI * 0.25, thetaLength = Math.PI * 1.5) {
+        return new THREE.CircleBufferGeometry(
+            radius, segments, thetaStart, thetaLength);
       },
     },
     ConeBufferGeometry: {
-      create() {
-        const radius = 6;
-        const height = 8;
-        const segments = 16;
-        const geometry = new THREE.ConeBufferGeometry(radius, height, segments);
-        return geometry;
+      ui: {
+        radius: { type: 'range', min: 1, max: 10, precision: 1, },
+        height: { type: 'range', min: 1, max: 10, precision: 1, },
+        radialSegments: { type: 'range', min: 1, max: 50, },
+        heightSegments: { type: 'range', min: 1, max: 10, },
+        openEnded: { type: 'bool', },
+        thetaStart: { type: 'range', min: 0, max: 2, mult: Math.PI },
+        thetaLength: { type: 'range', min: 0, max: 2, mult: Math.PI },
+      },
+      create(radius = 6, height = 8, radialSegments = 16) {
+        return new THREE.ConeBufferGeometry(radius, height, radialSegments);
+      },
+      create2(radius = 6, height = 8, radialSegments = 16, heightSegments = 2, openEnded = true, thetaStart = Math.PI * 0.25, thetaLength = Math.PI * 1.5) {
+        return new THREE.ConeBufferGeometry(
+            radius, height,
+            radialSegments, heightSegments,
+            openEnded,
+            thetaStart, thetaLength);
       },
     },
     CylinderBufferGeometry: {
-      create() {
-        const radiusTop = 4;
-        const radiusBottom = 4;
-        const height = 8;
-        const radialSegments = 12;
-        const geometry = new THREE.CylinderBufferGeometry(radiusTop, radiusBottom, height, radialSegments);
-        return geometry;
+      ui: {
+        radiusTop: { type: 'range', min: 0, max: 10, precision: 1, },
+        radiusBottom: { type: 'range', min: 0, max: 10, precision: 1, },
+        height: { type: 'range', min: 1, max: 10, precision: 1, },
+        radialSegments: { type: 'range', min: 1, max: 50, },
+        heightSegments: { type: 'range', min: 1, max: 10, },
+        openEnded: { type: 'bool', },
+        thetaStart: { type: 'range', min: 0, max: 2, mult: Math.PI },
+        thetaLength: { type: 'range', min: 0, max: 2, mult: Math.PI },
+      },
+      create(radiusTop = 4, radiusBottom = 4, height = 8, radialSegments = 12) {
+        return new THREE.CylinderBufferGeometry(
+            radiusTop, radiusBottom, height, radialSegments);
+      },
+      create2(radiusTop = 4, radiusBottom = 4, height = 8, radialSegments = 12, heightSegments = 2, openEnded = false, thetaStart = Math.PI * 0.25, thetaLength = Math.PI * 1.5) {
+        return new THREE.CylinderBufferGeometry(
+            radiusTop, radiusBottom, height,
+            radialSegments, heightSegments,
+            openEnded,
+            thetaStart, thetaLength);
       },
     },
     DodecahedronBufferGeometry: {
-      create() {
-        const radius = 7;
-        const geometry = new THREE.DodecahedronBufferGeometry(radius);
-        return geometry;
+      ui: {
+        radius: { type: 'range', min: 1, max: 10, precision: 1, },
+        detail: { type: 'range', min: 1, max: 5, precision: 0, },
+      },
+      create(radius = 7) {
+        return new THREE.DodecahedronBufferGeometry(radius);
+      },
+      create2(radius = 7, detail = 2) {
+        return new THREE.DodecahedronBufferGeometry(radius, detail);
       },
     },
     ExtrudeBufferGeometry: {
-      create() {
+      ui: {
+        steps: { type: 'range', min: 1, max: 10, },
+        depth: { type: 'range', min: 1, max: 20, precision: 1, },
+        bevelEnabled: { type: 'bool', },
+        bevelThickness: { type: 'range', min: 0.1, max: 3, },
+        bevelSize: { type: 'range', min: 0.1, max:3, },
+        bevelSegments: { type: 'range', min: 0, max: 8, },
+      },
+      addConstCode: false,
+      create(steps = 2, depth = 2, bevelEnabled = true, bevelThickness = 1, bevelSize = 1, bevelSegments = 2) {
         const shape = new THREE.Shape();
         const x = -2.5;
         const y = -5;
@@ -71,26 +131,59 @@ import {threejsLessonUtils} from './threejs-lesson-utils.js';
         shape.bezierCurveTo(x + 3.5, y, x + 2.5, y + 2.5, x + 2.5, y + 2.5);
 
         const extrudeSettings = {
-          steps: 2,
-          depth: 2,
-          bevelEnabled: true,
-          bevelThickness: 1,
-          bevelSize: 1,
-          bevelSegments: 2,
+          steps,
+          depth,
+          bevelEnabled,
+          bevelThickness,
+          bevelSize,
+          bevelSegments,
         };
 
         const geometry = new THREE.ExtrudeBufferGeometry(shape, extrudeSettings);
         return geometry;
       },
+      src: `
+const shape = new THREE.Shape();
+const x = -2.5;
+const y = -5;
+shape.moveTo(x + 2.5, y + 2.5);
+shape.bezierCurveTo(x + 2.5, y + 2.5, x + 2, y, x, y);
+shape.bezierCurveTo(x - 3, y, x - 3, y + 3.5, x - 3, y + 3.5);
+shape.bezierCurveTo(x - 3, y + 5.5, x - 1.5, y + 7.7, x + 2.5, y + 9.5);
+shape.bezierCurveTo(x + 6, y + 7.7, x + 8, y + 4.5, x + 8, y + 3.5);
+shape.bezierCurveTo(x + 8, y + 3.5, x + 8, y, x + 5, y);
+shape.bezierCurveTo(x + 3.5, y, x + 2.5, y + 2.5, x + 2.5, y + 2.5);
+
+const extrudeSettings = {
+  steps: 2,  // ui: steps
+  depth: 2,  // ui: depth
+  bevelEnabled: true,  // ui: bevelEnabled
+  bevelThickness: 1,  // ui: bevelThickness
+  bevelSize: 1,  // ui: bevelSize
+  bevelSegments: 2,  // ui: bevelSegments
+};
+
+const geometry = THREE.ExtrudeBufferGeometry(shape, extrudeSettings);
+`,
     },
     IcosahedronBufferGeometry: {
-      create() {
-        const radius = 7;
-        const geometry = new THREE.IcosahedronBufferGeometry(radius);
-        return geometry;
+      ui: {
+        radius: { type: 'range', min: 1, max: 10, precision: 1, },
+        detail: { type: 'range', min: 1, max: 5, precision: 0, },
+      },
+      create(radius = 7) {
+        return new THREE.IcosahedronBufferGeometry(radius);
+      },
+      create2(radius = 7, detail = 2) {
+        return new THREE.IcosahedronBufferGeometry(radius, detail);
       },
     },
     LatheBufferGeometry: {
+      ui: {
+        segments: { type: 'range', min: 1, max: 50, },
+        phiStart: { type: 'range', min: 0, max: 2, mult: Math.PI },
+        phiLength: { type: 'range', min: 0, max: 2, mult: Math.PI },
+      },
       create() {
         const points = [];
         for (let i = 0; i < 10; ++i) {
@@ -99,15 +192,31 @@ import {threejsLessonUtils} from './threejs-lesson-utils.js';
         const geometry = new THREE.LatheBufferGeometry(points);
         return geometry;
       },
+      create2(segments = 12, phiStart = Math.PI * 0.25, phiLength = Math.PI * 1.5) {
+        const points = [];
+        for (let i = 0; i < 10; ++i) {
+          points.push(new THREE.Vector2(Math.sin(i * 0.2) * 3 + 3, (i - 5) * .8));
+        }
+        return new THREE.LatheBufferGeometry(points, segments, phiStart, phiLength);
+      },
     },
     OctahedronBufferGeometry: {
-      create() {
-        const radius = 7;
-        const geometry = new THREE.OctahedronBufferGeometry(radius);
-        return geometry;
+      ui: {
+        radius: { type: 'range', min: 1, max: 10, precision: 1, },
+        detail: { type: 'range', min: 1, max: 5, precision: 0, },
+      },
+      create(radius = 7) {
+        return new THREE.OctahedronBufferGeometry(radius);
+      },
+      create2(radius = 7, detail = 2) {
+        return new THREE.OctahedronBufferGeometry(radius, detail);
       },
     },
     ParametricBufferGeometry: {
+      ui: {
+        stacks: { type: 'range', min: 1, max: 50, },
+        slices: { type: 'range', min: 1, max: 50, },
+      },
       /*
       from: https://github.com/mrdoob/three.js/blob/b8d8a8625465bd634aa68e5846354d69f34d2ff5/examples/js/ParametricGeometries.js
 
@@ -134,7 +243,7 @@ import {threejsLessonUtils} from './threejs-lesson-utils.js';
       THE SOFTWARE.
 
       */
-      create() {
+      create(slices = 25, stacks = 25) {
         // from: https://github.com/mrdoob/three.js/blob/b8d8a8625465bd634aa68e5846354d69f34d2ff5/examples/js/ParametricGeometries.js
         function klein(v, u, target) {
           u *= Math.PI;
@@ -157,24 +266,31 @@ import {threejsLessonUtils} from './threejs-lesson-utils.js';
           target.set(x, y, z).multiplyScalar(0.75);
         }
 
-        const slices = 25;
-        const stacks = 25;
-        const geometry = new THREE.ParametricBufferGeometry(klein, slices, stacks);
-        return geometry;
+        return new THREE.ParametricBufferGeometry(klein, slices, stacks);
       },
     },
     PlaneBufferGeometry: {
-      create() {
-        const width = 9;
-        const height = 9;
-        const widthSegments = 2;
-        const heightSegments = 2;
-        const geometry = new THREE.PlaneBufferGeometry(width, height, widthSegments, heightSegments);
-        return geometry;
+      ui: {
+        width: { type: 'range', min: 1, max: 10, precision: 1, },
+        height: { type: 'range', min: 1, max: 10, precision: 1, },
+        widthSegments: { type: 'range', min: 1, max: 10, },
+        heightSegments: { type: 'range', min: 1, max: 10, },
+      },
+      create(width = 9, height = 9) {
+        return new THREE.PlaneBufferGeometry(width, height);
+      },
+      create2(width = 9, height = 9, widthSegments = 2, heightSegments = 2) {
+        return new THREE.PlaneBufferGeometry(
+            width, height,
+            widthSegments, heightSegments);
       },
     },
     PolyhedronBufferGeometry: {
-      create() {
+      ui: {
+        radius: { type: 'range', min: 1, max: 10, precision: 1, },
+        detail: { type: 'range', min: 1, max: 5, precision: 0, },
+      },
+      create(radius = 7, detail = 2) {
         const verticesOfCube = [
             -1, -1, -1,    1, -1, -1,    1,  1, -1,    -1,  1, -1,
             -1, -1,  1,    1, -1,  1,    1,  1,  1,    -1,  1,  1,
@@ -187,22 +303,33 @@ import {threejsLessonUtils} from './threejs-lesson-utils.js';
             2, 3, 7,    7, 6, 2,
             4, 5, 6,    6, 7, 4,
         ];
-        const radius = 7;
-        const detail = 2;
-        const geometry = new THREE.PolyhedronBufferGeometry(verticesOfCube, indicesOfFaces, radius, detail);
-        return geometry;
+        return new THREE.PolyhedronBufferGeometry(
+            verticesOfCube, indicesOfFaces, radius, detail);
       },
     },
     RingBufferGeometry: {
-      create() {
-        const innerRadius = 2;
-        const outerRadius = 7;
-        const segments = 18;
-        const geometry = new THREE.RingBufferGeometry(innerRadius, outerRadius, segments);
-        return geometry;
+      ui: {
+        innerRadius: { type: 'range', min: 1, max: 10, precision: 1, },
+        outerRadius: { type: 'range', min: 1, max: 10, precision: 1, },
+        thetaSegments: { type: 'range', min: 1, max: 30, },
+        phiSegments: { type: 'range', min: 1, max: 10, },
+        thetaStart: { type: 'range', min: 0, max: 2, mult: Math.PI },
+        thetaLength: { type: 'range', min: 0, max: 2, mult: Math.PI },
+      },
+      create(innerRadius = 2, outerRadius = 7, thetaSegments = 18) {
+        return new THREE.RingBufferGeometry(innerRadius, outerRadius, thetaSegments);
+      },
+      create2(innerRadius = 2, outerRadius = 7, thetaSegments = 18, phiSegments = 2, thetaStart = Math.PI * 0.25, thetaLength = Math.PI * 1.5) {
+        return new THREE.RingBufferGeometry(
+            innerRadius, outerRadius,
+            thetaSegments, phiSegments,
+            thetaStart, thetaLength);
       },
     },
     ShapeBufferGeometry: {
+      ui: {
+        curveSegments: { type: 'range', min: 1, max: 30, },
+      },
       create() {
         const shape = new THREE.Shape();
         const x = -2.5;
@@ -217,25 +344,68 @@ import {threejsLessonUtils} from './threejs-lesson-utils.js';
         const geometry = new THREE.ShapeBufferGeometry(shape);
         return geometry;
       },
+      create2(curveSegments = 5) {
+        const shape = new THREE.Shape();
+        const x = -2.5;
+        const y = -5;
+        shape.moveTo(x + 2.5, y + 2.5);
+        shape.bezierCurveTo(x + 2.5, y + 2.5, x + 2, y, x, y);
+        shape.bezierCurveTo(x - 3, y, x - 3, y + 3.5, x - 3, y + 3.5);
+        shape.bezierCurveTo(x - 3, y + 5.5, x - 1.5, y + 7.7, x + 2.5, y + 9.5);
+        shape.bezierCurveTo(x + 6, y + 7.7, x + 8, y + 4.5, x + 8, y + 3.5);
+        shape.bezierCurveTo(x + 8, y + 3.5, x + 8, y, x + 5, y);
+        shape.bezierCurveTo(x + 3.5, y, x + 2.5, y + 2.5, x + 2.5, y + 2.5);
+        return new THREE.ShapeBufferGeometry(shape, curveSegments);
+      },
     },
     SphereBufferGeometry: {
-      create() {
-        const radius = 7;
-        const widthSegments = 12;
-        const heightSegments = 8;
-        const geometry = new THREE.SphereBufferGeometry(radius, widthSegments, heightSegments);
-        return geometry;
+      ui: {
+        radius: { type: 'range', min: 1, max: 10, precision: 1, },
+        widthSegments: { type: 'range', min: 1, max: 30, },
+        heightSegments: { type: 'range', min: 1, max: 30, },
+        phiStart: { type: 'range', min: 0, max: 2, mult: Math.PI },
+        phiLength: { type: 'range', min: 0, max: 2, mult: Math.PI },
+        thetaStart: { type: 'range', min: 0, max: 1, mult: Math.PI },
+        thetaLength: { type: 'range', min: 0, max: 1, mult: Math.PI },
+      },
+      create(radius = 7, widthSegments = 12, heightSegments = 8) {
+        return new THREE.SphereBufferGeometry(radius, widthSegments, heightSegments);
+      },
+      create2(radius = 7, widthSegments = 12, heightSegments = 8, phiStart = Math.PI * 0.25, phiLength = Math.PI * 1.5, thetaStart = Math.PI * 0.25, thetaLength = Math.PI * 0.5) {
+        return new THREE.SphereBufferGeometry(
+            radius,
+            widthSegments, heightSegments,
+            phiStart, phiLength,
+            thetaStart, thetaLength);
       },
     },
     TetrahedronBufferGeometry: {
-      create() {
-        const radius = 7;
-        const geometry = new THREE.TetrahedronBufferGeometry(radius);
-        return geometry;
+      ui: {
+        radius: { type: 'range', min: 1, max: 10, precision: 1, },
+        detail: { type: 'range', min: 1, max: 5, precision: 0, },
+      },
+      create(radius = 7) {
+        return new THREE.TetrahedronBufferGeometry(radius);
+      },
+      create2(radius = 7, detail = 2) {
+        return new THREE.TetrahedronBufferGeometry(radius, detail);
       },
     },
     TextBufferGeometry: {
-      async create() {
+      ui: {
+        text: { type: 'text', maxLength: 30, },
+        size: { type: 'range', min: 1, max: 10, precision: 1, },
+        height: { type: 'range', min: 1, max: 10, precision: 1, },
+        curveSegments: { type: 'range', min: 1, max: 20, },
+        // font', fonts ).onChange( generateGeometry );
+        // weight', weights ).onChange( generateGeometry );
+        bevelEnabled: { type: 'bool', },
+        bevelThickness: { type: 'range', min: 0.1, max: 3, },
+        bevelSize: { type: 'range', min: 0.1, max:3, },
+        bevelSegments: { type: 'range', min: 0, max: 8, },
+      },
+      addConstCode: false,
+      async create(text = 'three.js', size = 3, height = 0.2, curveSegments = 12, bevelEnabled = true, bevelThickness = 0.15, bevelSize = 0.3, bevelSegments = 5) {
         const loader = new THREE.FontLoader();
         // promisify font loading
         function loadFont(url) {
@@ -243,44 +413,72 @@ import {threejsLessonUtils} from './threejs-lesson-utils.js';
             loader.load(url, resolve, undefined, reject);
           });
         }
-
         const font = await loadFont('/threejs/resources/threejs/fonts/helvetiker_regular.typeface.json');
-        return new THREE.TextBufferGeometry('three.js', {
+        return new THREE.TextBufferGeometry(text, {
           font: font,
-          size: 3.0,
-          height: .2,
-          curveSegments: 12,
-          bevelEnabled: true,
-          bevelThickness: 0.15,
-          bevelSize: .3,
-          bevelSegments: 5,
+          size,
+          height,
+          curveSegments,
+          bevelEnabled,
+          bevelThickness,
+          bevelSize,
+          bevelSegments,
         });
       },
+      src: `
+const loader = new THREE.FontLoader();
+
+loader.load('../resources/threejs/fonts/helvetiker_regular.typeface.json', (font) => {
+  const text = 'three.js';  // ui: text
+  const geometry = new THREE.TextBufferGeometry(text, {
+    font: font,
+    size: 3,  // ui: size
+    height: 0.2,  // ui: height
+    curveSegments: 12,  // ui: curveSegments
+    bevelEnabled: true,  // ui: bevelEnabled
+    bevelThickness: 0.15,  // ui: bevelThickness
+    bevelSize: 0.3,  // ui: bevelSize
+    bevelSegments: 5,  // ui: bevelSegments
+  });
+  ...
+});
+      `,
     },
     TorusBufferGeometry: {
-      create() {
-        const radius = 5;
-        const tubeRadius = 2;
-        const radialSegments = 8;
-        const tubularSegments = 24;
-        const geometry = new THREE.TorusBufferGeometry(radius, tubeRadius, radialSegments, tubularSegments);
-        return geometry;
+      ui: {
+        radius: { type: 'range', min: 1, max: 10, precision: 1, },
+        tubeRadius: { type: 'range', min: 1, max: 10, precision: 1, },
+        radialSegments: { type: 'range', min: 1, max: 30, },
+        tubularSegments: { type: 'range', min: 1, max: 100, },
+      },
+      create(radius = 5, tubeRadius = 2, radialSegments = 8, tubularSegments = 24) {
+        return new THREE.TorusBufferGeometry(
+            radius, tubeRadius,
+            radialSegments, tubularSegments);
       },
     },
     TorusKnotBufferGeometry: {
-      create() {
-        const radius = 3.5;
-        const tube = 1.5;
-        const radialSegments = 8;
-        const tubularSegments = 64;
-        const p = 2;
-        const q = 3;
-        const geometry = new THREE.TorusKnotBufferGeometry(radius, tube, tubularSegments, radialSegments, p, q);
-        return geometry;
+      ui: {
+        radius: { type: 'range', min: 1, max: 10, precision: 1, },
+        tubeRadius: { type: 'range', min: 1, max: 10, precision: 1, },
+        radialSegments: { type: 'range', min: 1, max: 30, },
+        tubularSegments: { type: 'range', min: 1, max: 100, },
+        p: { type: 'range', min: 1, max: 20, },
+        q: { type: 'range', min: 1, max: 20, },
+      },
+      create(radius = 3.5, tubeRadius = 1.5, radialSegments = 8, tubularSegments = 64, p = 2, q = 3) {
+        return new THREE.TorusKnotBufferGeometry(
+            radius, tubeRadius, tubularSegments, radialSegments, p, q);
       },
     },
     TubeBufferGeometry: {
-      create() {
+      ui: {
+        tubularSegments: { type: 'range', min: 1, max: 100, },
+        radius: { type: 'range', min: 1, max: 10, precision: 1, },
+        radialSegments: { type: 'range', min: 1, max: 30, },
+        closed: { type: 'bool', },
+      },
+      create(tubularSegments = 20, radius = 1, radialSegments = 8, closed = false) {
         class CustomSinCurve extends THREE.Curve {
           constructor(scale) {
             super();
@@ -295,36 +493,55 @@ import {threejsLessonUtils} from './threejs-lesson-utils.js';
         }
 
         const path = new CustomSinCurve(4);
-        const tubularSegments = 20;
-        const radius = 1;
-        const radialSegments = 8;
-        const closed = false;
-        const geometry = new THREE.TubeBufferGeometry(path, tubularSegments, radius, radialSegments, closed);
-        return geometry;
+        return new THREE.TubeBufferGeometry(
+            path, tubularSegments, radius, radialSegments, closed);
       },
     },
     EdgesGeometry: {
-      create() {
-        const width = 8;
-        const height = 8;
-        const depth = 8;
-        const thresholdAngle = 15;
-        const geometry = new THREE.EdgesGeometry(
-            new THREE.BoxBufferGeometry(width, height, depth),
-            thresholdAngle);
-        return { lineGeometry: geometry };
+      ui: {
+        widthSegments: { type: 'range', min: 1, max: 10, },
+        heightSegments: { type: 'range', min: 1, max: 10, },
+        depthSegments: { type: 'range', min: 1, max: 10, },
+      },
+      create(widthSegments = 2, heightSegments = 2, depthSegments = 2) {
+        const size = 8;
+        return {
+          lineGeometry: new THREE.EdgesGeometry(new THREE.BoxBufferGeometry(
+            size, size, size,
+            widthSegments, heightSegments, depthSegments)),
+        };
       },
       nonBuffer: false,
+      src: `
+const size = 8;
+const geometry = new THREE.EdgesGeometry(
+    new THREE.BoxBufferGeometry(
+      size, size, size,
+      widthSegments, heightSegments, depthSegments));
+`,
     },
     WireframeGeometry: {
-      create() {
-        const width = 8;
-        const height = 8;
-        const depth = 8;
-        const geometry = new THREE.WireframeGeometry(new THREE.BoxBufferGeometry(width, height, depth));
-        return { lineGeometry: geometry };
+      ui: {
+        widthSegments: { type: 'range', min: 1, max: 10, },
+        heightSegments: { type: 'range', min: 1, max: 10, },
+        depthSegments: { type: 'range', min: 1, max: 10, },
+      },
+      create(widthSegments = 2, heightSegments = 2, depthSegments = 2) {
+        const size = 8;
+        return {
+          lineGeometry: new THREE.WireframeGeometry(new THREE.BoxBufferGeometry(
+            size, size, size,
+            widthSegments, heightSegments, depthSegments)),
+        };
       },
       nonBuffer: false,
+      src: `
+const size = 8;
+const geometry = new THREE.WireframeGeometry(
+    new THREE.BoxBufferGeometry(
+      size, size, size,
+      widthSegments, heightSegments, depthSegments));
+`,
     },
     Points: {
       create() {
@@ -362,83 +579,49 @@ import {threejsLessonUtils} from './threejs-lesson-utils.js';
       },
     },
     SphereBufferGeometryLow: {
-      create() {
-        const radius = 7;
-        const widthSegments = 5;
-        const heightSegments = 3;
-        const geometry = new THREE.SphereBufferGeometry(radius, widthSegments, heightSegments);
-        return geometry;
+      create(radius = 7, widthSegments = 5, heightSegments = 3) {
+        return new THREE.SphereBufferGeometry(radius, widthSegments, heightSegments);
       },
     },
     SphereBufferGeometryMedium: {
-      create() {
-        const radius = 7;
-        const widthSegments = 24;
-        const heightSegments = 10;
-        const geometry = new THREE.SphereBufferGeometry(radius, widthSegments, heightSegments);
-        return geometry;
+      create(radius = 7, widthSegments = 24, heightSegments = 10) {
+        return new THREE.SphereBufferGeometry(radius, widthSegments, heightSegments);
       },
     },
     SphereBufferGeometryHigh: {
-      create() {
-        const radius = 7;
-        const widthSegments = 50;
-        const heightSegments = 50;
-        const geometry = new THREE.SphereBufferGeometry(radius, widthSegments, heightSegments);
-        return geometry;
+      create(radius = 7, widthSegments = 50, heightSegments = 50) {
+        return new THREE.SphereBufferGeometry(radius, widthSegments, heightSegments);
       },
     },
     SphereBufferGeometryLowSmooth: {
-      create() {
-        const radius = 7;
-        const widthSegments = 5;
-        const heightSegments = 3;
-        const geometry = new THREE.SphereBufferGeometry(radius, widthSegments, heightSegments);
-        return geometry;
+      create(radius = 7, widthSegments = 5, heightSegments = 3) {
+        return new THREE.SphereBufferGeometry(radius, widthSegments, heightSegments);
       },
       showLines: false,
       flatShading: false,
     },
     SphereBufferGeometryMediumSmooth: {
-      create() {
-        const radius = 7;
-        const widthSegments = 24;
-        const heightSegments = 10;
-        const geometry = new THREE.SphereBufferGeometry(radius, widthSegments, heightSegments);
-        return geometry;
+      create(radius = 7, widthSegments = 24, heightSegments = 10) {
+        return new THREE.SphereBufferGeometry(radius, widthSegments, heightSegments);
       },
       showLines: false,
       flatShading: false,
     },
     SphereBufferGeometryHighSmooth: {
-      create() {
-        const radius = 7;
-        const widthSegments = 50;
-        const heightSegments = 50;
-        const geometry = new THREE.SphereBufferGeometry(radius, widthSegments, heightSegments);
-        return geometry;
+      create(radius = 7, widthSegments = 50, heightSegments = 50) {
+        return new THREE.SphereBufferGeometry(radius, widthSegments, heightSegments);
       },
       showLines: false,
       flatShading: false,
     },
     PlaneBufferGeometryLow: {
-      create() {
-        const width = 9;
-        const height = 9;
-        const widthSegments = 1;
-        const heightSegments = 1;
-        const geometry = new THREE.PlaneBufferGeometry(width, height, widthSegments, heightSegments);
-        return geometry;
+      create(width = 9, height = 9, widthSegments = 1, heightSegments = 1) {
+        return new THREE.PlaneBufferGeometry(width, height, widthSegments, heightSegments);
       },
     },
     PlaneBufferGeometryHigh: {
-      create() {
-        const width = 9;
-        const height = 9;
-        const widthSegments = 10;
-        const heightSegments = 10;
-        const geometry = new THREE.PlaneBufferGeometry(width, height, widthSegments, heightSegments);
-        return geometry;
+      create(width = 9, height = 9, widthSegments = 10, heightSegments = 10) {
+        return new THREE.PlaneBufferGeometry(width, height, widthSegments, heightSegments);
       },
     },
   };
@@ -467,8 +650,9 @@ import {threejsLessonUtils} from './threejs-lesson-utils.js';
     return addElem(parent, 'div', className);
   }
 
-  [...document.querySelectorAll('[data-diagram]')].forEach(createDiagram);
-  [...document.querySelectorAll('[data-primitive]')].forEach(createPrimitiveDOM);
+  const primitives = {};
+  document.querySelectorAll('[data-diagram]').forEach(createDiagram);
+  document.querySelectorAll('[data-primitive]').forEach(createPrimitiveDOM);
 
   function createPrimitiveDOM(base) {
     const name = base.dataset.primitive;
@@ -491,18 +675,49 @@ import {threejsLessonUtils} from './threejs-lesson-utils.js';
     }
     addDiv(right, '.note').innerHTML = text;
 
-    const rawLines = info.create.toString()
-        .replace(/ +return geometry;\n/, '')
-        .replace(/ +return { lineGeometry: geometry };\n/, '')
-        .split(/\n/);
-    const createRE = /^( *)[^ ]/;
-    const m = createRE.exec(rawLines[1]);
-    const prefixLen = m[1].length;
-    const trimmedLines = rawLines.slice(1, rawLines.length - 1).map(line => line.substring(prefixLen));
+    // I get that this is super brittle. I think I'd have to
+    // work through a bunch more examples to come up with a better
+    // structure. Also, I don't want to generate actual code and
+    // use eval. (maybe a bad striction)
+
+    function makeExample(elem, createFn, src) {
+      const rawLines = createFn.toString().replace(/return (new THREE\.[a-zA-Z]+Geometry)/, 'const geometry = $1').split(/\n/);
+      const createRE = /^ *(?:function *)*create\d*\((.*?)\)/;
+      const indentRE = /^( *)[^ ]/;
+      const m = indentRE.exec(rawLines[1]);
+      const prefixLen = m[1].length;
+      const m2 = createRE.exec(rawLines[0]);
+      const argString = m2[1].trim();
+      const trimmedLines = src
+          ? src.split('\n').slice(1, -1)
+          : rawLines.slice(1, rawLines.length - 1).map(line => line.substring(prefixLen));
+      if (info.addConstCode !== false && argString) {
+        const lines = argString.split(',').map((arg) => {
+          return `const ${arg.trim()};  // ui: ${arg.trim().split(' ')[0]}`;
+        });
+        const lineNdx = trimmedLines.findIndex(l => l.indexOf('const geometry') >= 0);
+        trimmedLines.splice(lineNdx < 0 ? 0 : lineNdx, 0, ...lines);
+      }
 
-    addElem(base, 'pre', 'prettyprint showmods', trimmedLines.join('\n'));
+      addElem(base, 'pre', 'prettyprint showmods', trimmedLines.join('\n'));
 
-    return createLiveImage(elem, info);
+      createLiveImage(elem, Object.assign({}, info, {create: createFn}), name);
+    }
+
+    makeExample(elem, info.create, info.src);
+
+    {
+      let i = 2;
+      for (;;) {
+        const createFn = info[`create${i}`];
+        if (!createFn) {
+          break;
+        }
+        const shapeElem = addDiv(base, 'shape');
+        makeExample(shapeElem, createFn, info[`src${i}`]);
+        ++i;
+      }
+    }
   }
 
   function createDiagram(base) {
@@ -511,13 +726,25 @@ import {threejsLessonUtils} from './threejs-lesson-utils.js';
     if (!info) {
       throw new Error(`no primitive ${name}`);
     }
-    return createLiveImage(base, info);
+    return createLiveImage(base, info, name);
   }
 
-  function createLiveImage(elem, info) {
-    const geometry = info.create();
+  const whiteLineMaterial = new THREE.LineBasicMaterial({
+    color: 0xffffff,
+    transparent: true,
+    opacity: 0.5,
+  });
+  const blackLineMaterial = new THREE.LineBasicMaterial({
+    color: 0x000000,
+    transparent: true,
+    opacity: 0.5,
+  });
+
+  function addGeometry(root, info, args = []) {
+    const geometry = info.create(...args);
     const promise = (geometry instanceof Promise) ? geometry : Promise.resolve(geometry);
-    promise.then((geometryInfo) => {
+
+    return promise.then((geometryInfo) => {
       if (geometryInfo instanceof THREE.BufferGeometry ||
           geometryInfo instanceof THREE.Geometry) {
         const geometry = geometryInfo;
@@ -526,41 +753,148 @@ import {threejsLessonUtils} from './threejs-lesson-utils.js';
         };
       }
 
-      const root = new THREE.Object3D();
-
-      if (geometry.mesh) {
-        root.add(geometry.mesh);
-      } else {
-        const boxGeometry = geometryInfo.geometry || geometryInfo.lineGeometry;
-        boxGeometry.computeBoundingBox();
-        const centerOffset = new THREE.Vector3();
-        boxGeometry.boundingBox.getCenter(centerOffset).multiplyScalar(-1);
+      const boxGeometry = geometryInfo.geometry || geometryInfo.lineGeometry;
+      boxGeometry.computeBoundingBox();
+      const centerOffset = new THREE.Vector3();
+      boxGeometry.boundingBox.getCenter(centerOffset).multiplyScalar(-1);
 
-        if (geometryInfo.geometry) {
+      if (geometryInfo.geometry) {
+        if (!info.material) {
           const material = new THREE.MeshPhongMaterial({
             flatShading: info.flatShading === false ? false : true,
             side: THREE.DoubleSide,
           });
           material.color.setHSL(Math.random(), .5, .5);
-          const mesh = new THREE.Mesh(geometryInfo.geometry, material);
-          mesh.position.copy(centerOffset);
-          root.add(mesh);
-        }
-
-        if (info.showLines !== false) {
-          const lineMesh = new THREE.LineSegments(
-            geometryInfo.lineGeometry || geometryInfo.geometry,
-            new THREE.LineBasicMaterial({
-              color: geometryInfo.geometry ? 0xffffff : colors.lines,
-              transparent: true,
-              opacity: 0.5,
-            }));
-          lineMesh.position.copy(centerOffset);
-          root.add(lineMesh);
+          info.material = material;
         }
+        const mesh = new THREE.Mesh(geometryInfo.geometry, info.material);
+        mesh.position.copy(centerOffset);
+        root.add(mesh);
       }
+      if (info.showLines !== false) {
+        const lineMesh = new THREE.LineSegments(
+          geometryInfo.lineGeometry || geometryInfo.geometry,
+          geometryInfo.geometry ? whiteLineMaterial : blackLineMaterial);
+        lineMesh.position.copy(centerOffset);
+        root.add(lineMesh);
+      }
+    });
+  }
+
+  function updateGeometry(root, info, params) {
+    const oldChildren = root.children.slice();
+    addGeometry(root, info, Object.values(params)).then(() => {
+      oldChildren.forEach((child) => {
+        root.remove(child);
+        child.geometry.dispose();
+      });
+    });
+  }
+
+  function createLiveImage(elem, info, name) {
+    const root = new THREE.Object3D();
+
+    primitives[name] = primitives[name] || [];
+    primitives[name].push({
+      root,
+      info,
+    });
 
+    addGeometry(root, info).then(() => {
       threejsLessonUtils.addDiagram(elem, {create: () => root});
     });
   }
+
+  function getValueElem(commentElem) {
+    return commentElem.previousElementSibling &&
+           commentElem.previousElementSibling.previousElementSibling &&
+           commentElem.previousElementSibling.previousElementSibling.previousElementSibling;
+  }
+
+  threejsLessonUtils.onAfterPrettify(() => {
+    document.querySelectorAll('[data-primitive]').forEach((base) => {
+      const primitiveName = base.dataset.primitive;
+      const infos = primitives[primitiveName];
+      base.querySelectorAll('pre.prettyprint').forEach((shape, ndx) => {
+        const {root, info} = infos[ndx];
+        const params = {};
+        [...shape.querySelectorAll('span.com')]
+        .filter(span => span.textContent.indexOf('// ui:') >= 0)
+        .forEach((span) => {
+          const nameRE = /ui: ([a-zA-Z0-9_]+) *$/;
+          const name = nameRE.exec(span.textContent)[1];
+          span.textContent = '';
+          if (!info.ui) {
+            console.error(`no ui for ${primitiveName}:${ndx}`);  // eslint-disable-line
+            return;
+            // throw new Error(`no ui for ${primitiveName}:${ndx}`);
+          }
+          const ui = info.ui[name];
+          if (!ui) {
+            throw new Error(`no ui for ${primitiveName}:${ndx} param: ${name}`);
+          }
+          const valueElem = getValueElem(span);
+          if (!valueElem) {
+            console.error(`no value element for ${primitiveName}:${ndx} param: ${name}`);  // eslint-disable-line
+            return;
+          }
+          switch (ui.type) {
+            case 'range': {
+              const valueRange = ui.max - ui.min;
+              const input = document.createElement('input');
+              const inputMax = 100;
+              input.type = 'range';
+              input.min = 0;
+              input.max = inputMax;
+              const value = parseFloat(valueElem.textContent);
+              params[name] = value * (ui.mult || 1);
+              input.value = (value - ui.min) / valueRange * inputMax;
+              span.appendChild(input);
+              const precision = ui.precision === undefined ? (valueRange > 4 ? 0 : 2) : ui.precision;
+              const padding = ui.max.toFixed(precision).length;
+              input.addEventListener('input', () => {
+                let newValue = input.value * valueRange / inputMax + ui.min;
+                if (precision === 0) {
+                  newValue = Math.round(newValue);
+                }
+                params[name] = newValue * (ui.mult || 1);
+                valueElem.textContent = newValue.toFixed(precision).padStart(padding, ' ');
+                updateGeometry(root, info, params);
+              });
+              break;
+            }
+            case 'bool': {
+              const input = document.createElement('input');
+              input.type = 'checkbox';
+              params[name] = valueElem.textContent === 'true';
+              input.checked = params[name];
+              span.appendChild(input);
+              input.addEventListener('change', () => {
+                params[name] = input.checked;
+                valueElem.textContent = params[name] ? 'true' : 'false';
+                updateGeometry(root, info, params);
+              });
+              break;
+            }
+            case 'text': {
+              const input = document.createElement('input');
+              input.type = 'text';
+              params[name] = valueElem.textContent.slice(1, -1);
+              input.value = params[name];
+              input.maxlength = ui.maxLength || 50;
+              span.appendChild(input);
+              input.addEventListener('input', () => {
+                params[name] = input.value;
+                valueElem.textContent = `'${input.value.replace(/'/g, '\'')}'`;
+                updateGeometry(root, info, params);
+              });
+              break;
+            }
+            default:
+              throw new Error(`unknonw type for ${primitiveName}:${ndx} param: ${name}`);
+          }
+        });
+      });
+    });
+  });
 }