<!DOCTYPE html>
<html>
<head>
  <style>
    body {
      background-color: #ffe0f0;
    }
    
    /* needed to keep Ganja canvas from blowing up */
    canvas {
      min-width: 600px;
      max-width: 600px;
      min-height: 600px;
      max-height: 600px;
    }
  </style>
  <script src="https://unpkg.com/ganja.js"></script>
</head>
<body>
  <p><button onclick="flip()">Flip</button></p>
  <script>
  // in the default view, e4 + e5 is the point at infinity
  let CGA3 = Algebra(4, 1);
  let elements = [
    CGA3.inline(() => Math.sqrt(0.5)*( 1e1 + 1e2 + 1e3 + 1e5))(),
    CGA3.inline(() => Math.sqrt(0.5)*( 1e1 - 1e2 - 1e3 + 1e5))(),
    CGA3.inline(() => Math.sqrt(0.5)*(-1e1 + 1e2 - 1e3 + 1e5))(),
    CGA3.inline(() => Math.sqrt(0.5)*(-1e1 - 1e2 + 1e3 + 1e5))(),
    CGA3.inline(() => -Math.sqrt(3)*1e4 + Math.sqrt(2)*1e5)()
  ];
  /*
    these blocks of commented-out code can be used to confirm that a spacelike
    vector and its Hodge dual represent the same generalized sphere
  */
  /*let elements = [
    CGA3.inline(() => Math.sqrt(0.5)*!( 1e1 + 1e2 + 1e3 + 1e5))(),
    CGA3.inline(() => Math.sqrt(0.5)*!( 1e1 - 1e2 - 1e3 + 1e5))(),
    CGA3.inline(() => Math.sqrt(0.5)*!(-1e1 + 1e2 - 1e3 + 1e5))(),
    CGA3.inline(() => Math.sqrt(0.5)*!(-1e1 - 1e2 + 1e3 + 1e5))(),
    CGA3.inline(() => !(-Math.sqrt(3)*1e4 + Math.sqrt(2)*1e5))()
  ];*/
  /*let elements = [
    CGA3.inline(() =>  1e1 + 1e5)(),
    CGA3.inline(() =>  1e2 + 1e5)(),
    CGA3.inline(() =>  1e3 + 1e5)(),
    CGA3.inline(() => -1e4 + 1e5)(),
    CGA3.inline(() => Math.sqrt(0.5)*(1e1 + 1e2 + 1e3 + 1e5))(),
    CGA3.inline(() => Math.sqrt(0.5)*!(1e1 + 1e2 + 1e3 - 0.01e4 + 1e5))()
  ];*/
  
  // set up palette
  var colorIndex;
  var palette = [0xff00b0, 0x00ffb0, 0x00b0ff, 0x8040ff, 0xc0c0c0];
  function nextColor() {
    colorIndex = (colorIndex + 1) % palette.length;
    return palette[colorIndex];
  }
  function resetColorCycle() {
    colorIndex = palette.length - 1;
  }
  resetColorCycle();
  
  // create scene function
  function scene() {
    commands = [];
    resetColorCycle();
    elements.forEach((elt) => commands.push(nextColor(), elt));
    return commands;
  }
  
  // initialize graph
  let graph = CGA3.graph(
    scene,
    {
      conformal: true, gl: true, grid: true
    }
  )
  document.body.appendChild(graph);
  
  function flip() {
    let last = elements.length - 1;
    for (let n = 0; n < last; ++n) {
      // reflect
      elements[n] = CGA3.Mul(CGA3.Mul(elements[last], elements[n]), elements[last]);
      
      // de-noise
      for (let k = 6; k < elements[n].length; ++k) {
      /*for (let k = 0; k < 26; ++k) {*/
        elements[n][k] = 0;
      }
    }
    requestAnimationFrame(graph.update.bind(graph, scene));
  }
  </script>
</body>
</html>