﻿<!DOCTYPE html>

<html lang="en">
	<head>
		<title>HYPERNOM</title>

		<!--

        Nom all the cells of each 4d platonic solid, by mapping your head rotations to S^3.

        By Henry Segerman, Vi Hart, and Andrea Hawksley, using Marc ten Bosch's 4D graphics shader, Mozilla's webVR stuff, and threejs.

        http://www.segerman.org/
				http://vihart.com
        https://github.com/hawksley

        http://www.marctenbosch.com
        https://github.com/MozVR/vr-web-examples/tree/master/threejs-vr-boilerplate
        http://threejs.org

		-->

		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
		<style>
			body {
				background-color: #000;
				color: #fff;
				margin: 0px;
				padding: 0;
				overflow: hidden;
			}
		</style>
	</head>

	<body style="cursor:pointer">
	<audio id='music' src="media/monkeygif.mp3"/>

    <audio id='nom1' src="media/nom1.ogg" >
    <audio id='nom2' src="media/nom2.ogg" >
    <audio id='nom3' src="media/nom3.ogg" >
    <audio id='nom4' src="media/nom4.ogg" >
    <audio id='nom5' src="media/nom5.ogg" >
    <audio id='win' src="media/win.ogg" >

	</body>

	<!--
	three.js 3d library
	-->
	<script src="js/lib/three.min.js"></script>
	<script src="js/lib/threex.dynamictexture.js"></script>

	<!--
	library for fast quaternion rotation
	-->
	<script src="js/lib/gl-matrix.js"></script>

	<script src="js/sphMath.js"></script>

	<!--
	VRControls.js acquires positional information from connected VR devices and applies the transformations to a three.js camera object.
	 -->
	<script src="js/vr/PhoneVR.js"></script>
	<script src="js/vr/VRControlsHyperNom.js"></script>

	<!--
	VREffect.js handles stereo camera setup and rendering.
	-->
	<script src="js/vr/VREffect.js"></script>


    <!-- Quaternions for the centers of the cells -->
<script src="js/centers_600_cell.js"></script>
    <script src="js/centers_5_cell.js"></script>
    <script src="js/centers_8_cell.js"></script>
    <script src="js/centers_16_cell.js"></script>
    <script src="js/centers_24_cell.js"></script>
    <script src="js/centers_120_cell.js"></script>

<script src="js/loaders/OBJLoader.js"></script>


<!--font from http://mrdoob.github.com/three.js/examples/fonts/helvetiker_regular.typeface.js -->
<script src="lib/helvetiker.js"></script>

<script type="x-shader/x-vertex" id="vertexShader">
// This shader moves vertices around

// Quaternion Multiplication
vec4 quatMult( in vec4 p, in vec4 q )
{
    vec4 r;
    r.w = + p.w*q.w - p.x*q.x - p.y*q.y - p.z*q.z;
    r.x = + p.w*q.x + p.x*q.w + p.y*q.z - p.z*q.y;
    r.y = + p.w*q.y - p.x*q.z + p.y*q.w + p.z*q.x;
    r.z = + p.w*q.z + p.x*q.y - p.y*q.x + p.z*q.w;
    return r;
}
vec4 quatInv( in vec4 p )
{
    vec4 r;
    r.x = -p.x;
    r.y = -p.y;
    r.z = -p.z;
    r.w = p.w;
    return r;
}

// Project the vector p to the 3-space perpendicular to q
vec4 projVecPerp( in vec4 p, in vec4 q )
{
    vec4 r;
    float pDotq = dot(p,q);
    float qDotq = dot(q,q);
    float foo = pDotq / qDotq;
    r = p - foo*q;
    return r;
}

// point on geod in S3 from p in direction of q going distance dist
vec4 pointOnS3Geod( in vec4 p, in vec4 q, in float dist)
{
    vec4 Q = normalize( q - dot(p,q) * p );
    return cos(dist)*p + sin(dist)*Q;
}

