block by syntagmatic c7d6ea0132c956140cbb

Hypersolids

Full Screen

A Simplex, Hypercube and Cross Polytope from Paul Bourke’s Hyperspace User Manual.

Visualized with hypersolid.js by Miłosz Kośmider.

index.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" /> 
<title>Hypersolids</title>
<style>
html, body { 
  background: #fff;
  color: #555;
  width: 960px;
  margin: 0 auto;
  font-family: sans-serif;
}
canvas {
  border: none;
  display: inline-block;
  margin: 0 20px;
} 
#hypercube-options {
  margin: 60px 0 0 85px;
}
label {
  margin: 0 20px;
  font-size: 15px;
  cursor: pointer;
}
h4 {
  display: inline-block;
  width: 300px;
  text-align:center; 
  font-size: 15px;
  margin: 60px 0 15px 0;
}
</style>
<script type="text/javascript" src="hypersolid.js"></script>
<script type="text/javascript" src="hypersolid.shapebank.js"></script>
</head>
<body>
  <h4>Simplex</h4>
  <h4>Tesseract</h4>
  <h4>Orthoplex</h4>
  <canvas id="simplex-canvas">Unfortunately, your browser does not support coolness.</canvas>
  <canvas id="hypercube-canvas">Unfortunately, your browser does not support coolness.</canvas>
  <canvas id="cross-canvas">Unfortunately, your browser does not support coolness.</canvas>
  <form id="hypercube-options">
    <label><input type="checkbox" name="rotate_xy" />Rotate xy</label>
    <label><input type="checkbox" name="rotate_yz" />Rotate yz</label>
    <label><input type="checkbox" name="rotate_xz" />Rotate xz</label>
    <label><input type="checkbox" name="rotate_xw" />Rotate xw</label>
    <label><input type="checkbox" name="rotate_yw" />Rotate yw</label>
    <label><input type="checkbox" name="rotate_zw" />Rotate zw</label>
  </form>
<script type="text/javascript">
  var simplex = Hypersolid.Simplex();
  var simplexView = Hypersolid.Viewport(simplex, document.getElementById('simplex-canvas'), {
    width: 260,
    height: 260,
    scale: 2,
    lineWidth: 3,
    lineJoin: 'round'
  }); 
  simplexView.draw();

  var cube = Hypersolid.Hypercube();
  var cubeView = Hypersolid.Viewport(cube, document.getElementById('hypercube-canvas'), {
    width: 260,
    height: 260,
    scale: 2,
    lineWidth: 3,
    lineJoin: 'round'
  });
  cubeView.draw();

  var cross = Hypersolid.Cross();
  var crossView = Hypersolid.Viewport(cross, document.getElementById('cross-canvas'), {
    width: 260,
    height: 260,
    scale: 2,
    lineWidth: 3,
    lineJoin: 'round'
  });
  crossView.draw();

  // starting rotation
  rotate("xz", 0.35);
  rotate("yz", 0.25);

  // animation
  options = document.getElementById('hypercube-options');
  function render() {
    if (options) {
      checkboxes = options.getElementsByTagName('input');
    }
    if (options.rotate_xz.checked) {
      rotate("xz", 0.008);
    }
    if (options.rotate_yz.checked) {
      rotate("yz", 0.008);
    }
    if (options.rotate_xw.checked) {
      rotate("xw", 0.008);
    }
    if (options.rotate_yw.checked) {
      rotate("yw", 0.008);
    }
    if (options.rotate_xy.checked) {
      rotate("xy", 0.008);
    }
    if (options.rotate_zw.checked) {
      rotate("zw", 0.008);
    }
  };

  function rotate(plane, x) {
    simplex.rotate(plane, x);
    simplexView.draw();
    cube.rotate(plane, x);
    cubeView.draw();
    cross.rotate(plane, x);
    crossView.draw();
  };

  window.requestAnimFrame = window.requestAnimationFrame ||
  window.webkitRequestAnimationFrame ||
  window.mozRequestAnimationFrame    ||
  window.oRequestAnimationFrame      ||
  window.msRequestAnimationFrame     ||
  function( callback ){
    window.setTimeout(callback, 1000 / 60);
  };

  (function animloop(){
    requestAnimFrame(animloop);
    render();
  })();
