<!DOCTYPE html>
<html ng-app="HW2">

  <head>
    <link rel="stylesheet" type="text/css" href="style.css" />
    <script src="//code.angularjs.org/1.3.0/angular.js" data-semver="1.3.0" data-require="angular.js@*"></script>
    <script src="http://www.cs.unm.edu/~angel/WebGL/7E/Common/MV.js" type="text/javascript"></script>
    <script src="http://www.cs.unm.edu/~angel/WebGL/7E/Common/webgl-utils.js" type="text/javascript"></script>
    <script src="scripts/main.js" type="text/javascript"></script>
    <script src="scripts/ShaderService.js" type="text/javascript"></script>
    <script src="scripts/WebGLDirective.js" type="text/javascript"></script>
    <script src="scripts/CanvasController.js" type="text/javascript"></script>
    <script src="scripts/ApplicationControls.js" type="text/javascript"></script>
    <script src="scripts/ShapeCreator.js" type="text/javascript"></script>
  </head>

  <body ng-keypress="handleKeypress($event)">
    <div ng-controller="CanvasController">
      <script type="x-shader/x-vertex" id="vertShader">
        attribute  vec4 vPosition;
        attribute  vec4 vColor;
        varying vec4 fColor;
        
        uniform mat4 modelViewMatrix;
        
        void main() 
        {
            fColor = vColor;
            gl_Position = modelViewMatrix * vPosition;
        } 
      </script>
      <script type="x-shader/x-fragment" id="fragShader">
        precision mediump float;
           
        varying vec4 fColor;
        
        void
        main()
        {
            gl_FragColor = fColor;
        }
      </script>
      <div gl-canvas="" id="hw2-canvas" step="step" animate="animate" speed="speed" wheel-count="wheelCount" cube-count="cubeCount" party-cube="partyCube" height="canvasHeight" width="canvasWidth"></div>
    </div>
    <span ng-include="'partials/applicationControls.html'"></span>
  </body>

</html>
(function(){

  var module = angular.module("HW2", []);
  
  module.run(function ($rootScope)
  {
    var self = $rootScope;
    
    self.cubeCount = 4;
    self.wheelCount = 3;
    self.x = self.y =  45;
    self.z = self.step = 0;
    
    /***
     * Method to run once the page is loaded
     */
    window.onload = function(){
      self.MAX_SPEED = 20 ;
      self.MIN_SPEED = 1;
    };
    
    /***
     * Toggles the application variable for speed
     */
    self.ChangeSpeed = function(newSpeed)
    {
      if(newSpeed > self.MAX_SPEED) newSpeed = self.MAX_SPEED;
      else if(newSpeed < self.MIN_SPEED) newSpeed = self.MIN_SPEED;
      
      self.speed = newSpeed;
    };
    
    /***
     * Toggles the application variable for animate
     */
    self.ToggleAnimation = function(animate)
    {
      self.animate = animate;
    };
    
    /***
     * Toggles the party cube feature 
     */
    self.TogglePartyCube = function(partyCube)
    {
      self.partyCube = partyCube;
    };
    
    /***
     * Rotates on the given axis
     */
    self.StepRotate = function(x, y, z)
    {
      self.step++;
      self.x = x*5;
      self.y = y*5;
      self.z = z*5;
    };
    
    /***
     * Update the wheel count in the application
     */
    self.ChangeWheelCount = function(wheelCount)
    {
      self.wheelCount = wheelCount;
    };
    
    /***
     * Update the cube count in the application
     */
    self.ChangeCubeCount = function(cubeCount)
    {
      self.cubeCount = cubeCount;
    };
    
    /***
     * Handles application keypresses in the application. The Application controls watch for changes in these
     * variables and update the UI when user clicks the key Automagically
     */
    self.handleKeypress = function($event)
    {
      var keyCode = $event.which;
      
      switch(keyCode)
      {
        case 32:
          self.ToggleAnimation(!self.animate);
          break;
        case 102:
        case 70:
          if(self.animate)
            self.ChangeSpeed(parseInt(self.speed) + 1);
          break;
        case 115:
        case 83:
          if(self.animate)
            self.ChangeSpeed(parseInt(self.speed) - 1);
          break;
        case 120:
        case 88:
          self.StepRotate(1,0,0);
          break;
        case 121:
        case 89:
          self.StepRotate(0,1,0);
          break;
        case 122:
        case 90:
          self.StepRotate(0,0,1);
          break;
      }
    };
  });
}());
/* Styles go here */
table, td {
    border: 1px solid black;
    border-collapse: collapse;
    padding-left:  10px;
    padding-right: 10px;
}
Computer Graphics HW 2
========================

