title: Improve 3D animation for didactic usage
body: <p>I am preparing some didactic materials about optical illusions. For this I decide to play around three.js. I’ve done illustrations for structure-from motion effect. The idea of this effect is that our visual system is always working in situation of insufficient information and should add lacking details. Thus, when we have light, shadows, perspective and so on we clearly perceive unambiguous shape (demo in snippet below). On this example we may clearly see that sphere rotates to right.</p>
  <p><div class="snippet" data-lang="js" data-hide="true" data-console="true" data-babel="false">
  <div class="snippet-code snippet-currently-hidden">
  <pre class="snippet-code-js lang-js prettyprint-override"><code>/* global THREE, getDefaultParams */
  
  'use strict';
  
  let camera;
  let renderer;
  const currentParameters = getDefaultParams();
  // once everything is loaded, we run our Three.js stuff.
  function init() {
    // create a scene, that will hold all our elements such as objects, cameras and lights.
    const scene = new THREE.Scene();
  
    // create a camera, which defines where we're looking at.
    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
  
    // create a render and set the size
    renderer = new THREE.WebGLRenderer();
    renderer.setClearColor(new THREE.Color(currentParameters.renderer.color));
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.shadowMap.enabled = currentParameters.renderer.shadows;
  
    // create the landscape
    const landscapeGeometry = new THREE.PlaneGeometry(
      currentParameters.landscape.width,
      currentParameters.landscape.height,
    );
    const landscapeMaterial = new THREE.MeshLambertMaterial({ color: currentParameters.landscape.color });
    const landscape = new THREE.Mesh(landscapeGeometry, landscapeMaterial);
  
    landscape.rotation.x = currentParameters.landscape.rotation.x;
    landscape.rotation.y = currentParameters.landscape.rotation.y;
    landscape.rotation.z = currentParameters.landscape.rotation.z;
  
    landscape.position.x = currentParameters.landscape.position.x;
    landscape.position.y = currentParameters.landscape.position.y;
    landscape.position.z = currentParameters.landscape.position.z;
  
    landscape.receiveShadow = currentParameters.landscape.shadows;
    scene.add(landscape);
  
    // create a SFM shapes
    let parentGeometry = new THREE.SphereGeometry(
      currentParameters.parentShape.size,
      currentParameters.parentShape.numberOfVertices,
      currentParameters.parentShape.numberOfVertices,
    );
    let parentMaterial = new THREE.MeshLambertMaterial({
      color: 0x7777ff,
      opacity: 0.3,
      transparent: true,
    });
  
  
    let parentShape = new THREE.Mesh(parentGeometry, parentMaterial); // new THREE.Mesh();
  
    const childShapeGeometry = new THREE.SphereGeometry(
      currentParameters.childShape.size,
      currentParameters.childShape.numberOfVertices,
      currentParameters.childShape.numberOfVertices,
    );
    const childShapeMaterial = new THREE.MeshLambertMaterial({
      color: currentParameters.childShape.color,
    });
    const childShapeTemplate = new THREE.Mesh(childShapeGeometry, childShapeMaterial);
    childShapeTemplate.castShadow = currentParameters.childShape.shadows;
    for (let i = 0, l = currentParameters.parentShape.numberOfChilds; i &lt; l; i++) {
      const childShape = childShapeTemplate.clone();
      // add random dot to parent shape
      [childShape.position.x, childShape.position.y, childShape.position.z] =
      generateRandomDotOnSphere(0, 0, 0, currentParameters.parentShape.size);
      parentShape.add(childShape);
    }
    scene.add(parentShape);
    // add fixation point
    let fixationGeometry = new THREE.TorusKnotGeometry(3, 1);
    let fixationMaterial = new THREE.MeshLambertMaterial({ color: 0xffff00 });
    let fixationShape = new THREE.Mesh(fixationGeometry, fixationMaterial);
    fixationShape.castShadow = true;
    fixationShape.receiveShadow = true;
    scene.add(fixationShape);
    // position and point the camera to the center of the scene
    camera.position.copy({ x: 0, y: 0, z: 100 });
    camera.lookAt(parentShape.position);
    // add spotlight for the shadows
    const spotLight = new THREE.SpotLight(0xffffff);
    spotLight.position.set(-100, 100, 50);
    spotLight.castShadow = true;
    scene.add(spotLight);
    // Output
    document.getElementById('WebGL-output').appendChild(renderer.domElement);
    renderScene();
  
    function renderScene() {
      parentShape.rotation.y += 0.02;
      requestAnimationFrame(renderScene);
      renderer.render(scene, camera);
    }
  }
  function onResize() {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
  }
  
  
  window.onload = init;
  // listen to the resize events
  window.addEventListener('resize', onResize, false);
  
  // *********************************************
  // *********HELPERS*****************************
  function generateRandomDotOnSphere(x0, y0, z0, radius) {
  /*
  source: https://stackoverflow.com/questions/5531827/random-point-on-a-given-sphere
  Returns a random point of a sphere, evenly distributed over the sphere.
  The sphere is centered at (x0,y0,z0) with the passed in radius.
  The returned point is returned as a three element array [x,y,z].
  */
    let u = Math.random();
    let v = Math.random();
    let theta = 2 * Math.PI * u;
    let phi = Math.acos(2 * v - 1);
    let x = x0 + (radius * Math.sin(phi) * Math.cos(theta));
    let y = y0 + (radius * Math.sin(phi) * Math.sin(theta));
    let z = z0 + (radius * Math.cos(phi));
    return [x, y, z];
  }
  
  function getDefaultParams() {
    // Define default parameters
    const defaultParameters = { // define all backgroun
      renderer: {
        color: 0xFFFFFF,
        shadows: true,
      }, // renderer
  
      landscape: {
        color: 0xFFFFFF,
        shadows: true,
        width: 80,
        height: 80,
  
        rotation: {
          x: -0.5 * Math.PI,
          y: 0,
          z: 0,
        }, // rotation
  
        position: {
          x: 10,
          y: -30,
          z: -20,
        }, // position
      }, // landscape
      parentShape: {
        numberOfVertices: 10,
        size: 20,
        numberOfChilds: 50,
      }, // parent shape
      childShape: {
        numberOfVertices: 5,
        size: 1,
        color: 0x7777ff,
        shadows: true,
      }, // child shape
    };
    return defaultParameters;
  }</code></pre>
  <pre class="snippet-code-html lang-html prettyprint-override"><code>&lt;!DOCTYPE html&gt;
  
  &lt;html&gt;
  
  &lt;head&gt;
      &lt;title&gt;Structure_from_motion&lt;/title&gt;
      &lt;script src="https://threejs.org/build/three.min.js"&gt;&lt;/script&gt;
  &lt;/head&gt;
  &lt;body&gt;
  &lt;div id="description"&gt;
  &lt;/div&gt;
  &lt;!-- Div which will hold the Output --&gt;
  &lt;div id="WebGL-output",style="heigh:100px;"&gt;&lt;/div&gt;
  &lt;!-- Javascript code that runs our Three.js examples --&gt;
  &lt;script src="demoSFM_simple.js"&gt;&lt;/script&gt;
  &lt;/body&gt;
  &lt;/html&gt;</code></pre>
  </div>
  </div>
  </p>
  <p>If I will remove shadows, perspective (OrthographicCamera), make ambient light sphere will change the direction of it’s rotation from time to time.</p>
  <p><div class="snippet" data-lang="js" data-hide="true" data-console="true" data-babel="false">
  <div class="snippet-code snippet-currently-hidden">
  <pre class="snippet-code-js lang-js prettyprint-override"><code>/*global THREE, getDefaultParams */
  
  'use strict';
  
  let camera;
  let renderer;
  const currentParameters = getDefaultParams();
  // once everything is loaded, we run our Three.js stuff.
  function init() {
    // create a scene, that will hold all our elements such as objects, cameras and lights.
    const scene = new THREE.Scene();
  
    // create a camera, which defines where we're looking at.
    camera = new THREE.OrthographicCamera( window.innerWidth / - 7, window.innerWidth / 7, window.innerHeight / 7, window.innerHeight / - 7, 1, 1000 );
  
    // create a render and set the size
    renderer = new THREE.WebGLRenderer();
    renderer.setClearColor(new THREE.Color(currentParameters.renderer.color));
    renderer.setSize(window.innerWidth, window.innerHeight);
  
  
    // create a SFM shapes
    let parentShape = new THREE.Mesh();
  
    const childShapeGeometry = new THREE.SphereGeometry(
      currentParameters.childShape.size,
      currentParameters.childShape.numberOfVertices,
      currentParameters.childShape.numberOfVertices,
    );
    const childShapeMaterial = new THREE.MeshBasicMaterial({
      color: currentParameters.childShape.color,
    });
    const childShapeTemplate = new THREE.Mesh(childShapeGeometry, childShapeMaterial);
    for (let i = 0, l = currentParameters.parentShape.numberOfChilds; i &lt; l; i++) {
      const childShape = childShapeTemplate.clone();
      // add random dot to parent shape
      [childShape.position.x, childShape.position.y, childShape.position.z] =
      generateRandomDotOnSphere(0, 0, 0, currentParameters.parentShape.size);
      parentShape.add(childShape);
    }
    scene.add(parentShape);
    // add fixation point
    let fixationGeometry = new THREE.CircleGeometry(1, 10);
    let fixationMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 });
    let fixationShape = new THREE.Mesh(fixationGeometry, fixationMaterial);
    fixationShape.position.z = -100;
  
    scene.add(fixationShape);
    // position and point the camera to the center of the scene
    camera.position.copy({ x: 0, y: 0, z: 100 });
    camera.lookAt(parentShape.position);
    // add ambientLight for the shadows
    const ambientLight = new THREE.AmbientLight(0xffffff);
    ambientLight.position.set(-100, 100, 50);
    scene.add(ambientLight);
    // Output
    document.getElementById('WebGL-output').appendChild(renderer.domElement);
    renderScene();
  
    function renderScene() {
      parentShape.rotation.y += 0.02;
      requestAnimationFrame(renderScene);
      renderer.render(scene, camera);
    }
  }
  
  window.onload = init;
  
  
  // *********************************************
  // *********HELPERS*****************************
  function generateRandomDotOnSphere(x0, y0, z0, radius) {
  /*
  source: https://stackoverflow.com/questions/5531827/random-point-on-a-given-sphere
  Returns a random point of a sphere, evenly distributed over the sphere.
  The sphere is centered at (x0,y0,z0) with the passed in radius.
  The returned point is returned as a three element array [x,y,z].
  */
    let u = Math.random();
    let v = Math.random();
    let theta = 2 * Math.PI * u;
    let phi = Math.acos(2 * v - 1);
    let x = x0 + (radius * Math.sin(phi) * Math.cos(theta));
    let y = y0 + (radius * Math.sin(phi) * Math.sin(theta));
    let z = z0 + (radius * Math.cos(phi));
    return [x, y, z];
  }
  function getDefaultParams() {
    // Define default parameters
    const defaultParameters = { // define all backgroun
      renderer: {
        color: 0xFFFFFF,
      }, // renderer
      parentShape: {
        numberOfVertices: 10,
        size: 20,
        numberOfChilds: 50,
      }, // parent shape
      childShape: {
        numberOfVertices: 5,
        size: 1,
        color: 0x7777ff,
      }, // child shape
    };
    return defaultParameters;
  }</code></pre>
  <pre class="snippet-code-html lang-html prettyprint-override"><code>&lt;!DOCTYPE html&gt;
  
  &lt;html&gt;
  
  &lt;head&gt;
      &lt;title&gt;Structure_from_motion&lt;/title&gt;
      &lt;script src="https://threejs.org/build/three.min.js"&gt;&lt;/script&gt;
  &lt;/head&gt;
  &lt;body&gt;
  &lt;!-- Div which will hold the Output --&gt;
  &lt;div id="WebGL-output",style="heigh:100px;"&gt;&lt;/div&gt;
  &lt;!-- Javascript code that runs our Three.js examples --&gt;
  
  &lt;script src="ambiguous.js"&gt;&lt;/script&gt;
  &lt;/body&gt;
  &lt;/html&gt;</code></pre>
  </div>
  </div>
  </p>
  <h1><a href="https://github.com/aleshins/demos_psychophysics" rel="nofollow noreferrer">GitHub repo</a>.</h1>
  <h1>General Question/Problem:</h1>
  <p>I am satisfied by demos above, but only as concepts. First demo looks ugly. I am not an artist at all and I ask you to help to make this (first) demo looks pretty and as much natural as possible. Later I plan to add GUI controls to switch off each of disambiguating factors to obtain second demo.</p>
  <h1>Detailed questions:</h1>
  <p>Besides main problem to make unambiguous demo more natural and interesting I have several concrete questions:</p>
  <ul>
  <li><p>animation looks not smooth and sometimes slow. Is it a limitation of three.js or it is possible to speedup and smooth animation?</p>
  </li>
  <li><p>y coordinate of camera is zero same as centre of transparent sphere (parent shape), but y coordinates of small spheres centres are change during rotation (i put ruler to my monitor). But It should be on the same level if I understand correctly.</p>
  </li>
  <li><p>How do I smooth the shadows? (thanks TheJim01).</p>
  </li>
  <li><p>What is the Physical Based Rendering and will it helps me? (thanks TheJim01)</p>
  </li>
  <li><p>I need to switch from <code>THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000)</code> to  <code>THREE.OrthographicCamera()</code>. How I should adjust parameters of OrthographicCamera to keep the size of the parent shape?</p>
  </li>
  </ul>
  <p><em>TO BE CONTINUED...</em></p>
  <h1>question will have bounty tomorrow</h1>