</script>

hypersolid.js

/*
 * Hypersolid, Four-dimensional solid viewer
 * 
 * Copyright (c) 2014 Milosz Kosmider <milosz@milosz.ca>
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 */

(function(Hypersolid) {
  /* Begin constants. */

  DEFAULT_VIEWPORT_WIDTH = 480; // Width of canvas in pixels
  DEFAULT_VIEWPORT_HEIGHT = 480; // Height of canvas in pixels
  DEFAULT_VIEWPORT_SCALE = 2; // Maximum distance from origin (in math units) that will be displayed on the canvas
  DEFAULT_VIEWPORT_FONT = 'italic 10px sans-serif';
  DEFAULT_VIEWPORT_FONT_COLOR = '#000';
  DEFAULT_VIEWPORT_LINE_WIDTH = 4;
  DEFAULT_VIEWPORT_LINE_JOIN = 'round';
  DEFAULT_CHECKBOX_VALUES = {
    perspective: { checked: true },
    indices: { checked: false },
    edges: { checked: true }
  };

  /* End constants. */

  /* Begin classes. */

  Hypersolid.Shape = function() {
    return new Shape(Array.prototype.slice.call(arguments, 0));
  };
  function Shape(argv) {
    var self = this,
      vertices = argv[0],
      edges = argv[1];

    // Rotations will always be relative to the original shape to avoid rounding errors.
    // This is a structure for caching the rotated vertices.
    var rotatedVertices = new Array(vertices.length);
    copyVertices();

    // This is where we store the current rotations about each axis.
    var rotations = { xy: 0, xz: 0, xw: 0, yz: 0, yw: 0, zw: 0 };

    var rotationOrder = {
      yz: 1,
      xw: 1,
      yw: 1,
      zw: 1,
      xy: 1,
      xz: 1,
    };

    // Multiplication by vector rotation matrices of dimension 4
    var rotateVertex = {
      xy: function(v, s, c) {
        tmp = c * v.x + s * v.y;
        v.y = -s * v.x + c * v.y;
        v.x = tmp;
      },
      xz: function(v, s, c) {
        tmp = c * v.x + s * v.z;
        v.z = -s * v.x + c * v.z;
        v.x = tmp;
      },
      xw: function(v, s, c) {
        tmp = c * v.x + s * v.w;
        v.w = -s * v.x + c * v.w;
        v.x = tmp;
      },
      yz: function(v, s, c) {
        tmp = c * v.y + s * v.z;
        v.z = -s * v.y + c * v.z;
        v.y = tmp;
      },
      yw: function(v, s, c) {
        tmp = c * v.y - s * v.w;
        v.w = s * v.y + c * v.w;
        v.y = tmp;
      },
      zw: function(v, s, c) {
        tmp = c * v.z - s * v.w;
        v.w = s * v.z + c * v.w;
        v.z = tmp;
      }
    };

    var eventCallbacks = {};

    self.getOriginalVertices = function() {
      return vertices;
    };

    self.getVertices = function() {
      return rotatedVertices;
    };

    self.getEdges = function() {
      return edges;
    };

    self.getRotations = function() {
      return rotations;
    };

    // This will copy the original shape and put a rotated version into rotatedVertices
    self.rotate = function(axis, theta)  {
      addToRotation(axis, theta);
      applyRotations();
      triggerEventCallbacks('rotate');
    };

    self.on = function(eventName, callback) {
      if (eventCallbacks[eventName] === undefined) {
        eventCallbacks[eventName] = [];
      }
      eventCallbacks[eventName].push(callback);
    };

    function triggerEventCallbacks(eventName) {
      if (eventCallbacks[eventName] !== undefined) {
        for (index in eventCallbacks[eventName]) {
          eventCallbacks[eventName][index].call(self);
        }
      }
    }

    function addToRotation(axis, theta) {
      rotations[axis] = (rotations[axis] + theta) % (2 * Math.PI);
    }

    function applyRotations() {
      copyVertices();

      for (var axis in rotationOrder) {
        // sin and cos precomputed for efficiency
        var s = Math.sin(rotations[axis]);
        var c = Math.cos(rotations[axis]);

        for (var i in vertices)
        {
          rotateVertex[axis](rotatedVertices[i], s, c);
        }
      }
    }

    function copyVertices() {
      for (var i in vertices) {
        var vertex = vertices[i];
        rotatedVertices[i] = {
          x: vertex.x,
          y: vertex.y,
          z: vertex.z,
          w: vertex.w
        };
      }
    }
  }

  Hypersolid.Viewport = function() {
    return new Viewport(Array.prototype.slice.call(arguments, 0));
  };
  function Viewport(argv) {
    var self = this,
      shape = argv[0],
      canvas = argv[1],
      options = argv[2];

    options = options || {};

    var scale = options.scale || DEFAULT_VIEWPORT_SCALE;
    canvas.width = options.width || DEFAULT_VIEWPORT_WIDTH;
    canvas.height = options.height || DEFAULT_VIEWPORT_HEIGHT;
    var bound = Math.min(canvas.width, canvas.height) / 2;

    var context = canvas.getContext('2d');
    context.font = options.font || DEFAULT_VIEWPORT_FONT;
    context.textBaseline = 'top';
    context.fillStyle = options.fontColor || DEFAULT_VIEWPORT_FONT_COLOR;
    context.lineWidth = options.lineWidth || DEFAULT_VIEWPORT_LINE_WIDTH;
    context.lineJoin = options.lineJoin || DEFAULT_VIEWPORT_LINE_JOIN;

    var checkboxes = options.checkboxes || DEFAULT_CHECKBOX_VALUES;

    var clicked = false;
    var startCoords;

    self.draw = function() {
      var vertices = shape.getVertices();
      var edges = shape.getEdges();

      context.clearRect(0, 0, canvas.width, canvas.height);
      var adjusted = [];
      for (var i in vertices) {
        if (checkboxes.perspective.checked) {
          var zratio = vertices[i].z / scale;
          adjusted[i] = {
            x: Math.floor(canvas.width / 2 + (0.90 + zratio * 0.30) * bound * (vertices[i].x / scale)) + 0.5,
            y: Math.floor(canvas.height / 2 - (0.90 + zratio * 0.30) * bound * (vertices[i].y / scale)) + 0.5,
            z: 0.50 + 0.40 * zratio,
            w: 121 + Math.floor(134 * vertices[i].w / scale)
          };
        }
        else {
          adjusted[i] = {
            x: Math.floor(canvas.width / 2 + bound * (vertices[i].x / scale)) + 0.5,
            y: Math.floor(canvas.height / 2 - bound * (vertices[i].y / scale)) + 0.5,
            z: 0.50 + 0.40 * vertices[i].z / scale,
            w: 121 + Math.floor(134 * vertices[i].w / scale)
          };
        }
      }

      if (checkboxes.edges.checked) {
        for (var i in edges) {
          var x = [adjusted[edges[i][0]].x, adjusted[edges[i][1]].x];
          var y = [adjusted[edges[i][0]].y, adjusted[edges[i][1]].y];
          var z = [adjusted[edges[i][0]].z, adjusted[edges[i][1]].z];
          var w = [adjusted[edges[i][0]].w, adjusted[edges[i][1]].w];
          context.beginPath();
          context.moveTo(x[0], y[0]);
          context.lineTo(x[1], y[1]);
          context.closePath();
          var gradient = context.createLinearGradient(x[0], y[0], x[1], y[1]); // Distance fade effect
          gradient.addColorStop(0, 'rgba(' + w[0] + ',94,' + (125-Math.round(w[0]/2)) +', ' + z[0] + ')');
          gradient.addColorStop(1, 'rgba(' + w[1] + ',94,' + (125-Math.round(w[0]/2)) +', ' + z[1] + ')');
          context.strokeStyle = gradient;
          context.stroke();
        }
      }

      if (checkboxes.indices.checked) {
        for (var i in adjusted) {
          context.fillText(i.toString(), adjusted[i].x, adjusted[i].y);
        }
      }
    };

    checkboxes.onchange = function() {
      self.draw();
    };
  }

  /* End classes. */

  /* Begin methods. */

  // parse ascii files from http://paulbourke.net/geometry/hyperspace/
  Hypersolid.parseVEF = function(text) {
    var lines = text.split("\n");
    var nV = parseInt(lines[0]);  // number of vertices
    var nE = parseInt(lines[1+nV]);  // number of edges
    var nF = parseInt(lines[2+nV+nE]);  // number of faces
    var vertices = lines.slice(1,1+nV).map(function(line) {
      var d = line.split("\t").map(parseFloat);
      return {
        x: d[0],
        y: d[1],
        z: d[2],
        w: d[3],
      }
    });
    var edges = lines.slice(2+nV,2+nV+nE).map(function(line) {
      var d = line.replace("\s","").split("\t").map(function(vertex) { return parseInt(vertex); });
      return [d[0], d[1]];;
    });
    var faces = lines.slice(3+nV+nE,3+nV+nE+nF).map(function(line) {
      var d = line.replace("\s","").split("\t").map(function(edge) { return parseInt(edge); });
      return d;
    });
    return [vertices,edges,faces]
  };

  /* End methods. */

})(window.Hypersolid = window.Hypersolid || {});