Overview
---------
You will write a program that makes a multi-pinwheel that spins.  
You should have one gigantic pinwheel that turns around the center as indicated by the gray arrow shown below.  
The pinwheel is composed of three other pinwheels which each are turning about their center in the direction indicated by the black arrows as shown below.  
The pinwheel components are RGB cubes which are spinning around the vertical axis as shown by the white arrows (but all three groups of cubes must spin like this, the arrows are only show on the top cube).  
The two vertical cubes have RGB on opposing faces (just three faces are shown).  The two horizontal ones are an RGB color cube as shown (which is in the CubeSpin example).
Make the cube spin on a horizontal diagonal through the cube.  Using Black-bottom, white-top for the cubes with interpolated colors.

Use of Angular
--------------------
The assignment didn't require this, nor were there any type of resources to really help me out in htis regard, so every
thing you see was as I learned angular, so there may be some oddities, and bad choices that may cause fuss for some. This is also only the second assignment with WebGL,
and so there are things regarding WebGL that probably will make those who know what they are doing, crindge a bit. But hey, it works and made it possible to go above and beyond the
assignment requirement. Hopefully, there are some things here that others can benefit from.
<!DOCTYPE html>
<div ng-controller="ApplicationControls">
  <div>
    <label>Toggle Animation:</label>
    <button ng-mouseup="ToggleAnimation(!_animate)" ng-init="ToggleAnimation(false)" ng-model="_animate">{{_animate == true && 'On' || 'Off'}}</button>
  </div>
  <div ng-show="_animate">
    <p>
    <label>Rotation Speed:</label>
    <input type="range" min={{MIN_SPEED}} max={{MAX_SPEED}} ng-change="ChangeSpeed(_speed)" ng-init="ChangeSpeed(10)" ng-model="_speed" />
    <label ng-bind="_speed"></label>
  </div>
  <div ng-hide="_animate">
    <p>
    <button ng-click="StepRotate(1,0,0)">Rotate X</button>
    <button ng-click="StepRotate(0,1,0)">Rotate Y</button>
    <button ng-click="StepRotate(0,0,1)">Rotate Z</button>
    <br/>
  </div>
  <p>
    <div>
      <label><b>Keypress Control Legend</b>
      </label>
      <br/>
      <div>
        <table>
          <tbody>
            <tr ng-show="_animate">
              <td><b>Animation Controls:</b></td>
              <td>'f' or 'f' for Faster</td>
              <td>'s' or 'S' for Slower</td>
              <td>'space' for Pause</td>
            </tr>
            <tr>
              <td><b>Spin Cubes:</b></td>
              <td>'x' or 'X' for X direction</td>
              <td>'y' or 'Y' for Y direction</td>
              <td>'z' or 'Z' for Z direction</td>
            </tr>
          </tbody>
        </table>
      </div>
      <p>
        <div ng-show="_animate">
          <div>
            <label><b>Additional Controls</b>
            </label>
          </div>
          <div>
            <button ng-click="TogglePartyCube(!_partyCube)" ng-init="TogglePartyCube(false)" ng-model="_partyCube">Party Cube {{_partyCube == true && 'On' || 'Off'}}</button>
          </div>
          <div>
            <label>Wheel Count</label>
            <input type="range" min=1 max=20 ng-change="ChangeWheelCount(_wheelCount)" ng-init="ChangeWheelCount(3)" ng-model="_wheelCount">
            <label ng-bind="_wheelCount"></label>
          </div>
          <div>
            <label>Cube Count</label>
            <input type="range" min=1 max=20 ng-change="ChangeCubeCount(_cubeCount)" ng-init="ChangeCubeCount(4)" ng-model="_cubeCount">
            <label ng-bind="_cubeCount"></label>
          </div>
        </div>
    </div>