// input
uniform float time; // global time in seconds
uniform vec4 quatPerCell; // quaternion that moves this monkey into 4-space, set once per monkey
// uniform int fogType; // which type of fog to use
uniform vec2 mousePos;
uniform vec4 travelDir; //quaternion for which way we are rotating
uniform vec4 colourDir; //quaternion for which way we are colouring
uniform mat4 HopfColorMatrix; //rotates colourDir to lie along (0,0,z,w)
uniform vec4 moveQuat; //quaternion for head
uniform mat3 rotMatrix; //rotate tetrahedral cells into correct orientation
uniform float modelScale; //scale model by this

// Hopf fibration coloring
// returns a color based on the 4D normal
vec3 HopfColor( in vec4 nBase )  /// head foot are all same colour
{
    /////////first rotate the 4D normal to a space aligned with the polychoron

    vec4 n = HopfColorMatrix * nBase;
	// compute the color

    float x = n.x;
    float y = n.y;
    float u = n.z;
    float v = n.w;

    float r = 2. * (u*x + v*y);
    float g = 2. * (u*y - v*x);
    float b = x*x + y*y - u*u - v*v;

    /// first two coords are 2*z*conj(w), where z = x+iy, w = u+iv

    /// rotate [0,0,-1] to [-1,-1,-1]/sqrt(3)

    mat3 RotDownToDiag = mat3( vec3(0.707107, -0.707107, 0.),       ///// input columns not rows?!?!?!
                               vec3(0.408248, 0.408248, -0.816497),  //Because line n+3 is RotDownToDiag*newCol, not newCol*RotDownToDiag.
                               vec3(0.57735, 0.57735, 0.57735) );    //This basically lets the shader do matrix multiplication via dot products, which is relatively efficient.
    vec3 newCol = vec3(r,g,b);

    newCol = RotDownToDiag * newCol;

    return vec3(newCol.x*0.5 + 0.5,newCol.y*0.5 + 0.5,newCol.z*0.5 + 0.5);
}

// output
varying vec3 vColor; // this shader computes the color of each vertex