hypersolid.shapebank.js

(function(Hypersolid) {

  /*
   * Hypercube
   */

  Hypersolid.Hypercube = function() {
    return new Hypercube();
  };
  function Hypercube() {};
  Hypercube.prototype = Hypersolid.Shape([
    { x:  1, y:  1, z:  1, w:  1 },
    { x:  1, y:  1, z:  1, w: -1 },
    { x:  1, y:  1, z: -1, w:  1 },
    { x:  1, y:  1, z: -1, w: -1 },
    { x:  1, y: -1, z:  1, w:  1 },
    { x:  1, y: -1, z:  1, w: -1 },
    { x:  1, y: -1, z: -1, w:  1 },
    { x:  1, y: -1, z: -1, w: -1 },
    { x: -1, y:  1, z:  1, w:  1 },
    { x: -1, y:  1, z:  1, w: -1 },
    { x: -1, y:  1, z: -1, w:  1 },
    { x: -1, y:  1, z: -1, w: -1 },
    { x: -1, y: -1, z:  1, w:  1 },
    { x: -1, y: -1, z:  1, w: -1 },
    { x: -1, y: -1, z: -1, w:  1 },
    { x: -1, y: -1, z: -1, w: -1 }
  ], [
    [ 0,  1], [ 0,  2], [ 0,  4], [ 0,  8],
              [ 1,  3], [ 1,  5], [ 1,  9],
              [ 2,  3], [ 2,  6], [ 2, 10],
                        [ 3,  7], [ 3, 11],
              [ 4,  5], [ 4,  6], [ 4, 12],
                        [ 5,  7], [ 5, 13],
                        [ 6,  7], [ 6, 14],
                                  [ 7, 15],
              [ 8,  9], [ 8, 10], [ 8, 12],
                        [ 9, 11], [ 9, 13],
                        [10, 11], [10, 14],
                                  [11, 15],
                        [12, 13], [12, 14],
                                  [13, 15],
                                  [14, 15]
  ]);

  // 5 cell
  Hypersolid.Simplex = function() {
    return new Simplex();
  };
  function Simplex() {};
  Simplex.prototype = Hypersolid.Shape([
    {"x":0,"y":0,"z":0,"w":2},
    {"x":-1,"y":1,"z":1,"w":0},
    {"x":1,"y":-1,"z":1,"w":0},
    {"x":1,"y":1,"z":-1,"w":0},
    {"x":-1,"y":-1,"z":-1,"w":0}
  ], [
    [0,1],[0,2],[0,3],
    [1,2],[1,3],
    [2,3],
    [3,4],
    [4,0],[4,1],[4,2],
  ]);

  // 16 cell
  Hypersolid.Cross = function() {
    return new Cross();
  };
  function Cross() {};
  Cross.prototype = Hypersolid.Shape([
    {"x":-2,"y":0,"z":0,"w":0},
    {"x":0,"y":-2,"z":0,"w":0},
    {"x":0,"y":0,"z":-2,"w":0},
    {"x":0,"y":0,"z":0,"w":-2},
    {"x":2,"y":0,"z":0,"w":0},
    {"x":0,"y":2,"z":0,"w":0},
    {"x":0,"y":0,"z":2,"w":0},
    {"x":0,"y":0,"z":0,"w":2}
  ], [
    [0,1],[0,2],[0,3],[0,5],[0,6],
    [1,2],[1,3],[1,4],[1,6],
    [2,3],[2,4],[2,5],
    [3,4],[3,5],
    [4,5],[4,6],
    [5,6],
    [6,3],[6,7],
    [7,0],[7,1],[7,2],[7,4],[7,5]
  ]);

  // 24 cell
  Hypersolid.Icositetrachoron = function() {
    return new Icositetrachoron();
  };
  function Icositetrachoron() {};
  Icositetrachoron.prototype = Hypersolid.Shape([
    {x:-2,y:0,z:0,w:0},
    {x:0,y:-2,z:0,w:0},
    {x:0,y:0,z:-2,w:0},
    {x:0,y:0,z:0,w:-2},
    {x:2,y:0,z:0,w:0},
    {x:0,y:2,z:0,w:0},
    {x:0,y:0,z:2,w:0},
    {x:0,y:0,z:0,w:2},
    {x:-1,y:-1,z:-1,w:-1},
    {x:-1,y:-1,z:-1,w:1},
    {x:-1,y:-1,z:1,w:-1},
    {x:-1,y:-1,z:1,w:1},
    {x:-1,y:1,z:-1,w:-1},
    {x:-1,y:1,z:-1,w:1},
    {x:-1,y:1,z:1,w:-1},
    {x:-1,y:1,z:1,w:1},
    {x:1,y:-1,z:-1,w:-1},
    {x:1,y:-1,z:-1,w:1},
    {x:1,y:-1,z:1,w:-1},
    {x:1,y:-1,z:1,w:1},
    {x:1,y:1,z:-1,w:-1},
    {x:1,y:1,z:-1,w:1},
    {x:1,y:1,z:1,w:-1},
    {x:1,y:1,z:1,w:1}
  ], [
    [0,8],
    [10,0],[10,1],[10,11],[10,14],[10,18],[10,3],
    [11,0],[11,1],[11,15],[11,19],[11,6],[11,7],
    [12,0],[12,13],[12,14],[12,2],[12,20],[12,3],
    [13,0],[13,15],[13,2],[13,21],[13,5],[13,7],
    [14,0],[14,15],[14,22],[14,3],[14,5],[14,6],
    [15,0],[15,23],[15,5],[15,6],[15,7],
    [16,1],[16,17],[16,18],[16,2],[16,20],[16,3],
    [17,1],[17,19],[17,2],[17,21],[17,4],[17,7],
    [1,8],
    [18,1],[18,19],[18,22],[18,3],[18,4],[18,6],
    [19,1],[19,23],[19,4],[19,6],[19,7],
    [20,2],[20,21],[20,22],[20,3],[20,4],[20,5],
    [21,2],[21,23],[21,4],[21,5],[21,7],
    [22,23],[22,3],[22,4],[22,5],[22,6],
    [23,4],[23,5],[23,6],[23,7],
    [2,8],[3,8],
    [4,16],
    [5,12],
    [6,10],
    [7,9],
    [8,10],[8,12],[8,16],[8,9],
    [9,0],[9,1],[9,11],[9,13],[9,17],[9,2]
  ]);
})(window.Hypersolid = window.Hypersolid || {});