<!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();
}]);