</div>
angular.module("HW2").controller('ApplicationControls',['$scope', function ApplicationControls($scope)
{
  var self = $scope;
  
  self.$watch("speed", function()
  {
    self._speed = self.speed;
  });
  
  self.$watch("animate", function()
  {
    self._animate = self.animate;
  });
  
  self.$watch("partyCube", function()
  {
    self._partyCube = self.partyCube;
  });
  
  self.$watch("wheelCount", function()
  {
    self._wheelCount = self.wheelCount;
  });
  
  self.$watch("cubeCount", function()
  {
    self._cubeCount = self.cubeCount;
  });
}]);
angular.module("HW2").controller('CanvasController', ['$scope', function CanvasController($scope)
{
  var self = $scope;
  
  self.canvasWidth  = 512;
  self.canvasHeight = 512;
}]);
angular.module('HW2')
  .directive('glCanvas', ['ShaderService','ShapeCreator','$interval', function glCanvas(ShaderService,ShapeCreator, $interval) 
  {
    //Scope contains the keywords that can be used in the html tag gl-canvas
    return     {
      restrict: 'A',
      scope: { 
        'width': '=',
        'height': '=',
        'speed': '=',
        'animate': '=',
        'partyCube': '=partyCube',
        'wheelCount': '=wheelCount',
        'cubeCount': '=cubeCount',
        'step': '='
      },
      link: function postLink(scope, element, attrs) 
      {
        var gl, shaders, animationTimer, pinWheel;
        var self = scope;
        
        /***
         * Constructor - Constructs this directive that manages the WebGL canvas
         */
        self.init = function()
        {
          //Create the canvas element and apply HTML details
          var canvas  = document.createElement( 'canvas' );
          canvas.width = self.width;
          canvas.height = self.height;
          canvas.style.border = "5px solid black";
          
          //Initialize canvas with WebGL. Set Viewport and default bg color; enable Depth test
          gl = self.initWebGL( canvas );
          gl.viewport( 0, 0, canvas.width, canvas.height );
          gl.clearColor( 1.0, 1.0, 1.0, 1.0 );
          gl.enable(gl.DEPTH_TEST);
      
          // Load shaders
          shaders = ShaderService.initShaders( gl, "vertShader",
                               "fragShader");
          gl.useProgram( shaders );
          
          //Create the pinwheel
          pinWheel = ShapeCreator.CreateWheel(true);
          
          //Add canvas to the HTML and then render
          element[0].appendChild( canvas );
          self.render();
        };
        
        /***
         * Clears the screen and renders the pinwheel
         */
        self.render = function()
        {
          if(self.partyCube === false || self.animate === false)
            gl.clearColor( 1.0, 1.0, 1.0, 1.0 );
          else if(self.partyCube === true)
            gl.clearColor( Math.random()*1.0, Math.random()*2.0, Math.random()*2.0, 2.0 );
          
          
          gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
          pinWheel.render(gl, shaders);
        };
        
        /***
         * Initializes WebGL
         */
        self.initWebGL = function(canvas) 
        {
          gl = null;
      
          try {
            // Try to grab the standard context. If it fails, fallback to experimental.
            gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
          } catch (e) {}
      
          // If we don't have a GL context, give up now
          if (!gl) {
            alert("Unable to initialize WebGL. Your browser may not support it.");
            gl = null;
          }
      
          return gl;
        };
        
        /***
         * Listener for value changes of animate and speed. Launches a new timer
         * which will animate the scene
         */
        self.$watch('animate + speed', function()
        {
          if(animationTimer)
            $interval.cancel(animationTimer);
          
          if(self.animate)
            animationTimer = $interval(self.render, 500/self.speed);
          else
            self.render();
        });
        
        /***
         * Listener for value changes of animate and speed. Launches a new timer
         * which will animate the scene
         */
        self.$watch('cubeCount + wheelCount', function()
        {
          pinWheel.changeCubeCount(self.cubeCount);
          pinWheel.changeWheelCount(self.wheelCount);
        });
        
        /***
         * Listens for when the user clicks the step buttons
         */
        self.$watch('step', function()
        {
          if(self.animate === false)
            self.render();
        });
        
        //Run Constructor
        self.init();
      }
    };
  }]);
