CS 4722 - Computer Graphics and Multimedia
Module #8, Assignment #2Exercise #1Build the RunningRobot application that creates a 3D scene that shows the robot figure with attached limbs with colors as shown in the picture below, and with a ground as shown below the figure that has the dimensions 100x100x0.5. Furthermore, place 10 columns covered by various textures and colors in different locations that you can customize around the scene (no two columns can overlap or intersect with the robot figure), and each column is to have a sphere with a color or a texture covering it. The spheres will be spinning, but that will be hard to see on the colored spheres, yet it will be easy to see on the spheres that are covered in a texture: Your Canvas should be 800x500 in dimension, and you should not have any visible controls on your page. In addition to that your program should have a SkyBox around it showing a horizon in every direction. When the up arrow or down arrow is pressed the robot should move forward. It should move with the walking animation when the up arrow is pressed, and it should move with the running animation at 4x the speed when the down arrow is pressed. The robot should not be able to leave the grounds of the scene or move through the columns. And your camera eye should move around the scene using your mouse down button (to drag the view horizontally and vertically), your mouse scroll wheel (to zoom in and out), your arrow keys (to rotate left or right) and the Page Up and Page Down keys (to zoom in and out). And finally, you must add lighting to your application with shadows showing from every object in the scene, and you must add textures to at least some of your columns. It will take a lot of work to get all of the features into this program, and it is worth it because it does a good job of bringing most of the ideas presented in this course together in a final project. You will use a simple stick figure program as the starting point for your code. The partial HTML code is given here: figure.html. The partial JavaScript code is given here: figure.js. You will use the following six images as your SkyBox images, so create an "Images" folder in your project and add them to it:
You will use the following three images for your texture images, that you will also add to your "Images" folder (Note: you will see below how you can add other texture images of your own choosing):
To accomplish all of these tasks your first step will be to get the stick figure to look like the Robot figure. Right now the stick figure looks like the following: The first change you need to make is to remove the controls. In your HTML code, remove all of the <div> ... </div> lines. And that means remove the <div> lines and the </div> lines and everything in between them. Then go into your JavaScript code in your init() function and remove all of the document.getElementById(...) calls. The next change you need to make is to add color to the different components of the robot so it is easier to see any other changes that you make. To do this add the following declaration to the Fragment Shader in your HTML code: uniform vec4 fColor;
Next, change the line setting "gl_FragColor" to the following line: gl_FragColor = fColor;
Next, in your JavaScript code add the following declarations: var black = vec4(0.0, 0.0, 0.0, 1.0);
var white = vec4(1.0, 1.0, 1.0, 1.0);
var red = vec4(1.0, 0.0, 0.0, 1.0);
var yellow = vec4(1.0, 1.0, 0.0, 1.0);
var lightblue = vec4(0.0, 0.0, 1.0, 1.0);
var darkblue = vec4(0.0, 0.0, 0.5, 1.0);
var lightgreen = vec4(0.0, 1.0, 0.0, 1.0);
var darkgreen = vec4(0.0, 0.5, 0.0, 1.0);
var lightcyan = vec4(0.0, 1.0, 1.0, 1.0);
var darkcyan = vec4(0.0, 0.5, 0.5, 1.0);
var brown = vec4(0.65, 0.16, 0.16, 1);
var colorLoc;
Then in your init() function, add the following line before the render() call: colorLoc = gl.getUniformLocation(program, "fColor");
Then right before the "gl.drawArrays(...);" line in your torso() function, add the following line to make the robot's torso red: gl.uniform4fv(colorLoc, red);
And right before the "gl.drawArrays(...);" line in your head() function, add the following line to make the robot's head yellow: gl.uniform4fv(colorLoc, yellow);
And right before the "gl.drawArrays(...);" line in your leftUpperArm() function, add the following line to make the robot's left upper arm light green: gl.uniform4fv(colorLoc, lightgreen);
And right before the "gl.drawArrays(...);" line in your leftLowerArm() function, add the following line to make the robot's left upper arm dark green: gl.uniform4fv(colorLoc, darkgreen);
And right before the "gl.drawArrays(...);" line in your rightUpperArm() function, add the following line to make the robot's right upper arm light green: gl.uniform4fv(colorLoc, lightgreen);
And right before the "gl.drawArrays(...);" line in your rightLowerArm() function, add the following line to make the robot's right upper arm dark green: gl.uniform4fv(colorLoc, darkgreen);
And right before the "gl.drawArrays(...);" line in your leftUpperLeg() function, add the following line to make the robot's left upper leg light blue: gl.uniform4fv(colorLoc, lightblue);
And right before the "gl.drawArrays(...);" line in your leftLowerLeg() function, add the following line to make the robot's left lower leg dark blue: gl.uniform4fv(colorLoc, darkblue);
And right before the "gl.drawArrays(...);" line in your rightUpperLeg() function, add the following line to make the robot's right upper leg light blue: gl.uniform4fv(colorLoc, lightblue);
And right before the "gl.drawArrays(...);" line in your rightLowerLeg() function, add the following line to make the robot's right lower leg dark blue: gl.uniform4fv(colorLoc, darkblue);
Now your robot figure should look like the following: Next, you need to connect the arms and legs of the robot to the body and start off with the arms of the robot pointing down. To make these changes go into the initFigureNodes(Id) function and find the text that is part of line, not the full line: + upperArmWidth
This text is there twice. In both cases replace it with: * 0.75
Similarly, while still in the initFigureNodes(Id) function find the text that is part of line, not the full line: + upperLegWidth
This text is there twice. In both cases replace it with: * 0.75
And next, to make your robot's arms smaller than the legs, go to your declarations at the top and change the following two lines: var upperArmHeight = 3.0;
var lowerArmHeight = 2.0;
Change these two lines to the following var upperArmHeight = 2.4;
var lowerArmHeight = 1.6;
Then bring the arms down by changing the third 0 to 180 on the following line that sets the angles of the different body components: var figureTheta = [0, 0, 0, 0, 0, 0, 180, 0, 180, 0, 0];
Change this line to the following var figureTheta = [0, 0, 180, 0, 180, 0, 180, 0, 180, 0, 0];
Now your robot figure should look like the following: Next, you will add the eyes and the mouth to the robot's head. However, to do this you will be using flatten spheres, and so you need to add code to make sphere shapes. Later, you will also make the columns with cylinder shapes, so you will add the code to make cylinder shapes as well. Above your render() function, add the following functions to your code for making spheres and cylinders: function makeSphere() {
var phi1, phi2, sinPhi1, sinPhi2, cosPhi1, cosPhi2;
var theta1, theta2, sinTheta1, sinTheta2, cosTheta1, cosTheta2;
var p1, p2, p3, p4;
var latitudeBands = 30;
var longitudeBands = 30;
// For each latitudinal band determine phi's value
for (var latNumber = 1; latNumber <= latitudeBands; latNumber++) {
phi1 = Math.PI * (latNumber - 1) / latitudeBands;
sinPhi1 = Math.sin(phi1);
cosPhi1 = Math.cos(phi1);
phi2 = Math.PI * latNumber / latitudeBands;
sinPhi2 = Math.sin(phi2);
cosPhi2 = Math.cos(phi2);
// For each longitudinal band determine theta's value and other calculations
for (var longNumber = 1; longNumber <= longitudeBands; longNumber++) {
theta1 = 2 * Math.PI * (longNumber - 1) / longitudeBands;
sinTheta1 = Math.sin(theta1);
cosTheta1 = Math.cos(theta1);
theta2 = 2 * Math.PI * longNumber / longitudeBands;
sinTheta2 = Math.sin(theta2);
cosTheta2 = Math.cos(theta2);
p1 = vec4(cosTheta1 * sinPhi1, cosPhi1, sinTheta1 * sinPhi1, 1.0);
p2 = vec4(cosTheta2 * sinPhi1, cosPhi1, sinTheta2 * sinPhi1, 1.0);
p3 = vec4(cosTheta1 * sinPhi2, cosPhi2, sinTheta1 * sinPhi2, 1.0);
p4 = vec4(cosTheta2 * sinPhi2, cosPhi2, sinTheta2 * sinPhi2, 1.0);
pointsArray.push(p1);
pointsArray.push(p2);
pointsArray.push(p3);
pointsArray.push(p2);
pointsArray.push(p4);
pointsArray.push(p3);
}
}
}
function makeCylinder(isClosed) {
var x1, x2, y1, y2, xoff;
var theta1, theta2, sinTheta1, sinTheta2, cosTheta1, cosTheta2;
var p1, p2, p3, p4;
var radialSlices = 30;
var verticalSlices = 30;
// For each vertical slice get the y values
for (var vertNumber = 1; vertNumber <= verticalSlices; vertNumber++) {
y1 = ((vertNumber - 1) * (1 / verticalSlices) - 0.5);
y2 = (vertNumber * (1 / verticalSlices) - 0.5);
// For each radial slice determine theta's value and other calculations
for (var radNumber = 1; radNumber <= radialSlices; radNumber++) {
theta1 = 2 * Math.PI * (radNumber - 1) / radialSlices;
sinTheta1 = Math.sin(theta1);
cosTheta1 = Math.cos(theta1);
theta2 = 2 * Math.PI * radNumber / radialSlices;
sinTheta2 = Math.sin(theta2);
cosTheta2 = Math.cos(theta2);
p1 = vec4(cosTheta1 * 0.5, y1, sinTheta1 * 0.5, 1.0);
p2 = vec4(cosTheta2 * 0.5, y1, sinTheta2 * 0.5, 1.0);
p3 = vec4(cosTheta1 * 0.5, y2, sinTheta1 * 0.5, 1.0);
p4 = vec4(cosTheta2 * 0.5, y2, sinTheta2 * 0.5, 1.0);
pointsArray.push(p1);
pointsArray.push(p2);
pointsArray.push(p3);
pointsArray.push(p2);
pointsArray.push(p4);
pointsArray.push(p3);
}
}
if (isClosed) {
var count = pointsArray.length;
for (var radNumber = 1; radNumber <= radialSlices; radNumber++) {
theta1 = 2 * Math.PI * (radNumber - 1) / radialSlices;
sinTheta1 = Math.sin(theta1);
cosTheta1 = Math.cos(theta1);
theta2 = 2 * Math.PI * radNumber / radialSlices;
sinTheta2 = Math.sin(theta2);
cosTheta2 = Math.cos(theta2);
p1 = vec4(0, -0.5, 0);
p2 = vec4(cosTheta1 * 0.5, -0.5, sinTheta1 * 0.5, 1.0);
p3 = vec4(cosTheta2 * 0.5, -0.5, sinTheta2 * 0.5, 1.0);
pointsArray.push(p1);
pointsArray.push(p2);
pointsArray.push(p3);
p1 = vec4(0, 0.5, 0);
p2 = vec4(cosTheta1 * 0.5, 0.5, sinTheta1 * 0.5, 1.0);
p3 = vec4(cosTheta2 * 0.5, 0.5, sinTheta2 * 0.5, 1.0);
pointsArray.push(p1);
pointsArray.push(p2);
pointsArray.push(p3);
}
cylinderEdgeVerts = pointsArray.length - count;
}
}
Next, you need variables to keep track of which vertices in the pointsArray are points for the cube shape, which are for the sphere shape, and which are are for the cylinder shape. So add the following variable declarations to your code: var cubeVerts = 0;
var sphereVerts = 0;
var cylinderVerts = 0;
var cylinderEdgeVerts = 0;
Then in your init() function, AFTER you call "makeCube()", add the following lines: cubeVerts = pointsArray.length;
makeSphere();
sphereVerts = pointsArray.length - cubeVerts;
makeCylinder(true);
cylinderVerts = pointsArray.length - cubeVerts - sphereVerts;
Next, you need modify all of the places where you are drawing your cube shape and use the cubeVerts variable there instead of pointsArray.length. This means that every place where you have the following line: gl.drawArrays(gl.TRIANGLES, 0, pointsArray.length);
You need to replace that line with the following line: gl.drawArrays(gl.TRIANGLES, 0, cubeVerts);
Now you are ready to create the eyes and the mouth for your robot. Once again you will need to declare variables to store details about them, so in your declarations, find the following line: var rightLowerLegId = 9;
BELOW that line, add the following lines: var eye1Id = 11;
var eye2Id = 12;
var mouthId = 13;
Next, find the following line: var headWidth = 1.0;
BELOW that line, add the following lines: var eyeWidth = 0.1;
var eyeDepth = 0.05;
var mouthWidth = 0.3;
var mouthHeight = 0.1;
var mouthDepth = 0.05;
Next, find the following set of lines: var numFigureNodes = 10;
var figureTheta = [0, 0, 0, 0, 0, 0, 180, 0, 180, 0, 0];
var figure = [];
This time, REPLACE these lines with the following lines: var numFigureNodes = 14;
var figureTheta = [0, 0, 180, 0, 180, 0, 180, 0, 180, 0, 0, 0, 0, 0];
var figure = [];
var figurePos = [0, 0, 0];
var fTheta;
Next, go to your initFigureNodes(Id) function, and find the following two lines: figure[headId] = createNode(m, head, leftUpperArmId, null);
break;
REPLACE these two lines with the following lines: figure[headId] = createNode(m, head, leftUpperArmId, eye1Id);
break;
case eye1Id:
m = translate(0.0, torsoHeight + 0.5 * headHeight, 0.0);
m = mult(m, rotate(figureTheta[head1Id], 1, 0, 0))
m = mult(m, rotate(figureTheta[head2Id], 0, 1, 0));
m = mult(m, translate(0.0, -0.5 * headHeight, 0.0));
figure[eye1Id] = createNode(m, lefteye, eye2Id, null);
break;
case eye2Id:
m = translate(0.0, torsoHeight + 0.5 * headHeight, 0.0);
m = mult(m, rotate(figureTheta[head1Id], 1, 0, 0))
m = mult(m, rotate(figureTheta[head2Id], 0, 1, 0));
m = mult(m, translate(0.0, -0.5 * headHeight, 0.0));
figure[eye2Id] = createNode(m, righteye, mouthId, null);
break;
case mouthId:
m = translate(0.0, torsoHeight + 0.5 * headHeight, 0.0);
m = mult(m, rotate(figureTheta[head1Id], 1, 0, 0))
m = mult(m, rotate(figureTheta[head2Id], 0, 1, 0));
m = mult(m, translate(0.0, -0.5 * headHeight, 0.0));
figure[mouthId] = createNode(m, mouth, null, null);
break;
Next, go find your rightLowerLeg() function, and go BELOW that entire function and add the following functions: function lefteye() {
instanceMatrix = mult(modelViewMatrix, translate(-0.2, -4.0, 0.5));
instanceMatrix = mult(instanceMatrix, scalem(eyeWidth, eyeWidth, eyeDepth));
gl.uniformMatrix4fv(modelViewMatrixLoc, false, flatten(instanceMatrix));
gl.uniform4fv(colorLoc, black);
gl.drawArrays(gl.TRIANGLES, cubeVerts, sphereVerts);
}
function righteye() {
instanceMatrix = mult(modelViewMatrix, translate(0.2, -4.0, 0.5));
instanceMatrix = mult(instanceMatrix, scalem(eyeWidth, eyeWidth, eyeDepth));
gl.uniformMatrix4fv(modelViewMatrixLoc, false, flatten(instanceMatrix));
gl.uniform4fv(colorLoc, black);
gl.drawArrays(gl.TRIANGLES, cubeVerts, sphereVerts);
}
function mouth() {
instanceMatrix = mult(modelViewMatrix, translate(0.0, -4.5, 0.5));
instanceMatrix = mult(instanceMatrix, scalem(mouthWidth, mouthHeight, mouthDepth));
gl.uniformMatrix4fv(modelViewMatrixLoc, false, flatten(instanceMatrix));
gl.uniform4fv(colorLoc, black);
gl.drawArrays(gl.TRIANGLES, 0, cubeVerts);
}
Now your robot figure should look like the following: Next, you need to add the scene around your robot. When you do this you need the canvas to be the right size, so go into your HTML code and find the canvas tag and set its width to "800" and its height to "500". Next, go back to your JavaScript code where you are going to create a set of new variables and change the projection view to perspective. You will also create a new hierarchical tree model variable called scene and traverse it inside of the render() function. Find the following line in your variable declarations: var figureTheta = [0, 0, 180, 0, 180, 0, 180, 0, 180, 0, 0, 0, 0, 0];
Change that first zero to fifty, so you get: var figureTheta = [50, 0, 180, 0, 180, 0, 180, 0, 180, 0, 0, 0, 0, 0];
This rotates the robot, so it is easier to see initially. The next thing to do is to add the following new declarations: var groundWidth = 100;
var groundHeight = .5;
var groundFloor = -4.9;
var groundId = 0;
var columnId1 = 1;
var columnId2 = 2;
var columnId3 = 3;
var columnId4 = 4;
var columnId5 = 5;
var columnId6 = 6;
var columnId7 = 7;
var columnId8 = 8;
var columnId9 = 9;
var columnId10 = 10;
var columnDiameter = 2;
var columnHeight = 18;
var colPosArray = [null, // dummy value
vec3(5, 0, 5), vec3(40, 0, 40), vec3(30, 0, 30),
vec3(20, 0, 40), vec3(10, 0, 40), vec3(0, 0, 40),
vec3(-10, 0, 40), vec3(-20, 0, 40), vec3(-30, 0, 40),
vec3(-40, 0, 40)];
var numSceneNodes = 11;
var scene = [];
var at = vec3(0.0, 0.0, 0.0);
var up = vec3(0.0, 1.0, 0.0);
var eye;
var xAngle = 0.0;
var yAngle = 0.5;
var eyeRadius = 50.0;
var fovy = 37;
var aspect = 1.3;
var pnear = 0.01;
var pfar = 1000.0;
var trackingMouse = false;
var curx = 0;
var cury = 0;
var armStack = [];
var legStack = [];
Then, in your init() function, find the following line: projectionMatrix = ortho(-10.0, 10.0, -10.0, 10.0, -10.0, 10.0);
REPLACE that line with the following line: projectionMatrix = perspective(fovy, aspect, pnear, pfar);
Then, in your init() function, find the following lines: for (var i = 0; i < numFigureNodes; i++) {
figure[i] = createNode(null, null, null, null);
initFigureNodes(i);
}
BELOW these lines add the following lines: for (var i = 0; i < numSceneNodes; i++) {
scene[i] = createNode(null, null, null, null);
initSceneNodes(i);
}
Next, find your traverseFigure(Id) function, and right ABOVE that function add the following function: function initSceneNodes(Id) {
var m;
switch (Id) {
case groundId:
m = mat4();
scene[groundId] = createNode(m, ground, columnId1, null);
break;
case columnId1:
m = translate(colPosArray[columnId1]);
scene[columnId1] = createNode(m, column, columnId2, null);
break;
case columnId2:
m = translate(colPosArray[columnId2]);
scene[columnId2] = createNode(m, column, columnId3, null);
break;
case columnId3:
m = translate(colPosArray[columnId3]);
scene[columnId3] = createNode(m, column, columnId4, null);
break;
case columnId4:
m = translate(colPosArray[columnId4]);
scene[columnId4] = createNode(m, column, columnId5, null);
break;
case columnId5:
m = translate(colPosArray[columnId5]);
scene[columnId5] = createNode(m, column, columnId6, null);
break;
case columnId6:
m = translate(colPosArray[columnId6]);
scene[columnId6] = createNode(m, column, columnId7, null);
break;
case columnId7:
m = translate(colPosArray[columnId7]);
scene[columnId7] = createNode(m, column, columnId8, null);
break;
case columnId8:
m = translate(colPosArray[columnId8]);
scene[columnId8] = createNode(m, column, columnId9, null);
break;
case columnId9:
m = translate(colPosArray[columnId9]);
scene[columnId9] = createNode(m, column, columnId10, null);
break;
case columnId10:
m = translate(colPosArray[columnId10]);
scene[columnId10] = createNode(m, column, null, null);
break;
}
}
Next, find your torso() function, and right ABOVE that function add the following function: function traverseScene(Id) {
if (Id == null) return;
mvStack.push(modelViewMatrix);
modelViewMatrix = mult(modelViewMatrix, scene[Id].transform);
scene[Id].render();
if (scene[Id].child != null)
traverseScene(scene[Id].child);
modelViewMatrix = mvStack.pop();
if (scene[Id].sibling != null)
traverseScene(scene[Id].sibling);
}
Next, find your makeCube() function, and right ABOVE that function add the following functions: function ground() {
instanceMatrix = mult(modelViewMatrix, translate(0.0, groundFloor, 0.0));
instanceMatrix = mult(instanceMatrix, scalem(groundWidth, groundHeight, groundWidth));
gl.uniformMatrix4fv(modelViewMatrixLoc, false, flatten(instanceMatrix));
gl.uniform4fv(colorLoc, darkcyan);
gl.drawArrays(gl.TRIANGLES, 0, cubeVerts);
}
function column() {
instanceMatrix = mult(modelViewMatrix, translate(0.0, groundFloor + columnHeight / 2, 0.0));
instanceMatrix = mult(instanceMatrix, scalem(columnDiameter, columnHeight, columnDiameter));
gl.uniformMatrix4fv(modelViewMatrixLoc, false, flatten(instanceMatrix));
gl.uniform4fv(colorLoc, brown);
gl.drawArrays(gl.TRIANGLES, cubeVerts+sphereVerts, cylinderVerts-cylinderEdgeVerts);
gl.uniform4fv(colorLoc, black);
gl.drawArrays(gl.TRIANGLES, cubeVerts+sphereVerts+cylinderVerts-cylinderEdgeVerts, cylinderEdgeVerts);
}
Next, inside your render() function, above the traverseFigure(torsoId); line add the following lines: if (yAngle < 0)
yAngle += 2 * Math.PI;
if (yAngle > 2 * Math.PI)
yAngle -= 2 * Math.PI;
if (xAngle < 0)
xAngle += 2 * Math.PI;
if (xAngle > 2 * Math.PI)
xAngle -= 2 * Math.PI;
if (yAngle > Math.PI / 2 && yAngle < 3 * Math.PI / 2) {
up = vec3(0.0, -1.0, 0.0);
}
else {
up = vec3(0.0, 1.0, 0.0);
}
var eye = vec3(figurePos[0] + eyeRadius * Math.cos(xAngle) * Math.cos(yAngle),
figurePos[1] + eyeRadius * Math.sin(yAngle),
figurePos[2] + eyeRadius * Math.sin(xAngle) * Math.cos(yAngle));
at = vec3(figurePos);
modelViewMatrix = lookAt(eye, at, up);
traverseScene(groundId);
Now your scene should look like the following: Next, you will enable your camera eye to move around the scene using your mouse down button, your mouse scroll wheel, your arrow keys and the Page Up and Page Down keys. To do this, add the following handlers to your init() function above the "render();" call. canvas.addEventListener("mousedown", function (event) {
var x = 2 * event.clientX / canvas.width - 1;
var y = 2 * (canvas.height - event.clientY) / canvas.height - 1;
startMotion(x, y);
});
canvas.addEventListener("mouseup", function (event) {
var x = 2 * event.clientX / canvas.width - 1;
var y = 2 * (canvas.height - event.clientY) / canvas.height - 1;
stopMotion(x, y);
});
canvas.addEventListener("mousemove", function (event) {
var x = 2 * event.clientX / canvas.width - 1;
var y = 2 * (canvas.height - event.clientY) / canvas.height - 1;
mouseMotion(x, y);
});
document.addEventListener("keydown",
function (event) {
if (event.keyCode == 65 || event.keyCode == 37) { // A or Left
figureTheta[torsoId] -= 5;
initFigureNodes(torsoId);
}
if (event.keyCode == 68 || event.keyCode == 39) { // D or Right
figureTheta[torsoId] += 5;
initFigureNodes(torsoId);
}
if (event.keyCode == 87 || event.keyCode == 38) { // W or Up
walkArmMove();
walkLegMove();
}
if (event.keyCode == 83 || event.keyCode == 40) { // S or Down
runArmMove();
runLegMove();
}
if (event.keyCode == 33) { // Page Up
if (eyeRadius < 200) {
++eyeRadius;
}
}
if (event.keyCode == 34) { // Page Down
if (eyeRadius > 20) {
--eyeRadius;
}
}
}, false);
document.addEventListener("wheel",
function (event) {
var delta = Math.sign(event.deltaY);
if (eyeRadius < 200 && delta > 0) {
eyeRadius += 2;
}
else if (eyeRadius > 20 && delta < 0) {
eyeRadius -= 2;
}
});
Then below your init() function add the following functions. function mouseMotion(x, y) {
if (trackingMouse) {
xAngle += (x - curx);
curx = x;
yAngle += (cury - y);
cury = y;
}
}
function startMotion(x, y) {
trackingMouse = true;
curx = x;
cury = y;
}
function stopMotion(x, y) {
trackingMouse = false;
}
function runArmMove() {
if (armStack.length == 0) {
armBackForth(rightLowerArmId, 15, 5, 12, 2);
armBackForth(leftLowerArmId, 15, -5, 12, 2);
armBackForth(rightUpperArmId, 15, 5, 12, 2);
armBackForth(leftUpperArmId, 15, -5, 12, 2);
}
}
function runLegMove() {
if (legStack.length == 0) {
legBackForthV1(rightLowerLegId, 15, 4, 12, 2);
legBackForthV1(leftLowerLegId, 15, 4, 12, 2);
legBackForthV2(rightUpperLegId, 15, -6, -4, 12, 2);
legBackForthV2(leftUpperLegId, 15, 4, 6, 12, 2);
}
}
function walkArmMove() {
if (armStack.length == 0) {
armBackForth(rightLowerArmId, 8, 2, 30, 2);
armBackForth(leftLowerArmId, 8, -2, 30, 2);
armBackForth(rightUpperArmId, 8, 2, 30, 2);
armBackForth(leftUpperArmId, 8, -2, 30, 2);
}
}
function walkLegMove() {
if (legStack.length == 0) {
legBackForthV1(rightLowerLegId, 8, 1, 30, 2);
legBackForthV1(leftLowerLegId, 8, 1, 30, 2);
legBackForthV2(rightUpperLegId, 8, -3, -2, 30, 2);
legBackForthV2(leftUpperLegId, 8, 2, 3, 30, 2);
}
}
function armBackForth(id, reps, angoff, delay, parts) {
armStack.push(true);
armForth(id, reps, reps, angoff, delay, parts);
}
function armForth(id, start, curr, off, delay, parts) {
figureTheta[id] += off;
initFigureNodes(id);
--curr;
setTimeout(function () {
if (curr > 0) {
armForth(id, start, curr, off, delay, parts);
}
if (curr == 0) {
armBack(id, start, start, off, delay, parts);
}
}, delay);
}
function armBack(id, start, curr, off, delay, parts) {
figureTheta[id] -= off;
initFigureNodes(id);
--curr;
if (curr > 0) {
setTimeout(function () {
armBack(id, start, curr, off, delay, parts);
}, delay);
}
else {
--parts;
if (parts > 0) {
armForth(id, start, start, -off, delay, parts);
}
else {
armStack.pop();
}
}
}
function legBackForthV1(id, reps, angoff, delay, parts) {
legStack.push(true);
legForthV1(id, reps, reps, angoff, delay, parts);
}
function legForthV1(id, start, curr, off, delay, parts) {
figureTheta[id] += off;
initFigureNodes(id);
--curr;
setTimeout(function () {
if (curr > 0) {
legForthV1(id, start, curr, off, delay, parts);
}
if (curr == 0) {
legBackV1(id, start, start, off, delay, parts);
}
}, delay);
}
function legBackV1(id, start, curr, off, delay, parts) {
figureTheta[id] -= off;
initFigureNodes(id);
--curr;
if (curr > 0) {
setTimeout(function () {
legBackV1(id, start, curr, off, delay, parts);
}, delay);
}
else {
--parts;
if (parts > 0) {
legForthV1(id, start, start, off, delay, parts);
}
else {
legStack.pop();
}
}
}
function legBackForthV2(id, reps, angoff1, angoff2, delay, parts) {
legStack.push(true);
legForthV2(id, reps, reps, angoff1, angoff2, delay, parts);
}
function legForthV2(id, start, curr, off, off2, delay, parts) {
figureTheta[id] += off;
initFigureNodes(id);
--curr;
setTimeout(function () {
if (curr > 0) {
legForthV2(id, start, curr, off, off2, delay, parts);
}
if (curr == 0) {
legBackV2(id, start, start, off, off2, delay, parts);
}
}, delay);
}
function legBackV2(id, start, curr, off, off2, delay, parts) {
figureTheta[id] -= off;
initFigureNodes(id);
--curr;
if (curr > 0) {
setTimeout(function () {
legBackV2(id, start, curr, off, off2, delay, parts);
}, delay);
}
else {
--parts;
if (parts > 0) {
legForthV2(id, start, start, -off2, -off, delay, parts);
}
else {
legStack.pop();
}
}
}
Now you should be able to move your camera eye all around the scene and see the robot from any angle. Next you will add a SkyBox to your scene to show a horizon in any direction that you look, with sun and clouds above the platform and clouds below it as well. To do this, you need to add the following declaration to your Vertex Shader: varying vec3 fTexCubeCoord;
Then you need add the following line at the end of your Vertex Shader: fTexCubeCoord = normalize(vPosition.xyz);
Next, in your Fragment shader you need to add the following three declarations: varying vec3 fTexCubeCoord;
uniform samplerCube texMap;
uniform bool showSky;
Then you need to replace the following line: gl_FragColor = fColor;
With the following five lines: if (showSky) {
gl_FragColor = textureCube(texMap, fTexCubeCoord);
} else {
gl_FragColor = fColor;
}
And below the canvas tags in your HTML, you need to add the following code: <img id="cubefront" src="Images/skybox_front.png" hidden />
<img id="cubeback" src="Images/skybox_back.png" hidden />
<img id="cubetop" src="Images/skybox_top.png" hidden />
<img id="cubebottom" src="Images/skybox_bottom.png" hidden />
<img id="cubeleft" src="Images/skybox_left.png" hidden />
<img id="cuberight" src="Images/skybox_right.png" hidden />
<img id="texture1" src="Images/stripedtexture.png" hidden />
<img id="texture2" src="Images/wallpapertexture.png" hidden />
<img id="texture3" src="Images/fabrictexture.png" hidden />
NOTE: If you are experiencing the CORS error so that your images don't load, then add the following <script> tags below your other <script> tags in your HTML file: <script type="text/javascript"
src="http://ksuweb.kennesaw.edu/~ashaw8/cs4722/assignments/skybox_front.js">
</script>
<script type="text/javascript"
src="http://ksuweb.kennesaw.edu/~ashaw8/cs4722/assignments/skybox_back.js">
</script>
<script type="text/javascript"
src="http://ksuweb.kennesaw.edu/~ashaw8/cs4722/assignments/skybox_top.js">
</script>
<script type="text/javascript"
src="http://ksuweb.kennesaw.edu/~ashaw8/cs4722/assignments/skybox_bottom.js">
</script>
<script type="text/javascript"
src="http://ksuweb.kennesaw.edu/~ashaw8/cs4722/assignments/skybox_left.js">
</script>
<script type="text/javascript"
src="http://ksuweb.kennesaw.edu/~ashaw8/cs4722/assignments/skybox_right.js">
</script>
<script type="text/javascript"
src="http://ksuweb.kennesaw.edu/~ashaw8/cs4722/assignments/stripedtexture.js">
</script>
<script type="text/javascript"
src="http://ksuweb.kennesaw.edu/~ashaw8/cs4722/assignments/wallpapertexture.js">
</script>
<script type="text/javascript"
src="http://ksuweb.kennesaw.edu/~ashaw8/cs4722/assignments/fabrictexture.js">
</script>
Next, whether or not you have the CORS error, go into your JavaScript code and add the following declarations: var cubeMap;
var cubeFrontImage;
var cubeBackImage;
var cubeTopImage;
var cubeBottomImage;
var cubeLeftImage;
var cubeRightImage;
var showSkyLoc;
var skyboxScale = 600;
Next, only if you DO NOT HAVE the CORS error, go into your init() function and add the following lines above the "render();" call: cubeFrontImage = document.getElementById("cubefront");
cubeBackImage = document.getElementById("cubeback");
cubeTopImage = document.getElementById("cubetop");
cubeBottomImage = document.getElementById("cubebottom");
cubeLeftImage = document.getElementById("cubeleft");
cubeRightImage = document.getElementById("cuberight");
If you DO HAVE the CORS error, don't add the above lines, instead in your init() function above the "render();" call add the following lines: cubeFrontImage = new Image();
cubeFrontImage.src = fileskybox_frontdata;
cubeBackImage = new Image();
cubeBackImage.src = fileskybox_backdata;
cubeTopImage = new Image();
cubeTopImage.src = fileskybox_topdata;
cubeBottomImage = new Image();
cubeBottomImage.src = fileskybox_bottomdata;
cubeRightImage = new Image();
cubeRightImage.src = fileskybox_rightdata;
cubeLeftImage = new Image();
cubeLeftImage.src = fileskybox_leftdata;
// Reloads the window after 1 second to give the
// script-tag loaded images time to finish loading
setTimeout(function () {
if (window.location.hash != '#r') {
window.location.hash = 'r';
window.location.reload();
}
}, 1000);
Next, whether or not you have the CORS error, below the code you just added (which means still above the "render();" call), add the following code: configureCubeMap(cubeFrontImage, cubeBackImage, cubeTopImage,
cubeBottomImage, cubeRightImage, cubeLeftImage);
gl.activeTexture(gl.TEXTURE0);
gl.uniform1i(gl.getUniformLocation(program, "texMap"), 0);
showSkyLoc = gl.getUniformLocation(program, "showSky");
The next set of instructions are for all students, whether you have the CORS error or not. Add the following function below your init() function: function configureCubeMap(frontImg, backImg, topImg, bottomImg, rightImg, leftImg) {
cubeMap = gl.createTexture();
gl.bindTexture(gl.TEXTURE_CUBE_MAP, cubeMap);
gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X, 0, gl.RGBA,
gl.RGBA, gl.UNSIGNED_BYTE, rightImg);
gl.texImage2D(gl.TEXTURE_CUBE_MAP_NEGATIVE_X, 0, gl.RGBA,
gl.RGBA, gl.UNSIGNED_BYTE, leftImg);
gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_Y, 0, gl.RGBA,
gl.RGBA, gl.UNSIGNED_BYTE, topImg);
gl.texImage2D(gl.TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, gl.RGBA,
gl.RGBA, gl.UNSIGNED_BYTE, bottomImg);
gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_Z, 0, gl.RGBA,
gl.RGBA, gl.UNSIGNED_BYTE, frontImg);
gl.texImage2D(gl.TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, gl.RGBA,
gl.RGBA, gl.UNSIGNED_BYTE, backImg);
gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
}
Then, in your render() function, ABOVE the "traverseScene(groundId);" line, add the following code: // This draws the SkyBox
mvStack.push(modelViewMatrix);
gl.uniform1i(showSkyLoc, true);
modelViewMatrix = mult(modelViewMatrix, scalem(skyboxScale, skyboxScale, skyboxScale));
gl.uniformMatrix4fv(modelViewMatrixLoc, false, flatten(modelViewMatrix));
gl.drawArrays(gl.TRIANGLES, 0, cubeVerts);
gl.uniform1i(showSkyLoc, false);
modelViewMatrix = mvStack.pop();
Now when you run your program you should see a SkyBox around your scene (you may have to refresh the web page to see it). Use the Page Up buttom to scroll back away from the robot, and then press on your mouse button while you drag the mouse around to view to see the SkyBox on the horizon and above and below the scene. The next change to make is to get your robot walking and running around the scene. To do that, first add the following to the declarations at the top of your program: var xMax = 48;
var xMin = -48;
var zMax = 48;
var zMin = -48;
var walkDistance = 0.25;
var runDistance = 1.0;
Next, go into your init() function and find the following "walkArmMove();" and "walkLegMove();" lines that are inside your "keydown" event handler: walkArmMove();
walkLegMove();
Replace those two lines with the following line: walkForward();
Then, a little below that line, find the following two lines: runArmMove();
runLegMove();
Replace those two lines with the following line: runForward();
Next, go down below your init() function and find where your runArmMove() function is defined. Then above your runArmMove() function add the following two functions: function walkForward() {
offsetFigurePos(walkDistance);
walkArmMove();
walkLegMove();
}
function runForward() {
offsetFigurePos(runDistance);
runArmMove();
runLegMove();
}
Next, add the following functions right above the walkForward() and the runForward() functions you just added. These functions move the robot while also testing for collisions with the columns or for the robot going off of the edge. If there is a collision or the robot goes off of the edge, it is moved back away from the problem location: function offsetFigurePos(unit) {
var initX = figurePos[0];
var initZ = figurePos[2];
fTheta = Math.PI * figureTheta[torsoId] / 180;
figurePos[0] += Math.sin(fTheta) * unit;
figurePos[2] += Math.cos(fTheta) * unit;
for (var i = 1; i <= 4; ++i) {
if (!collision()) {
break;
} else if (i == 4) {
figurePos[0] = initX;
figurePos[2] = initZ;
} else { // attempts made at different distances
figurePos[0] = initX + Math.sin(fTheta) * unit * (4 - i) / 4;
figurePos[2] = initZ + Math.cos(fTheta) * unit * (4 - i) / 4;
}
}
checkEdge();
initFigureNodes(torsoId);
}
function collision() {
for (var i = 0; i < colPosArray.length; ++i) {
if (colPosArray[i] != null) {
if (circleRectCollide(colPosArray[i][0],
colPosArray[i][2], columnDiameter / 2,
figurePos[0] - torsoWidth,
figurePos[2] - torsoWidth,
figurePos[0] + torsoWidth,
figurePos[2] + torsoWidth)) {
return true;
}
}
}
return false;
}
function circleRectCollide(circX, circY, circRad,
rectLeft, rectTop, rectRight, rectBottom) {
// Find the closest point to the circle within the rectangle
var closestX = Math.min(rectRight, Math.max(rectLeft, circX));
var closestY = Math.min(rectBottom, Math.max(rectTop, circY));
// Find the distance between circle's center and closest point
var distanceX = circX - closestX;
var distanceY = circY - closestY;
// If less than the circle's radius, an collision has occured
var distanceSquared = (distanceX * distanceX) +
(distanceY * distanceY);
return distanceSquared < (circRad * circRad);
}
function checkEdge() {
if (figurePos[0] < xMin) {
figurePos[0] = xMin;
}
else if (figurePos[0] > xMax) {
figurePos[0] = xMax;
}
if (figurePos[2] < zMin) {
figurePos[2] = zMin;
}
else if (figurePos[2] > zMax) {
figurePos[2] = zMax;
}
}
Next, go down and find your initFigureNodes() function and then find the following line near the top of that function: m = rotate(figureTheta[torsoId], 0, 1, 0);
Replace that line with the following two lines: m = translate(figurePos);
m = mult(m, rotate(figureTheta[torsoId], 0, 1, 0));
Now when you run your program you should be able to move your robot all around the scene using the arrow keys. The next change to make is to add the spheres to the top of the columns. Normally this would not be a problem, since they can be added the same way the columns are added. But because they have different colors or textures we have to store a different color and texture information with each sphere's node in the tree graph for the scene. Right now, our nodes don't include color or texture information, so to make this work we will first need to add that to the node structure. To do this, find the createNode() function, and find the header line that right now is: function createNode(transform, render, sibling, child) {
And change this to the following: function createNode(transform, render, sibling, child, color1, color2, texIndex1, texIndex2) {
Then find the "child: child," line in the function, and BELOW that line add the following lines: color1: color1,
color2: color2,
texIndex1: texIndex1,
texIndex2: texIndex2
Next, find the following declaration line, which determines how far down the ground surface starts: var groundFloor = -4.9;
Change this value to -4.6, as shown below: var groundFloor = -4.6;
Next, find the following declaration line, which determines how many objects are in the scene: var numSceneNodes = 11;
Replace this line with the following lines which declare 10 more node objects and their sphere Ids, along with an array to determine where the spheres will be located. Notice that the position locations in the vec3() calls of the spherePosArray have the same X and Z locations as the column position locations in the colPosArray. But the Y value is at 13.3, which is the location at the top of the columns. And again, notice that the "numSceneNodes" value is going from 11 to 21: var sphereId1 = 11;
var sphereId2 = 12;
var sphereId3 = 13;
var sphereId4 = 14;
var sphereId5 = 15;
var sphereId6 = 16;
var sphereId7 = 17;
var sphereId8 = 18;
var sphereId9 = 19;
var sphereId10 = 20;
// the null values are dummy values
var spherePosArray = [null, null, null, null, null, null,
null, null, null, null, null,
vec3(5, 13.3, 5), vec3(40, 13.3, 40),
vec3(30, 13.3, 30), vec3(20, 13.3, 40),
vec3(10, 13.3, 40), vec3(0, 13.3, 40),
vec3(-10, 13.3, 40), vec3(-20, 13.3, 40),
vec3(-30, 13.3, 40), vec3(-40, 13.3, 40)];
var sphereDiameter = 1;
var sphereHeight = 1.25;
var sphereRot = 0;
var numSceneNodes = 21;
var grayish = vec4(0.8, 0.6, 0.6, 1.0);
Next, you have to add the handlers for each sphere you will create in the initSceneNodes() function. However, now that you have added new fields for the Node you also need to pass appropriate values to these fields in the figure and scene handlers. To do this, almost every line of the initFigureNodes() function and the initSceneNodes() function must change, so it makes sense to entirely replace those functions with the following new versions of the functions: function initFigureNodes(Id) {
var m;
switch (Id) {
case torsoId:
m = translate(figurePos);
m = mult(m, rotate(figureTheta[torsoId], 0, 1, 0));
figure[torsoId] = createNode(m, torso, null, headId, red, null, null, null);
break;
case headId:
case head1Id:
case head2Id:
m = translate(0.0, torsoHeight + 0.5 * headHeight, 0.0);
m = mult(m, rotate(figureTheta[head1Id], 1, 0, 0))
m = mult(m, rotate(figureTheta[head2Id], 0, 1, 0));
m = mult(m, translate(0.0, -0.5 * headHeight, 0.0));
figure[headId] = createNode(m, head, leftUpperArmId, eye1Id, yellow,
null, null, null);
break;
case eye1Id:
m = translate(0.0, torsoHeight + 0.5 * headHeight, 0.0);
m = mult(m, rotate(figureTheta[head1Id], 1, 0, 0))
m = mult(m, rotate(figureTheta[head2Id], 0, 1, 0));
m = mult(m, translate(0.0, -0.5 * headHeight, 0.0));
figure[eye1Id] = createNode(m, lefteye, eye2Id, null, black, null, null, null);
break;
case eye2Id:
m = translate(0.0, torsoHeight + 0.5 * headHeight, 0.0);
m = mult(m, rotate(figureTheta[head1Id], 1, 0, 0))
m = mult(m, rotate(figureTheta[head2Id], 0, 1, 0));
m = mult(m, translate(0.0, -0.5 * headHeight, 0.0));
figure[eye2Id] = createNode(m, righteye, mouthId, null, black, null, null, null);
break;
case mouthId:
m = translate(0.0, torsoHeight + 0.5 * headHeight, 0.0);
m = mult(m, rotate(figureTheta[head1Id], 1, 0, 0))
m = mult(m, rotate(figureTheta[head2Id], 0, 1, 0));
m = mult(m, translate(0.0, -0.5 * headHeight, 0.0));
figure[mouthId] = createNode(m, mouth, null, null, black, null, null, null);
break;
case leftUpperArmId:
m = translate(-(torsoWidth * 0.75), 0.9 * torsoHeight, 0.0);
m = mult(m, rotate(figureTheta[leftUpperArmId], 1, 0, 0));
figure[leftUpperArmId] = createNode(m, leftUpperArm, rightUpperArmId, leftLowerArmId,
lightgreen, null, null, null);
break;
case rightUpperArmId:
m = translate(torsoWidth * 0.75, 0.9 * torsoHeight, 0.0);
m = mult(m, rotate(figureTheta[rightUpperArmId], 1, 0, 0));
figure[rightUpperArmId] = createNode(m, rightUpperArm, leftUpperLegId, rightLowerArmId,
lightgreen, null, null, null);
break;
case leftUpperLegId:
m = translate(-(torsoWidth * 0.75), 0.1 * upperLegHeight, 0.0);
m = mult(m, rotate(figureTheta[leftUpperLegId], 1, 0, 0));
figure[leftUpperLegId] = createNode(m, leftUpperLeg, rightUpperLegId, leftLowerLegId,
lightblue, null, null, null);
break;
case rightUpperLegId:
m = translate(torsoWidth * 0.75, 0.1 * upperLegHeight, 0.0);
m = mult(m, rotate(figureTheta[rightUpperLegId], 1, 0, 0));
figure[rightUpperLegId] = createNode(m, rightUpperLeg, null, rightLowerLegId,
lightblue, null, null, null);
break;
case leftLowerArmId:
m = translate(0.0, upperArmHeight, 0.0);
m = mult(m, rotate(figureTheta[leftLowerArmId], 1, 0, 0));
figure[leftLowerArmId] = createNode(m, leftLowerArm, null, null,
darkgreen, null, null, null);
break;
case rightLowerArmId:
m = translate(0.0, upperArmHeight, 0.0);
m = mult(m, rotate(figureTheta[rightLowerArmId], 1, 0, 0));
figure[rightLowerArmId] = createNode(m, rightLowerArm, null, null,
darkgreen, null, null, null);
break;
case leftLowerLegId:
m = translate(0.0, upperLegHeight, 0.0);
m = mult(m, rotate(figureTheta[leftLowerLegId], 1, 0, 0));
figure[leftLowerLegId] = createNode(m, leftLowerLeg, null, null,
darkblue, null, null, null);
break;
case rightLowerLegId:
m = translate(0.0, upperLegHeight, 0.0);
m = mult(m, rotate(figureTheta[rightLowerLegId], 1, 0, 0));
figure[rightLowerLegId] = createNode(m, rightLowerLeg, null, null,
darkblue, null, null, null);
break;
}
}
function initSceneNodes(Id) {
var m;
switch (Id) {
case groundId:
m = mat4();
scene[groundId] = createNode(m, ground, columnId1, null, darkcyan, null, null, null);
break;
case columnId1:
m = translate(colPosArray[columnId1]);
scene[columnId1] = createNode(m, column, columnId2, null, brown, grayish, null, null);
break;
case columnId2:
m = translate(colPosArray[columnId2]);
scene[columnId2] = createNode(m, column, columnId3, null, brown, grayish, null, null);
break;
case columnId3:
m = translate(colPosArray[columnId3]);
scene[columnId3] = createNode(m, column, columnId4, null, brown, grayish, null, null);
break;
case columnId4:
m = translate(colPosArray[columnId4]);
scene[columnId4] = createNode(m, column, columnId5, null, brown, grayish, null, null);
break;
case columnId5:
m = translate(colPosArray[columnId5]);
scene[columnId5] = createNode(m, column, columnId6, null, brown, grayish, null, null);
break;
case columnId6:
m = translate(colPosArray[columnId6]);
scene[columnId6] = createNode(m, column, columnId7, null, brown, grayish, null, null);
break;
case columnId7:
m = translate(colPosArray[columnId7]);
scene[columnId7] = createNode(m, column, columnId8, null, brown, grayish, null, null);
break;
case columnId8:
m = translate(colPosArray[columnId8]);
scene[columnId8] = createNode(m, column, columnId9, null, brown, grayish, null, null);
break;
case columnId9:
m = translate(colPosArray[columnId9]);
scene[columnId9] = createNode(m, column, columnId10, null, brown, grayish, null, null);
break;
case columnId10:
m = translate(colPosArray[columnId10]);
scene[columnId10] = createNode(m, column, sphereId1, null, brown, grayish, null, null);
break;
case sphereId1:
m = translate(spherePosArray[sphereId1]);
scene[sphereId1] = createNode(m, sphere, sphereId2, null, red, null, 0, null);
break;
case sphereId2:
m = translate(spherePosArray[sphereId2]);
scene[sphereId2] = createNode(m, sphere, sphereId3, null, yellow, null, 0, null);
break;
case sphereId3:
m = translate(spherePosArray[sphereId3]);
scene[sphereId3] = createNode(m, sphere, sphereId4, null, lightgreen, null, null, null);
break;
case sphereId4:
m = translate(spherePosArray[sphereId4]);
scene[sphereId4] = createNode(m, sphere, sphereId5, null, lightblue, null, 0, null);
break;
case sphereId5:
m = translate(spherePosArray[sphereId5]);
scene[sphereId5] = createNode(m, sphere, sphereId6, null, darkgreen, null, null, null);
break;
case sphereId6:
m = translate(spherePosArray[sphereId6]);
scene[sphereId6] = createNode(m, sphere, sphereId7, null, darkblue, null, 0, null);
break;
case sphereId7:
m = translate(spherePosArray[sphereId7]);
scene[sphereId7] = createNode(m, sphere, sphereId8, null, lightcyan, null, null, null);
break;
case sphereId8:
m = translate(spherePosArray[sphereId8]);
scene[sphereId8] = createNode(m, sphere, sphereId9, null, darkcyan, null, 0, null);
break;
case sphereId9:
m = translate(spherePosArray[sphereId9]);
scene[sphereId9] = createNode(m, sphere, sphereId10, null, white, null, null, null);
break;
case sphereId10:
m = translate(spherePosArray[sphereId10]);
scene[sphereId10] = createNode(m, sphere, null, null, black, null, 0, null);
break;
}
}
Next, find the traverseFigure() function, and in that function find the following line: figure[Id].render();
This is the line that calls whatever function is stored in the node that will draw the node. This will be the "sphere()" function for our sphere nodes, and we need to send the color to that function. So replace this line with the following line: figure[Id].render(figure[Id].color1, figure[Id].color2,
figure[Id].texIndex1, scene[Id].texIndex2);
Next, find the traverseScene() function, and in that function find the following line: scene[Id].render();
This is the line that calls whatever function is stored in the node that will draw the node. This will be the "sphere()" function for our sphere nodes, and we need to send the color to that function. So replace this line with the following line: scene[Id].render(scene[Id].color1, scene[Id].color2,
scene[Id].texIndex1, scene[Id].texIndex2);
And then below this function find the column() function, and ABOVE that function above add the following function: function sphere(color1, color2, texIndex1, texIndex2) {
instanceMatrix = mult(modelViewMatrix, translate(0.0, sphereHeight, 0.0));
instanceMatrix = mult(instanceMatrix,
scalem(sphereDiameter, sphereHeight, sphereDiameter));
gl.uniformMatrix4fv(modelViewMatrixLoc, false, flatten(instanceMatrix));
gl.uniform4fv(colorLoc, color1);
gl.drawArrays(gl.TRIANGLES, cubeVerts, sphereVerts);
}
Now, when you run your program you should see different color spheres on all of the columns. The number of columns and spheres and their colors are determined in the initSceneNodes() function. Your next change will be to add lighting and shadows to your scene. This will require adding Phong lighting and a Shadow Map, and to create a render() function that renders the scene twice. To begin all of these changes, you will need to completely replace your Vertex Shader and your Fragment Shader in your HTML code with the following two new Vertex Shaders and Fragment Shaders:
<script id="vertex-shader1" type="x-shader/x-vertex">
attribute vec4 vPosition;
uniform mat4 pmvMatrixFromLight;
void main()
{
gl_Position = pmvMatrixFromLight * vPosition;
}
</script>
<script id="fragment-shader1" type="x-shader/x-fragment">
precision mediump float;
void main() {
// Write the z-value in R
gl_FragColor = vec4(gl_FragCoord.z, 0.0, 0.0, 1.0);
}
</script>
<script id="vertex-shader2" type="x-shader/x-vertex">
attribute vec4 vPosition;
attribute vec3 vNormal;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
uniform vec4 ambientProduct, diffuseProduct, specularProduct;
uniform vec4 lightPosition;
uniform float shininess;
uniform mat4 pmvMatrixFromLight;
varying vec4 fPositionFromLight;
varying vec3 fTexCubeCoord;
varying vec4 fColor2;
varying float normShadowVal;
void main()
{
vec3 pos = -(modelViewMatrix * vPosition).xyz;
//fixed light postion
vec3 light = lightPosition.xyz;
vec3 L = normalize( light - pos );
vec3 E = normalize( -pos );
vec3 H = normalize( L + E );
vec4 NN = vec4(vNormal,0);
// Transform vertex normal into eye coordinates
vec3 N = normalize( (modelViewMatrix*NN).xyz);
// Compute terms in the illumination equation
vec4 ambient = ambientProduct;
float Kd = max( dot(L, N), 0.0 );
vec4 diffuse = Kd*diffuseProduct;
float Ks = pow( max(dot(N, H), 0.0), shininess );
vec4 specular = Ks * specularProduct;
if( dot(L, N) < 0.0 ) {
specular = vec4(0.0, 0.0, 0.0, 1.0);
}
fColor2 = ambient + diffuse + specular;
fColor2.a = 1.0;
gl_Position = projectionMatrix * modelViewMatrix * vPosition;
fTexCubeCoord = normalize(vPosition.xyz);
fPositionFromLight = pmvMatrixFromLight * vPosition;
normShadowVal = specular.a;
}
</script>
<script id="fragment-shader2" type="x-shader/x-fragment">
precision mediump float;
uniform vec4 fColor;
uniform samplerCube texMap;
uniform bool showSky;
uniform sampler2D shadowTexture;
uniform int visibleState;
varying vec3 fTexCubeCoord;
varying vec4 fColor2;
varying vec4 fPositionFromLight;
varying float normShadowVal;
void main()
{
if (showSky) {
gl_FragColor = textureCube(texMap, fTexCubeCoord);
} else {
vec3 shadowCoord = (fPositionFromLight.xyz/fPositionFromLight.w)/2.0 + 0.5;
vec4 rgbaDepth = texture2D(shadowTexture, shadowCoord.xy);
float depth = rgbaDepth.r; // Retrieve the z-value from R
float visibility = (shadowCoord.z > depth + 0.005 && visibleState != 0 &&
(visibleState == 2 || normShadowVal < 1.0)) ? 0.7 : 1.0;
gl_FragColor = vec4(fColor2.rgb * fColor.rgb * visibility, 1.0);
}
}
</script>
Next, in your JavaScript code, add the following initializations: var vPosition;
var lightAmbient = vec4(0.4, 0.4, 0.4, 1.0);
var lightDiffuse = vec4(1.0, 1.0, 1.0, 1.0);
var lightSpecular = vec4(1.0, 1.0, 1.0, 1.0);
var materialAmbient = vec4(1.0, 1.0, 1.0, 1.0);
var materialDiffuse = vec4(1.0, 1.0, 1.0, 1.0);
var materialSpecular = vec4(1.0, 1.0, 1.0, 1.0);
var materialShininess = 100.0;
var shinniness;
var ambientColor, diffuseColor, specularColor;
var ambientProduct, diffuseProduct, specularProduct;
var ambientProductLoc, diffuseProductLoc, specularProductLoc;
var shininessLoc;
var lightPosition = vec4(-250.0, 250.0, 250.0, 0.0);
var lightPosition2;
var lightPositionLoc;
var yAngleDeg;
var xAngleDeg;
var nBuffer;
var vNormal;
var normalsArray = [];
var shadowProgram;
var SHADOWTEXTUREWIDTH = 2048, SHADOWTEXTUREHEIGHT = 2048;
var framebuffer, depthBuffer;
var shadowVBuffer;
var shadowVPosition;
var shadowVPositionLoc;
var projectionMatrixFromLight;
var pmvMatrixFromLightLoc1, pmvMatrixFromLightLoc2;
var modelViewMatrix2;
var mvStack2 = [];
var instanceMatrix2;
var shadowTexture;
var shadowTextureLoc;
var near = -10.0;
var far = 150.0;
var left = -75.0;
var right = 75.0;
var ytop = 50.0;
var bottom = -50.0;
var lightRad = 40;
var lightEye = vec3(lightRad, lightRad, lightRad);
var lightAt = vec3(0.0, 0.0, 0.0);
var lightUp = vec3(0.0, 1.0, 0.0);
var visibleState = 0;
var visibleStateLoc;
var inShadow = false;
Next, in your init() function find the following block of code that you will need to remove: program = initShaders(gl, "vertex-shader", "fragment-shader");
gl.useProgram(program);
modelViewMatrixLoc = gl.getUniformLocation(program, "modelViewMatrix");
projectionMatrixLoc = gl.getUniformLocation(program, "projectionMatrix");
instanceMatrix = mat4();
modelViewMatrix = mat4();
projectionMatrix = perspective(fovy, aspect, pnear, pfar);
gl.uniformMatrix4fv(projectionMatrixLoc, false, flatten(projectionMatrix));
After you have removed the above block of code, below it you should see the following lines of code: makeCube();
cubeVerts = pointsArray.length;
makeSphere();
sphereVerts = pointsArray.length - cubeVerts;
makeCylinder(true);
cylinderVerts = pointsArray.length - cubeVerts - sphereVerts;
Leave these lines of code there, and below them add the following block of code: setFBOs();
shadowProgram = initShaders(gl, "vertex-shader1", "fragment-shader1");
program = initShaders(gl, "vertex-shader2", "fragment-shader2");
gl.useProgram(shadowProgram);
shadowVBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, shadowVBuffer);
gl.bufferData(gl.ARRAY_BUFFER, flatten(pointsArray), gl.STATIC_DRAW);
shadowVPosition = gl.getAttribLocation(shadowProgram, "vPosition");
gl.vertexAttribPointer(shadowVPosition, 4, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(shadowVPosition);
pmvMatrixFromLightLoc1 = gl.getUniformLocation(shadowProgram, "pmvMatrixFromLight");
projectionMatrixFromLight = ortho(left, right, bottom, ytop, near, far);
gl.useProgram(program);
pmvMatrixFromLightLoc2 = gl.getUniformLocation(program, "pmvMatrixFromLight");
shadowTextureLoc = gl.getUniformLocation(program, "shadowTexture");
modelViewMatrixLoc = gl.getUniformLocation(program, "modelViewMatrix");
projectionMatrixLoc = gl.getUniformLocation(program, "projectionMatrix");
ambientProduct = mult(lightAmbient, materialAmbient);
diffuseProduct = mult(lightDiffuse, materialDiffuse);
specularProduct = mult(lightSpecular, materialSpecular);
ambientProductLoc = gl.getUniformLocation(program, "ambientProduct");
diffuseProductLoc = gl.getUniformLocation(program, "diffuseProduct");
specularProductLoc = gl.getUniformLocation(program, "specularProduct");
shininessLoc = gl.getUniformLocation(program, "shininess");
gl.uniform4fv(gl.getUniformLocation(program, "ambientProduct"),
flatten(ambientProduct));
gl.uniform4fv(gl.getUniformLocation(program, "diffuseProduct"),
flatten(diffuseProduct));
gl.uniform4fv(gl.getUniformLocation(program, "specularProduct"),
flatten(specularProduct));
gl.uniform1f(gl.getUniformLocation(program,
"shininess"), materialShininess);
lightPositionLoc = gl.getUniformLocation(program, "lightPosition");
gl.uniform4fv(lightPositionLoc, lightPosition);
projectionMatrix = perspective(fovy, aspect, pnear, pfar);
gl.uniformMatrix4fv(projectionMatrixLoc, false, flatten(projectionMatrix));
visibleState = 1;
visibleStateLoc = gl.getUniformLocation(program, "visibleState");
gl.uniform1i(visibleStateLoc, visibleState);
nBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, nBuffer);
gl.bufferData(gl.ARRAY_BUFFER, flatten(normalsArray), gl.STATIC_DRAW);
vNormal = gl.getAttribLocation(program, "vNormal");
gl.vertexAttribPointer(vNormal, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(vNormal);
Next, find the following line in your init() function: var vPosition = gl.getAttribLocation(program, "vPosition");
The "vPosition" variable needs to be a global variable, so remove the "var" so that the line becomes: vPosition = gl.getAttribLocation(program, "vPosition");
Next, add the following function below the init() function: // Sets up the Frame Buffer Objects
function setFBOs() {
shadowTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, shadowTexture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, SHADOWTEXTUREWIDTH, SHADOWTEXTUREHEIGHT, 0, gl.RGBA,
gl.UNSIGNED_BYTE, null);
gl.generateMipmap(gl.TEXTURE_2D);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.bindTexture(gl.TEXTURE_2D, null);
// Allocate a frame buffer object
framebuffer = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
// Attach color buffer
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0,
gl.TEXTURE_2D, shadowTexture, 0);
// create a depth renderbuffer
depthBuffer = gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer);
// make a depth buffer and the same size as the targetTexture
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16,
SHADOWTEXTUREWIDTH, SHADOWTEXTUREHEIGHT);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuffer);
// check for completeness
var status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
if (status != gl.FRAMEBUFFER_COMPLETE)
alert('Framebuffer Not Complete');
}
Next, go to the quad() function and add the following lines to the end of the function to compute your normals for the cube: var t1 = subtract(vertices[a], vertices[b]);
var t2 = subtract(vertices[a], vertices[c]);
var normal = vec3(cross(t1, t2));
normalsArray.push(normal);
normalsArray.push(normal);
normalsArray.push(normal);
normalsArray.push(normal);
normalsArray.push(normal);
normalsArray.push(normal);
Next, go to the makeSphere() function and add the following lines after the last "pointsArray.push(p3);" line to add your normals for the sphere: normalsArray.push(vec3(p1));
normalsArray.push(vec3(p2));
normalsArray.push(vec3(p3));
normalsArray.push(vec3(p2));
normalsArray.push(vec3(p4));
normalsArray.push(vec3(p3));
Next, go to the makeCylinder() function and add the following lines after the 2nd "pointsArray.push(p3);" line in the first set of for-loops to add the first set of normals for the cylinder: var t1 = subtract(p2, p1);
var t2 = subtract(p2, p3);
var normal = vec3(cross(t1, t2));
normalsArray.push(normal);
normalsArray.push(normal);
normalsArray.push(normal);
t1 = subtract(p4, p2);
t2 = subtract(p4, p3);
normal = vec3(cross(t1, t2));
normalsArray.push(normal);
normalsArray.push(normal);
normalsArray.push(normal);
Then in the for-loop at the end of the function, add the following lines after the 2nd "pointsArray.push(p3);" line to add the second set of normals for the cylinder: var t1 = subtract(p1, p2);
var t2 = subtract(p1, p3);
var normal = vec3(cross(t1, t2));
normalsArray.push(normal);
normalsArray.push(normal);
normalsArray.push(normal);
t1 = subtract(p1, p3);
t2 = subtract(p1, p2);
normal = vec3(cross(t1, t2));
normalsArray.push(normal);
normalsArray.push(normal);
normalsArray.push(normal);
Next, find the traverseFigure(Id) function and find the following line: modelViewMatrix = mult(modelViewMatrix, figure[Id].transform);
BELOW that line, add the following two lines: mvStack2.push(modelViewMatrix2);
modelViewMatrix2 = mult(modelViewMatrix2, figure[Id].transform);
Then find the following line which is a few lines down from there: modelViewMatrix = mvStack.pop();
BELOW that line, add the following line: modelViewMatrix2 = mvStack2.pop();
Next, find the traverseScene(Id) function and find the following line: modelViewMatrix = mult(modelViewMatrix, scene[Id].transform);
BELOW that line, add the following two lines: mvStack2.push(modelViewMatrix2);
modelViewMatrix2 = mult(modelViewMatrix2, scene[Id].transform);
Then find the following line which is a few lines down from there: modelViewMatrix = mvStack.pop();
BELOW that line, add the following line: modelViewMatrix2 = mvStack2.pop();
For the next set of changes, you will need to add lines of code that pass along Shadow Map data after it has been rendered for each of the functions that draw objects in the scene, and like a similar issue above, this affects all of the object rendering functions. So, in order to do this the easiest way will be to just remove and replace each of them. First remove the following functions: function torso ...
function head ...
function leftUpperArm ...
function leftLowerArm ...
function rightUpperArm ...
function rightLowerArm ...
function leftUpperLeg ...
function leftLowerLeg ...
function rightUpperLeg ...
function rightLowerLeg ...
function lefteye ...
function righteye ...
function mouth ...
function ground ...
function sphere ...
function column ...
Now replace the functions you just removed with the following new version of these functions with two additional helper functions:
function torso(color1, color2, texIndex1, texIndex2) {
instanceMatrix = mult(modelViewMatrix, translate(0.0, 0.5 * torsoHeight, 0.0));
instanceMatrix = mult(instanceMatrix, scalem(torsoWidth, torsoHeight, torsoWidth));
if (!inShadow) {
instanceMatrix2 = mult(modelViewMatrix2, translate(0.0, 0.5 * torsoHeight, 0.0));
instanceMatrix2 = mult(instanceMatrix2, scalem(torsoWidth, torsoHeight, torsoWidth));
}
setDrawState(color1, texIndex1);
gl.drawArrays(gl.TRIANGLES, 0, cubeVerts);
unsetDrawState(texIndex1);
}
function head(color1, color2, texIndex1, texIndex2) {
instanceMatrix = mult(modelViewMatrix, translate(0.0, 0.5 * headHeight, 0.0));
instanceMatrix = mult(instanceMatrix, scalem(headWidth, headHeight, headWidth));
if (!inShadow) {
instanceMatrix2 = mult(modelViewMatrix2, translate(0.0, 0.5 * headHeight, 0.0));
instanceMatrix2 = mult(instanceMatrix2, scalem(headWidth, headHeight, headWidth));
}
setDrawState(color1, texIndex1);
gl.drawArrays(gl.TRIANGLES, 0, cubeVerts);
unsetDrawState(texIndex1);
}
function leftUpperArm(color1, color2, texIndex1, texIndex2) {
instanceMatrix = mult(modelViewMatrix, translate(0.0, 0.5 * upperArmHeight, 0.0));
instanceMatrix = mult(instanceMatrix, scalem(upperArmWidth, upperArmHeight, upperArmWidth));
if (!inShadow) {
instanceMatrix2 = mult(modelViewMatrix2, translate(0.0, 0.5 * upperArmHeight, 0.0));
instanceMatrix2 = mult(instanceMatrix2, scalem(upperArmWidth, upperArmHeight, upperArmWidth));
}
setDrawState(color1, texIndex1);
gl.drawArrays(gl.TRIANGLES, 0, cubeVerts);
unsetDrawState(texIndex1);
}
function leftLowerArm(color1, color2, texIndex1, texIndex2) {
instanceMatrix = mult(modelViewMatrix, translate(0.0, 0.5 * lowerArmHeight, 0.0));
instanceMatrix = mult(instanceMatrix, scalem(lowerArmWidth, lowerArmHeight, lowerArmWidth));
if (!inShadow) {
instanceMatrix2 = mult(modelViewMatrix2, translate(0.0, 0.5 * lowerArmHeight, 0.0));
instanceMatrix2 = mult(instanceMatrix2, scalem(lowerArmWidth, lowerArmHeight, lowerArmWidth));
}
setDrawState(color1, texIndex1);
gl.drawArrays(gl.TRIANGLES, 0, cubeVerts);
unsetDrawState(texIndex1);
}
function rightUpperArm(color1, color2, texIndex1, texIndex2) {
instanceMatrix = mult(modelViewMatrix, translate(0.0, 0.5 * upperArmHeight, 0.0));
instanceMatrix = mult(instanceMatrix, scalem(upperArmWidth, upperArmHeight, upperArmWidth));
if (!inShadow) {
instanceMatrix2 = mult(modelViewMatrix2, translate(0.0, 0.5 * upperArmHeight, 0.0));
instanceMatrix2 = mult(instanceMatrix2, scalem(upperArmWidth, upperArmHeight, upperArmWidth));
}
setDrawState(color1, texIndex1);
gl.drawArrays(gl.TRIANGLES, 0, cubeVerts);
unsetDrawState(texIndex1);
}
function rightLowerArm(color1, color2, texIndex1, texIndex2) {
instanceMatrix = mult(modelViewMatrix, translate(0.0, 0.5 * lowerArmHeight, 0.0));
instanceMatrix = mult(instanceMatrix, scalem(lowerArmWidth, lowerArmHeight, lowerArmWidth));
if (!inShadow) {
instanceMatrix2 = mult(modelViewMatrix2, translate(0.0, 0.5 * lowerArmHeight, 0.0));
instanceMatrix2 = mult(instanceMatrix2, scalem(lowerArmWidth, lowerArmHeight, lowerArmWidth));
}
setDrawState(color1, texIndex1);
gl.drawArrays(gl.TRIANGLES, 0, cubeVerts);
unsetDrawState(texIndex1);
}
function leftUpperLeg(color1, color2, texIndex1, texIndex2) {
instanceMatrix = mult(modelViewMatrix, translate(0.0, 0.5 * upperLegHeight, 0.0));
instanceMatrix = mult(instanceMatrix, scalem(upperLegWidth, upperLegHeight, upperLegWidth));
if (!inShadow) {
instanceMatrix2 = mult(modelViewMatrix2, translate(0.0, 0.5 * upperLegHeight, 0.0));
instanceMatrix2 = mult(instanceMatrix2, scalem(upperLegWidth, upperLegHeight, upperLegWidth));
}
setDrawState(color1, texIndex1);
gl.drawArrays(gl.TRIANGLES, 0, cubeVerts);
unsetDrawState(texIndex1);
}
function leftLowerLeg(color1, color2, texIndex1, texIndex2) {
instanceMatrix = mult(modelViewMatrix, translate(0.0, 0.5 * lowerLegHeight, 0.0));
instanceMatrix = mult(instanceMatrix, scalem(lowerLegWidth, lowerLegHeight, lowerLegWidth));
if (!inShadow) {
instanceMatrix2 = mult(modelViewMatrix2, translate(0.0, 0.5 * lowerLegHeight, 0.0));
instanceMatrix2 = mult(instanceMatrix2, scalem(lowerLegWidth, lowerLegHeight, lowerLegWidth));
}
setDrawState(color1, texIndex1);
gl.drawArrays(gl.TRIANGLES, 0, cubeVerts);
unsetDrawState(texIndex1);
}
function rightUpperLeg(color1, color2, texIndex1, texIndex2) {
instanceMatrix = mult(modelViewMatrix, translate(0.0, 0.5 * upperLegHeight, 0.0));
instanceMatrix = mult(instanceMatrix, scalem(upperLegWidth, upperLegHeight, upperLegWidth));
if (!inShadow) {
instanceMatrix2 = mult(modelViewMatrix2, translate(0.0, 0.5 * upperLegHeight, 0.0));
instanceMatrix2 = mult(instanceMatrix2, scalem(upperLegWidth, upperLegHeight, upperLegWidth));
}
setDrawState(color1, texIndex1);
gl.drawArrays(gl.TRIANGLES, 0, cubeVerts);
unsetDrawState(texIndex1);
}
function rightLowerLeg(color1, color2, texIndex1, texIndex2) {
instanceMatrix = mult(modelViewMatrix, translate(0.0, 0.5 * lowerLegHeight, 0.0));
instanceMatrix = mult(instanceMatrix, scalem(lowerLegWidth, lowerLegHeight, lowerLegWidth));
if (!inShadow) {
instanceMatrix2 = mult(modelViewMatrix2, translate(0.0, 0.5 * lowerLegHeight, 0.0));
instanceMatrix2 = mult(instanceMatrix2, scalem(lowerLegWidth, lowerLegHeight, lowerLegWidth));
}
setDrawState(color1, texIndex1);
gl.drawArrays(gl.TRIANGLES, 0, cubeVerts);
unsetDrawState(texIndex1);
}
function lefteye(color1, color2, texIndex1, texIndex2) {
instanceMatrix = mult(modelViewMatrix, translate(-0.2, -4.0, 0.5));
instanceMatrix = mult(instanceMatrix, scalem(eyeWidth, eyeWidth, eyeDepth));
if (!inShadow) {
instanceMatrix2 = mult(modelViewMatrix2, translate(-0.2, -4.0, 0.5));
instanceMatrix2 = mult(instanceMatrix2, scalem(eyeWidth, eyeWidth, eyeDepth));
}
setDrawState(color1, texIndex1);
gl.drawArrays(gl.TRIANGLES, cubeVerts, sphereVerts);
unsetDrawState(texIndex1);
}
function righteye(color1, color2, texIndex1, texIndex2) {
instanceMatrix = mult(modelViewMatrix, translate(0.2, -4.0, 0.5));
instanceMatrix = mult(instanceMatrix, scalem(eyeWidth, eyeWidth, eyeDepth));
if (!inShadow) {
instanceMatrix2 = mult(modelViewMatrix2, translate(0.2, -4.0, 0.5));
instanceMatrix2 = mult(instanceMatrix2, scalem(eyeWidth, eyeWidth, eyeDepth));
}
setDrawState(color1, texIndex1);
gl.drawArrays(gl.TRIANGLES, cubeVerts, sphereVerts);
unsetDrawState(texIndex1);
}
function mouth(color1, color2, texIndex1, texIndex2) {
instanceMatrix = mult(modelViewMatrix, translate(0.0, -4.5, 0.5));
instanceMatrix = mult(instanceMatrix, scalem(mouthWidth, mouthHeight, mouthDepth));
if (!inShadow) {
instanceMatrix2 = mult(modelViewMatrix2, translate(0.0, -4.5, 0.5));
instanceMatrix2 = mult(instanceMatrix2, scalem(mouthWidth, mouthHeight, mouthDepth));
}
setDrawState(color1, texIndex1);
gl.drawArrays(gl.TRIANGLES, 0, cubeVerts);
unsetDrawState(texIndex1);
}
function ground(color1, color2, texIndex1, texIndex2) {
instanceMatrix = mult(modelViewMatrix, translate(0.0, groundFloor, 0.0));
instanceMatrix = mult(instanceMatrix, scalem(groundWidth, groundHeight, groundWidth));
if (!inShadow) {
instanceMatrix2 = mult(modelViewMatrix2, translate(0.0, groundFloor, 0.0));
instanceMatrix2 = mult(instanceMatrix2, scalem(groundWidth, groundHeight, groundWidth));
}
setDrawState(color1, texIndex1);
gl.drawArrays(gl.TRIANGLES, 0, cubeVerts);
unsetDrawState(texIndex1);
}
function sphere(color1, color2, texIndex1, texIndex2) {
instanceMatrix = mult(modelViewMatrix, translate(0.0, sphereHeight, 0.0));
instanceMatrix = mult(instanceMatrix, rotateY(sphereRot));
instanceMatrix = mult(instanceMatrix, scalem(sphereDiameter, sphereHeight, sphereDiameter));
if (!inShadow) {
instanceMatrix2 = mult(modelViewMatrix2, translate(0.0, sphereHeight, 0.0));
instanceMatrix2 = mult(instanceMatrix2, scalem(sphereDiameter, sphereHeight, sphereDiameter));
}
setDrawState(color1, texIndex1);
if (!inShadow)
gl.uniform1i(visibleStateLoc, 0);
gl.drawArrays(gl.TRIANGLES, cubeVerts, sphereVerts);
if (!inShadow)
gl.uniform1i(visibleStateLoc, visibleState);
unsetDrawState(texIndex1);
}
function column(color1, color2, texIndex1, texIndex2) {
instanceMatrix = mult(modelViewMatrix, translate(0.0, groundFloor + columnHeight / 2, 0.0));
instanceMatrix = mult(instanceMatrix, scalem(columnDiameter, columnHeight, columnDiameter));
if (!inShadow) {
instanceMatrix2 = mult(modelViewMatrix2, translate(0.0, groundFloor + columnHeight / 2, 0.0));
instanceMatrix2 = mult(instanceMatrix2, scalem(columnDiameter, columnHeight, columnDiameter));
}
setDrawState(color1, texIndex1);
gl.drawArrays(gl.TRIANGLES, cubeVerts + sphereVerts, cylinderVerts - cylinderEdgeVerts);
unsetDrawState(texIndex1);
setDrawState(color2, texIndex2);
gl.drawArrays(gl.TRIANGLES, cubeVerts + sphereVerts + cylinderVerts - cylinderEdgeVerts,
cylinderEdgeVerts);
unsetDrawState(texIndex2);
}
function setDrawState(color, texIndex) {
if (inShadow) {
gl.uniformMatrix4fv(pmvMatrixFromLightLoc1, false, flatten(instanceMatrix));
} else {
gl.uniformMatrix4fv(modelViewMatrixLoc, false, flatten(instanceMatrix));
gl.uniformMatrix4fv(pmvMatrixFromLightLoc2, false, flatten(instanceMatrix2));
gl.uniform4fv(colorLoc, color);
}
}
function unsetDrawState(texIndex) {
}
Now you have a final set of changes to make to your render() function, which now has to render the scene twice. So go to the TOP of your render() function and ADD the following code that sets up the FrameBuffer, then renders the scene to the Shadow Buffer before the scene is next rendered to the screen:
/////////////////// Part 1 ////////////////////
// This First Part goes to the Shadow Buffer //
///////////////////////////////////////////////
gl.useProgram(shadowProgram);
// send data to framebuffer for off-screen render
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
gl.disableVertexAttribArray(vPosition);
gl.disableVertexAttribArray(vNormal);
gl.enableVertexAttribArray(shadowVPosition);
gl.bindBuffer(gl.ARRAY_BUFFER, shadowVBuffer);
gl.vertexAttribPointer(shadowVPosition, 4, gl.FLOAT, false, 0, 0);
gl.viewport(0, 0, SHADOWTEXTUREWIDTH, SHADOWTEXTUREHEIGHT);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
modelViewMatrix = mult(projectionMatrixFromLight, lookAt(lightEye, lightAt, lightUp));
modelViewMatrix2 = mult(projectionMatrixFromLight, lookAt(lightEye, lightAt, lightUp));
inShadow = true;
traverseScene(groundId);
traverseFigure(torsoId);
inShadow = false;
///////////////// Part 2 /////////////////
// This Second Part goes to the Screen //
//////////////////////////////////////////
gl.useProgram(program);
// send data to GPU for normal render
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.viewport(0, 0, canvas.width, canvas.height);
gl.disableVertexAttribArray(shadowVPosition);
gl.enableVertexAttribArray(vPosition);
gl.enableVertexAttribArray(vNormal);
gl.bindBuffer(gl.ARRAY_BUFFER, vBuffer);
gl.vertexAttribPointer(vPosition, 4, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, nBuffer);
gl.vertexAttribPointer(vNormal, 3, gl.FLOAT, false, 0, 0);
gl.activeTexture(gl.TEXTURE1);
gl.bindTexture(gl.TEXTURE_2D, shadowTexture);
gl.uniform1i(shadowTextureLoc, 1);
if (eyeRadius * Math.sin(yAngle) < -4.5) {
visibleState = 0;
} else {
visibleState = 1;
}
gl.uniform1i(visibleStateLoc, visibleState);
sphereRot += 2.0;
yAngleDeg = 180.0 * yAngle / Math.PI;
xAngleDeg = 180.0 * xAngle / Math.PI;
lightPosition2 = vec4(lightPosition[0] - figurePos[0], lightPosition[1],
lightPosition[2] - figurePos[2], 0.0);
lightPosition2 = mult(rotateY(xAngleDeg), lightPosition);
lightPosition2 = mult(rotateX(yAngleDeg), lightPosition2);
gl.uniform4fv(lightPositionLoc, lightPosition2);
Now, when you run your program you should see that every object in the scene is now casting a shadow, including the robot. Next, you will put textures around some of your columns and spheres. You can use different textures for the different columns, and in these steps you will use two textures for your columns, and alternate between which columns get these textures and which do not, and you will load a third texture for selected spheres. Now, in your second Vertex Shader ("vertex-shader2") add the following declarations: attribute vec2 vTexCoord;
varying vec2 fTexCoord;
Then add the following line at the end of this Vertex Shader: fTexCoord = vTexCoord;
Next, in your Fragment shader you need to add the following three declarations: uniform bool showTexture;
varying vec2 fTexCoord;
uniform sampler2D texture;
Then in the main() method of your Fragment shader find the following line: gl_FragColor = vec4(fColor2.rgb * fColor.rgb * visibility, 1.0);
Replace this line with the following lines: if (showTexture) {
gl_FragColor = vec4(fColor2.rgb * texture2D(texture, fTexCoord).rgb
* visibility, 1.0);
} else {
gl_FragColor = vec4(fColor2.rgb * fColor.rgb * visibility, 1.0);
}
Next, in your JavaScript code, add the following declarations: var tBuffer;
var vTexCoord;
var texCoordsArray = [];
var textureLoc;
var showTextureLoc;
var stripedTexture;
var wallpaperTexture;
var fabricTexture;
var surfaceTextures;
Next, to your init() function add the following lines above the "render();" call: // Create texture buffer and vTexCoord attribute
tBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, tBuffer);
gl.bufferData(gl.ARRAY_BUFFER, flatten(texCoordsArray), gl.STATIC_DRAW);
vTexCoord = gl.getAttribLocation(program, "vTexCoord");
gl.vertexAttribPointer(vTexCoord, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(vTexCoord);
Next, only if you DO NOT HAVE the CORS error, add the following line below the above lines you just added: loadImages(document.getElementById("texture1"),
document.getElementById("texture2"),
document.getElementById("texture3"));
If you DO HAVE the CORS error, don't add the above line, instead add the following lines: var txtImage1 = new Image();
txtImage1.src = filestripedtexturedata;
var txtImage2 = new Image();
txtImage2.src = filewallpapertexturedata;
var txtImage3 = new Image();
txtImage3.src = filefabrictexturedata;
loadImages(txtImage1, txtImage2, txtImage3);
Next, whether or not you have the CORS error, below the code you just added, add the following code: textureLoc = gl.getUniformLocation(program, "texture");
gl.activeTexture(gl.TEXTURE2);
gl.bindTexture(gl.TEXTURE_2D, stripedTexture);
gl.uniform1i(textureLoc, 2);
showTextureLoc = gl.getUniformLocation(program, "showTexture");
gl.uniform1i(showTextureLoc, false);
The next set of instructions are for all students, whether you have the CORS error or not. Add the following function below the init() function: function loadImages(texImage1, texImage2, texImage3) {
stripedTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, stripedTexture);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texImage1);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST);
gl.generateMipmap(gl.TEXTURE_2D);
wallpaperTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, wallpaperTexture);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texImage2);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST);
gl.generateMipmap(gl.TEXTURE_2D);
fabricTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, fabricTexture);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texImage3);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST);
gl.generateMipmap(gl.TEXTURE_2D);
surfaceTextures = [stripedTexture, wallpaperTexture, fabricTexture];
}
Next, find the setDrawState() function, and find the following line: gl.uniform4fv(colorLoc, color);
Below that line add the following line: if (texIndex != null) {
gl.activeTexture(gl.TEXTURE2);
gl.bindTexture(gl.TEXTURE_2D, surfaceTextures[texIndex]);
gl.uniform1i(textureLoc, 2);
gl.uniform1i(showTextureLoc, true);
}
Then, a little below this you will see the empty unsetDrawState() function. Replace it with the following code: function unsetDrawState(texIndex)
{
if (!inShadow && texIndex != null) {
gl.uniform1i(showTextureLoc, false);
}
}
Next, find the quad() function. You need to add code to this function to identify its texture coordinates. Do this by adding the following lines to the end of this function: texCoordsArray.push(vec2(0, 0));
texCoordsArray.push(vec2(0, 1));
texCoordsArray.push(vec2(1, 1));
texCoordsArray.push(vec2(1, 1));
texCoordsArray.push(vec2(1, 0));
texCoordsArray.push(vec2(0, 0));
Next, find the makeSphere() function. You also need to add code to this function to identify its texture coordinates. Do this by first finding the following line: var longitudeBands = 30;
Below that line add the following lines: var u1, u2, v1, v2;
var uv1, uv2, uv3, uv4;
Next, while still in the makeSphere() function, find the last of the following lines in this function: normalsArray.push(vec3(p3));
Below that line add the following lines: u1 = 1 - ((longNumber - 1) / longitudeBands);
u2 = 1 - (longNumber / longitudeBands);
v1 = 1 - ((latNumber - 1) / latitudeBands);
v2 = 1 - (latNumber / latitudeBands);
uv1 = vec2(u1, v1);
uv2 = vec2(u2, v1);
uv3 = vec2(u1, v2);
uv4 = vec2(u2, v2);
texCoordsArray.push(uv1);
texCoordsArray.push(uv2);
texCoordsArray.push(uv3);
texCoordsArray.push(uv2);
texCoordsArray.push(uv4);
texCoordsArray.push(uv3);
Next, find the makeCylinder() function. You also need to add code to this function to identify its texture coordinates. Do this by first finding the following line: var verticalSlices = 30;
Replace that line with the following 3 lines: var verticalSlices = 1;
var u1, u2, v1, v2;
var uv1, uv2, uv3, uv4;
Next, while still in the makeCylinder() function, find the last of the following lines in the first set of for-loops: normalsArray.push(normal);
Below that line add the following lines: u1 = 1 - ((radNumber - 1) / radialSlices);
u2 = 1 - (radNumber / radialSlices);
v1 = 1 - ((vertNumber - 1) / verticalSlices);
v2 = 1 - (vertNumber / verticalSlices);
uv1 = vec2(u1, v1);
uv2 = vec2(u2, v1);
uv3 = vec2(u1, v2);
uv4 = vec2(u2, v2);
texCoordsArray.push(uv1);
texCoordsArray.push(uv2);
texCoordsArray.push(uv3);
texCoordsArray.push(uv2);
texCoordsArray.push(uv4);
texCoordsArray.push(uv3);
Next, while still in the makeCylinder() function, find the last for-loop and find the last of the following lines in that last for-loop: normalsArray.push(normal);
Below that line add the following lines: if (radNumber <= radialSlices / 2) {
u1 = (2 * (radNumber - 1)) / radialSlices;
u2 = (2 * radNumber) / radialSlices;
v1 = 1;
v2 = 1;
}
else {
u1 = 1;
u2 = 1;
v1 = (2 * (radialSlices - radNumber + 1)) / radialSlices;
v2 = (2 * (radialSlices - radNumber)) / radialSlices;
}
u2 = 1 - (radNumber / radialSlices);
v1 = 1 - ((vertNumber - 1) / verticalSlices);
v2 = 1 - (vertNumber / verticalSlices);
uv1 = vec2(0, 0);
uv2 = vec2(u1, v1);
uv3 = vec2(u2, v2);
texCoordsArray.push(uv1);
texCoordsArray.push(uv2);
texCoordsArray.push(uv3);
texCoordsArray.push(uv1);
texCoordsArray.push(uv2);
texCoordsArray.push(uv3);
For your final change, find the initSceneNodes() function, and you will see that there is one case statement that deals with the ground and ten case statements that deal with the columns using the "columnId" nodes. For the ones that deal with the columns the last two "null" values are for textures. The first of these values is for a texture that would go around the curved perimeter of a column and the second of these values is for a texture that would go on the top and bottom of a column. These two null values are for numeric arguments and since you only loaded two textures for your columns, they can be either 1 or 2. For the wallpaper texture, use a 1, for the fabric texture use a 2. For instance if "columnId2" had the following line: scene[columnId2] = createNode(m, column, columnId3, null, brown, grayish, 1, null);
Then the "columnId2" column would have a wallpaper texture around it. Try alternating the value of the first of those two null values for the 10 columns by giving "columnId1" a 1, then giving "columnId2" a 2, then giving "columnId3" a null, then "columnId4" a 1, then "columnId5" a 2, and so on until all of the 10 columns alternate between the different textures. Now you should be all done. All of your columns should be alternating between different textures and no texture. Add a Comment block section to the top of your JavaScript program in this assignment with the following information filled in using the following format: /*
* Course: CS 4722
* Section: .....
* Name: ......
* Professor: ......
* Assignment #: ......
*/
Be sure your program runs without error. DeliverablesTurn in the files:
Do this by uploading the file as an attachment to this Module's assignment drop box in D2L Brightspace. |