// this gets called once per vertex of the monkey mesh (and numCells times since there are numCells monkeys)
void main()
{
    // base position
	// turn a 3D position of a model into a 4D position by adding a 1 as the w component then normalizing to get onto the unit 3-sphere
	// vec4 p3sphere = normalize( vec4(position.zyx, 1.0) );
    vec3 posn = position.zyx;
    posn = rotMatrix * posn;

	vec4 p3sphere = normalize( vec4(modelScale * posn,  1.0) );

	// then rotate using this cell's quaternion to place in 4D
    vec4 pt0 = quatMult( quatPerCell, p3sphere ); //position at time = 0

	// this is the normal to the point
	// same concept as for the position, but we add a 0 as the w component
	vec4 n3sphere = vec4( normal.zyx, 0.0);
    // above is normal on a cubical cell of the hypercube, below we get the corresponding
    // normal on the 3-sphere
    n3sphere = projVecPerp( n3sphere, p3sphere );
    // rotate the normal using this monkey's quaternion
	vec4 nt0 = quatMult(quatPerCell, n3sphere );

 //    // also rotate everything over time
    // vec4 quatOverTime = pointOnS3Geod( vec4(0,0,0,1), travelDir, 0.5*time );
    // vec4 quatOverTime = vec4(0,0,0,1);
    vec4 quatOverTime = moveQuat;

    vec4 p = quatMult( quatOverTime, pt0 );
    vec4 n = quatMult( quatOverTime, nt0 );

    // stereographic projection from 4D to 3D
    vec3 pos3 = vec3( p.x / (1.0-p.w), p.y / (1.0-p.w), p.z / (1.0-p.w) );

	// compute the color from the normal

    //// using HopfColor again...
    vec3 nColor = HopfColor(nt0);

    //// or the transported back to 1 normal
    // vec4 nTransported = quatMult(quatInv(pt0), nt0);
    // vec3 nColor = vec3(0.5,0.5,0.5) + 0.5*normalize( vec3( nTransported.x, nTransported.y, nTransported.z) );


    vec3 pColor = HopfColor(pt0);
    vColor = -0.5*(nColor-vec3(0.5,0.5,0.5)) + 1.0*(pColor-vec3(0.5,0.5,0.5)) + vec3(0.5,0.5,0.5);


    // vColor = pColor;
    // vColor = nColor;

	// take the final 3D position and project it onto the screen
    // gl_Position = projectionMatrix * modelViewMatrix * vec4( pos3 + vec3(0.0,-0.6,-1.5), 1.0 );
    // gl_Position = projectionMatrix * modelViewMatrix * vec4( pos3 + vec3(0.0,-0.7,-2.3), 1.0 );
    gl_Position = modelViewMatrix * vec4( pos3 , 1.0 ); //truncate before the projectionMatrix transform (continued on line 253)

	//Okay, now a slightly tricky thing:
	//The camera's going to cull any vertices that are closer than 0.2, or further away than 25, from the camera.
	//(in practice, this seems to be slightly different. I'm not sure why.)
	//When this happens, it creates the black triangle of death, which pulls the viewer out of the virtual reality.
	//To get around this, we're going to flatten out each vertex just before it reaches 0.2 or 25, moving it to where it would be.
	//So, essentially, this is like replacing actual stars with a correctly painted planetarium.
	//In case there's some weird z-layering going on, we're going to map -0.3,0  to -0.3, -0.2, preserving order.

	float oldz=gl_Position.z;

	if(oldz>-0.3 && oldz<0.0){
		//map [-0.3, 0.0] to [-0.3,-0.2]
		float newz=(oldz*0.3333333 - 0.2);
				gl_Position.x=gl_Position.x*newz/(oldz);
		gl_Position.y=gl_Position.y*newz/(oldz);
		gl_Position.z=newz;
		gl_Position.w=1.0;
	}
	gl_Position=projectionMatrix * gl_Position;
    // do fog
    // if ( fogType == 1 )
    // {
        // ramp fog
        // compute distance to camera from 0 to 1
        float zz = gl_Position.z / gl_Position.w;
        // go from 1 to 0 instead (0 is furthest and 1 is where the camera is )
        // ( note that the computed distance is not linear )
        float fogScale = 1. - zz;
        // anything closer than 0.1 gets regular color
        if ( fogScale > 0.1 )
            fogScale = 1.0;
        // everything else ramps from 0 to 1
        else
            fogScale = fogScale / 0.1;
        // mutliply color by this value to make it go to black
         vColor *= fogScale; //2015-02-17: fog re-enabled
    // }
    // else if ( fogType == 2 )
    // {
    //     // near fog
    //     float zz = gl_Position.z / gl_Position.w;
    //     // go from 1 to 0, and make the curve less straight
    //     float fogScale = pow( 1. - zz, 0.7 );
    //     // everything closer than 0.2 gets regular color
    //     // but everything else stays the same, creating a discontinuity
    //     if ( fogScale > 0.2 ) fogScale = 1.0;
    //     // mutliply color by this value to make it go to black
    //     vColor *= fogScale;
    // } else if (fogType == 3 ){
    // 	vColor.r *= mousePos.x/1000.;
    // 	vColor.g *= mousePos.y/1000.;
    // 	vColor.b *= abs(1. - (mousePos.x + mousePos.y)/1000.);
    // }

}
</script>

<script type="x-shader/x-vertex" id="fragmentShader">
// this gets called once per pixel
varying vec3 vColor;
void main()
{
	// just use the color we computed and assign it to this pixel
    gl_FragColor = vec4( vColor, 1. );
}
</script>

<script type="text/javascript" id="mainCode" src="js/hypernom.js"></script>
</html>