angular.module("HW2").service("ShaderService",
    [ '$rootScope', function( $rootScope ) {
    var service = {
      initShaders: function(gl, vertexShaderId, fragmentShaderId) {
        var vertShdr, fragShdr, msg;
        
        //Load Vertex shader from HTML
        var vertElem = document.getElementById(vertexShaderId);
        if (!vertElem) {
          alert("Unable to load vertex shader " + vertexShaderId);
          return -1;
        } 
        else {
          vertShdr = gl.createShader(gl.VERTEX_SHADER);
          gl.shaderSource(vertShdr, vertElem.text);
          gl.compileShader(vertShdr);
          
          if (!gl.getShaderParameter(vertShdr, gl.COMPILE_STATUS)) {
            msg = "Vertex shader failed to compile.  The error log is:" + "<pre>" + gl.getShaderInfoLog(vertShdr) + "</pre>";
            alert(msg);
            return -1;
          }
        }
        
        //Load frag shader from html
        var fragElem = document.getElementById(fragmentShaderId);
        if (!fragElem) {
          alert("Unable to load frag shader " + fragmentShaderId);
          return -1;
        } else {
          fragShdr = gl.createShader(gl.FRAGMENT_SHADER);
          gl.shaderSource(fragShdr, fragElem.text);
          gl.compileShader(fragShdr);
          
          if (!gl.getShaderParameter(fragShdr, gl.COMPILE_STATUS)) {
            msg = "Fragment shader failed to compile.  The error log is:" + "<pre>" + gl.getShaderInfoLog(fragShdr) + "</pre>";
            alert(msg);
            return -1;
          }
        }
        
        //Compile and link shader code
        var program = gl.createProgram();
        gl.attachShader(program, vertShdr);
        gl.attachShader(program, fragShdr);
        gl.linkProgram(program);
        if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
          msg = "Shader program failed to link.  The error log is:" + "<pre>" + gl.getProgramInfoLog(program) + "</pre>";
          alert(msg);
          return -1;
        }
  
        //return shader
        return program;
      }
    };
    
    return service;
}]);
angular.module("HW2").service("ShapeCreator",
  [ '$rootScope', function( $rootScope ) {
    var service = function(){
      
      /***
       * Pulled from Angel's code. Creates a scaling matrix with the given length since it will always be applied
       * to cubes
       */
      var scale4 = function (length) {
         var result = mat4();
         result[0][0] = length;
         result[1][1] = length;
         result[2][2] = length;
         return result;
      };
      
      var self = $rootScope;
      var theta = 0;
      
      var x = 0;
      var y = 0;
      var z = 0;
      
      /***
       * Creates a wheel object that can draw itself and transform, as well as add shapes
       * 
       * root - If this is the root pinwheel, it will create wheels as it's subshape, otherwise it will make cubes
       */
       //****Begin Wheel Object ****//
      var CreateWheel = function(root){
        var shapes = [];
        var transforms = [];
        
        /***
         * Constructor for this pinwheel. Creates a list of subshapes
         */
        var init = function()
        {
          if(root === true) addWheels();
          else addCubes();
        };
        
        /***
         * Adds the specified number of wheels to this wheel
         */
        var addWheels = function()
        {
          changeWheelCount(self.wheelCount);
        };
        
        /***
         * Adds the specified number of cubes to this wheel
         */
        var addCubes = function()
        {
          changeCubeCount(self.cubeCount);
        };
        
        /***
         * Change the number of the wheels under the root wheel
         */
        var changeWheelCount = function(newCount)
        {
          while(shapes.length > newCount) shapes.pop();
          while(shapes.length < newCount) shapes.push(CreateWheel());
        };
        
        /***
         * Change the number of cubes under the root cube
         */
        var changeCubeCount = function(newCount)
        {
          if(root === true)
          {
            for(var i = 0; i < shapes.length; i++)
              shapes[i].changeCubeCount(newCount);
          }
          else
          {
            while(shapes.length > newCount) shapes.pop();
            while(shapes.length < newCount) shapes.push(CreateCube(shapes.length % 2 === 1));
          }
        };
        
        /***
         * Draws all the subshapes, applying standard transformations downward.
         */
        var render = function(gl, shaders)
        {
          var n = shapes.length;
          
          updateAngles();
          
          for(var i = 0; i < n; i++)
          {
            //Shift value to create the wheel
            var radius = root ? 0.4 : 0.2;
            
            //Rotation angle for each shape in this wheel; calculated based on the shape count
              //and the direction it should be moving
            var k = ((i*2*Math.PI)/n) + (root ? -radians(theta/2) : radians(theta/2));
            
            //Add the translation to the sub shape's list of transformations
            shapes[i].addTransform(translate(radius*(Math.cos(k)-Math.sin(k)), 
                                             radius*(Math.sin(k)+Math.cos(k)), 0));
            
            //Draw the subshape
            shapes[i].render(gl, shaders);
          }
          
          //Only update theta if we are animating
          if(root === true && self.animate === true) theta+=5;
        };
        
        /***
         * Updates the current angle modifiers with the user input
         */
        var updateAngles = function()
        {
          x+=self.x;
          y+=self.y;
          z+=self.z;
          
          self.x = self.y = self.z = 0;
        };
        
        /***
         *  Adds the given transform to all it's children subshapes
         * 
         * transform - Transform matrix to be applied
         */
        var addTransform = function(transform)
        {
          for(var i = 0; i < shapes.length; i++)
            shapes[i].addTransform(transform);
        };
        
        //Calls the constructor at the creation of this object
        init();
        
        //All the public methods
        return {
          updateAngles: updateAngles,
          changeCubeCount: changeCubeCount,
          changeWheelCount: changeWheelCount,
          addTransform: addTransform,
          render: render
        };
      };
      //****End Wheel Object ****//
      
      /***
       *  Creates a cube object
       * 
       * interpolated - If true, will use the crazy color scheme, otherwise just RGB
       */
       //****Begin Cube Object ****//
      var CreateCube = function(interpolated){
        var NumVertices  = 36;
        
        var points = [];
        var colors = [];
        var transforms =[];
        
        /***
         * Constructor - Creates the cube
         */
        var init = function(){
          quad(1, 0, 3, 2);
          quad(2, 3, 7, 6);
          quad(3, 0, 4, 7);
          quad(6, 5, 1, 2);
          quad(4, 5, 6, 7);
          quad(5, 4, 0, 1);
        };
        
        /***
         * First color pallet of just RGB
         */
        var colorSet1 = function(){
          return [
            [ 0.0, 0.0, 0.0, 1.0 ],  // black
            [ 1.0, 1.0, 0.0, 1.0 ],  // yellow
            [ 0.0, 0.0, 1.0, 1.0 ],  // blue
            [ 1.0, 0.0, 0.0, 1.0 ],  // red
            [ 0.0, 1.0, 0.0, 1.0 ],  // green
            [ 0.0, 1.0, 1.0, 1.0 ],  // cyan
            [ 1.0, 0.0, 1.0, 1.0 ],  // magenta
            [ 1.0, 1.0, 1.0, 1.0 ]   // white
          ];
        };
        
        /***
         * Second color pallet of RGB and others from robot arm solution
         */
        var colorSet2 = function(){
          return [
            [ 0.0, 0.0, 0.0, 1.0 ],   // black
            [ 1.0, 0.0, 0.0, 1.0 ],  // red
            [ 1.0, 1.0, 0.0, 1.0 ],  // yellow
            [ 0.0, 1.0, 0.0, 1.0 ],  // green
            [ 0.0, 0.0, 1.0, 1.0 ],  // blue
            [ 1.0, 0.0, 1.0, 1.0 ],  // magenta
            [ 1.0, 1.0, 1.0, 1.0 ],  // white
            [ 0.0, 1.0, 1.0, 1.0 ]   // cyan
            ];
        };
        
        var cubeVerts = function(){
          var n = 0.5;
          
          return [
            vec3(-n,-n, n),
            vec3(-n, n, n),
            vec3( n, n, n),
            vec3( n,-n, n),
            vec3(-n,-n,-n),
            vec3(-n, n,-n),
            vec3( n, n,-n),
            vec3( n,-n,-n),
          ];
        };
        
        /***
         * Creates a quad for the cube with points and colors
         */
        var quad = function(a, b, c, d) {
          
          var vertexColors = interpolated ? colorSet2() : colorSet1();
          
          var indices = [ a, b, c, a, c, d ];

          for ( var i = 0; i < indices.length; ++i ) {
            points.push( cubeVerts()[indices[i]] );
            
            interpolated ?
              colors.push( vertexColors[indices[i]] ) :
              colors.push( vertexColors[a]);
          }
        };
        
        /***
         *Adds a transform to the list of transformations to be applied ot the cube
         */
        var addTransform = function(transformToAdd) {
          transforms.push(transformToAdd);
        };
        
        /***
         * Draws the cube on screen
         */
        var render = function(gl, shaders) {
          //****Begin Color****//
          var cBuffer = gl.createBuffer();
          gl.bindBuffer( gl.ARRAY_BUFFER, cBuffer );
          gl.bufferData( gl.ARRAY_BUFFER, flatten(colors), gl.STATIC_DRAW );
      
          var vColor = gl.getAttribLocation( shaders, "vColor" );
          gl.vertexAttribPointer( vColor, 4, gl.FLOAT, false, 0, 0 );
          gl.enableVertexAttribArray( vColor );
          //****End Color****//
      
          //****Begin Placement****//
          var vBuffer = gl.createBuffer();
          gl.bindBuffer( gl.ARRAY_BUFFER, vBuffer );
          gl.bufferData( gl.ARRAY_BUFFER, flatten(points), gl.STATIC_DRAW );
      
          var vPosition = gl.getAttribLocation( shaders, "vPosition" );
          gl.vertexAttribPointer( vPosition, 3, gl.FLOAT, false, 0, 0 );
          gl.enableVertexAttribArray( vPosition );
          //****End Placement****//
        
          //***Begin Transformations****//
          var modelViewMatrixLoc = gl.getUniformLocation(shaders, "modelViewMatrix");
          var modelViewMatrix = mat4();
          var prime = interpolated ? -90 : 0;
          
          //Apply Parental Transforms
          while(transforms.length > 0)
            modelViewMatrix = mult(modelViewMatrix, transforms.pop());
          
          //Scale cube
          modelViewMatrix = mult(modelViewMatrix, scale4(0.15));
          
          //Custom rotation. This includes the initial angle plus the adjustments made
            // with the X Y Z buttons
          modelViewMatrix = mult(modelViewMatrix, rotate(x+prime, 1, 0, 0));
          modelViewMatrix = mult(modelViewMatrix, rotate(y+theta, 0, 1, 0));
          modelViewMatrix = mult(modelViewMatrix, rotate(z, 0, 0, 1));
          

            
          gl.uniformMatrix4fv( modelViewMatrixLoc,  false, flatten(modelViewMatrix) );
          //***End Transformations****//
          
          //Draw the cube
          gl.drawArrays( gl.TRIANGLES, 0, 36 );
        };
        
        //Call Constructor
        init();
        
        //Public methods
        return {
          addTransform: addTransform,
          render: render
        };
      };
      
      //Public methods in the Shape Creator service
      return {
        CreateCube: CreateCube,
        CreateWheel: CreateWheel
      };
    };
    //****End Wheel Object ****//
    
    return service();
    }]);