world_of_cow/mmn_17.c

552 lines
19 KiB
C

#include <GL/freeglut.h>
#include <GL/freeglut_std.h>
#include <GL/gl.h>
#include <GL/glext.h>
#include <GL/glu.h>
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include "ui.h"
#include "grass_texture_color.h"
#include "help.h"
#include "cow.h"
// close enough
#define PI 3.14
// user controlled light thing
vec3 lightPos = { 0.0, 10, 25.0 };
// Camera rotation and stuff
float camRadius = 40.0;
vec3 camCenter = { 0.0, 0.0, 0.0 };
vec3 camRot = { -PI * 0.25, PI, 0.0 };
// some stuff to remember
struct {
unsigned char lockYMovement: 1;
unsigned char showHelp: 1;
unsigned char cowsPOV: 1;
unsigned char controlLight: 1;
GLenum textureFilter;
} settings = {
0, 0, 0, 0, GL_NEAREST,
};
struct {
int main;
int cowControl;
int filters;
} menus; // to simply hold the menues in a labed manner
// holding the lists, although only 1 was used
struct {
GLuint grass;
} lists;
// Ui stuff
int lastMouseButton = GLUT_LEFT_BUTTON; // for camera movement/rotation, also used with the sliders
int lastMouseX = 0, lastMouseY = 0; // for camera movement/rotation
ui_slider* draggedSlider = NULL; // helps when dragging a slider
ui_slider sliders[] = {
{ // ambient red
/* pos */ { { 0.9, 0.9, 0.0 }, { -50.0, -10.0, 0.0 } },
/* size */ { 100.0, 10.0, 10.0 },
0.1
},
{ // ambient green
/* pos */ { { 0.9, 0.9, 0.0 }, { -50.0, -40.0, 0.0 } },
/* size */ { 100.0, 10.0, 10.0 },
0.1
},
{ // ambient blue
/* pos */ { { 0.9, 0.9, 0.0 }, { -50.0, -70.0, 0.0 } },
/* size */ { 100.0, 10.0, 10.0 },
0.1
},
{ // drag speed
/* pos */ { { 0.9, 0.9, 0.0 }, { -50.0, -120.0, 0.0 } },
/* size */ { 100.0, 10.0, 10.0 },
0.5
},
{ // rotation speed
/* pos */ { { 0.9, 0.9, 0.0 }, { -50.0, -170.0, 0.0 } },
/* size */ { 100.0, 10.0, 10.0 },
0.3
},
{ // user light - red
/* pos */ { { 0., 0.9, 0.0 }, { 20.0, -10.0, 0.0 } },
/* size */ { 100.0, 10.0, 10.0 },
0.0
},
{ // user light - green
/* pos */ { { 0.0, 0.9, 0.0 }, { 20.0, -40.0, 0.0 } },
/* size */ { 100.0, 10.0, 10.0 },
0.0
},
{ // user light - blue
/* pos */ { { 0.0, 0.9, 0.0 }, { 20.0, -70.0, 0.0 } },
/* size */ { 100.0, 10.0, 10.0 },
1.0
},
{ // user light - movement step
/* pos */ { { 0.0, 0.9, 0.0 }, { 20.0, -100.0, 0.0 } },
/* size */ { 100.0, 10.0, 10.0 },
0.2
},
{ // user light - constant attenuation
/* pos */ { { 0.0, 0.9, 0.0 }, { 20.0, -130.0, 0.0 } },
/* size */ { 100.0, 10.0, 10.0 },
0.0
},
{ // user light - linear attenuation
/* pos */ { { 0.0, 0.9, 0.0 }, { 20.0, -160.0, 0.0 } },
/* size */ { 100.0, 10.0, 10.0 },
0.0
},
{ // user light - cubic attenuation
/* pos */ { { 0.0, 0.9, 0.0 }, { 20.0, -190.0, 0.0 } },
/* size */ { 100.0, 10.0, 10.0 },
0.5
},
};
// sliders named, struct to give sliders a name, they are stored in an array to ease handling them
struct {
ui_slider* ambientRed;
ui_slider* ambientGreen;
ui_slider* ambientBlue;
ui_slider* dragSpeed;
ui_slider* rotSpeed;
ui_slider* lightRed;
ui_slider* lightGreen;
ui_slider* lightBlue;
ui_slider* lightMove;
ui_slider* lightConAtten;
ui_slider* lightLinAtten;
ui_slider* lightQuadAtten;
} slidersN = {
sliders, sliders + 1, sliders + 2, sliders + 3, sliders + 4, sliders + 5, sliders + 6,
sliders + 7, sliders + 8, sliders + 9, sliders + 10, sliders + 11
};
// generate a list for the grass, using a list makes it much faster
// and the correct way to make them wiggle a bit(wind) is using a vertex shader
// but that was not in the scope of the course
void generateGrassVertexList() {
lists.grass = glGenLists(1);
glNewList(lists.grass, GL_COMPILE);
glDisable(GL_CULL_FACE); // disable culling so we could see the grass from both directions
for(float x = -50.0; x < 50.0; x += 1.0) {
for(float z = -50.0; z < 50.0; z += 1.0) {
float tx = -0.03 * (rand() % 21), tz = -0.03 * (rand() % 21); // give the grass a bit of randomness in their looks
glPushMatrix();
glTranslatef(x, 0.0, z); // move the grass to the locaiton on grid
glRotatef(rand() % 360, 0.0, 1.0, 0.0); // rotate by a random amount
glBegin(GL_TRIANGLES);
glColor3f(0.0, 0.3, 0.0);
glVertex3f(0.0, 0.0, 0.0);
glColor3f(0.0, 0.5, 0.0);
glVertex3f(0.5, 0.0, 0.0);
glVertex3f(tx, 1.5, tz);
glColor3f(0.0, 0.2, 0.0);
glVertex3f(0.0, 0.0, 0.0);
glVertex3f(0.0, 0.0, 0.5);
glColor3f(0.0, 0.5, 0.0);
glVertex3f(tx, 1.5, tz);
glEnd();
glPopMatrix();
}
}
glEnable(GL_CULL_FACE);
glEndList();
}
// draws a tree based on the radius and the height in a location based on the matrix stack
void drawTree(float rad, float height) {
// Bark
glEnable(GL_COLOR_MATERIAL);
glColor3f(0.3, 0.1, 0.3);
glPushMatrix();
glTranslatef(0.0, height, 0.0);
glRotatef(90.0, 1.0, 0.0, 0.0);
glutSolidCylinder(rad, height, 16, 3);
glPopMatrix();
glColor3f(0.0, 1.0, 0.0);
glPushMatrix();
glTranslatef(0.0, height, 0.0);
// leaves, 5 green balls
vec3 ts[] = {
vec3_new(1.0, 0.0, 0.0), vec3_new(-1.0, 0.0, 0.0),
vec3_new(0.0, 0.0, -1.0), vec3_new(0.0, 0.0, 1.0),
vec3_new(0.0, 1.0, 0.0),
};
for(int i = 0; i < 5; i += 1) {
vec3 t = ts[i];
glPushMatrix();
glTranslatef(t.x * rad, t.y * rad, t.z * rad);
glutSolidSphere(rad * 1.5, 8, 8);
glPopMatrix();
}
glPopMatrix();
glColor3f(1.0, 1.0, 1.0);
glDisable(GL_COLOR_MATERIAL);
}
// rotates a vector by the camera rotation
vec3 rotateByCameraRotation(vec3 v) {
vec3 r;
// rotate along the x axis(yz plane)
r.z = cosf(camRot.x) * v.z + sinf(camRot.x) * v.y;
r.y = -sinf(camRot.x) * v.z + cosf(camRot.x) * v.y;
v = r;
// rotate along the y axis(xy plane)
r.z = cosf(camRot.y) * v.z + sinf(camRot.y) * v.x;
r.x = -sinf(camRot.y) * v.z + cosf(camRot.y) * v.x;
return r;
}
void drawUi(void) {
glDisable(GL_LIGHTING);
glDisable(GL_DEPTH_TEST);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
// set ui camera
int wWidth = glutGet(GLUT_WINDOW_WIDTH);
int wHeight = glutGet(GLUT_WINDOW_HEIGHT);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0.0, wWidth, 0.0, wHeight);
// draw sliders
for(ui_slider* s = sliders; s < sliders + sizeof sliders / sizeof(ui_slider); s += 1) {
ui_slider_draw(s);
}
// sliders labels
glColor3f(1.0, 1.0, 1.0);
unsigned char t[] = "Ambient light";
glRasterPos2f(wWidth * 0.9 - glutBitmapLength(GLUT_BITMAP_TIMES_ROMAN_24, t) * 0.5, wHeight * 0.90 + 20.0);
glutBitmapString(GLUT_BITMAP_TIMES_ROMAN_24, t);
glRasterPos2f(wWidth * 0.9 - 75.0, wHeight * 0.9 - 10.0);
glutBitmapString(GLUT_BITMAP_TIMES_ROMAN_24, (unsigned char*)"r\ng\nb");
unsigned char td[] = "Drag speed";
unsigned char tr[] = "Rotate speed";
glRasterPos2f(wWidth * 0.9 - glutBitmapLength(GLUT_BITMAP_TIMES_ROMAN_24, td) * 0.5, wHeight * 0.9 - 100.0);
glutBitmapString(GLUT_BITMAP_TIMES_ROMAN_24, td);
glRasterPos2f(wWidth * 0.9 - glutBitmapLength(GLUT_BITMAP_TIMES_ROMAN_24, tr) * 0.5, wHeight * 0.9 - 150.0);
glutBitmapString(GLUT_BITMAP_TIMES_ROMAN_24, tr);
// light sliders
unsigned char t2[] = "Light orb";
unsigned char side[] = "r\ng\nb\nSpeed\nConstant Atten\nLinear Atten\nQuadraic Atten";
glRasterPos2f(10.0, wHeight * 0.9 + 20.0);
glutBitmapString(GLUT_BITMAP_TIMES_ROMAN_24, t2);
glRasterPos2f(140.0, wHeight * 0.9 - 10.0);
glutBitmapString(GLUT_BITMAP_TIMES_ROMAN_24, side);
// show help if needed
if(settings.showHelp) {
glColor3f(0.3, 0.3, 0.3);
glBegin(GL_QUADS);
glVertex3f(wWidth * 0.1, wHeight * 0.1, 0.0);
glVertex3f(wWidth * 0.9, wHeight * 0.1, 0.0);
glVertex3f(wWidth * 0.9, wHeight * 0.9, 0.0);
glVertex3f(wWidth * 0.1, wHeight * 0.9, 0.0);
glEnd();
glColor3f(1.0,1.0,1.0);
glRasterPos2f(wWidth * 0.12, wHeight * 0.8);
glutBitmapString(GLUT_BITMAP_TIMES_ROMAN_24, helpString);
}
// check for errors
int err = glGetError();
if(err != 0) {
printf("opengl error %d: %s\n", err, gluErrorString(err));
}
}
void drawWorld(void) {
glEnable(GL_DEPTH_TEST);
// set perspective camera
GLdouble wWidth = (GLdouble) glutGet(GLUT_WINDOW_WIDTH);
GLdouble wHeight = (GLdouble) glutGet(GLUT_WINDOW_HEIGHT);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
if(settings.cowsPOV) {
setCameraToCowPOV();
}
else {
// Calculate current camera position from rotation
vec3 cp = vec3_new(0.0, 0.0, camRadius);
cp = rotateByCameraRotation(cp);
cp = vec3_add(cp, camCenter);
vec3 up = rotateByCameraRotation(vec3_new(0.0, 1.0, 0.0));
gluLookAt(cp.x, cp.y, cp.z, camCenter.x, camCenter.y, camCenter.z, up.x, up.y, up.z);
}
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(90, wWidth / wHeight , 0.5, 500.0);
// Lights
glEnable(GL_LIGHTING);
glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE);
// Ambient light
GLfloat ambient[] = { slidersN.ambientRed->value, slidersN.ambientGreen->value, slidersN.ambientBlue->value, 1.0 };
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient);
// GL_LIGHT0 is reserved for the cow's flashlight
// user light
glEnable(GL_LIGHT1);
GLfloat ulPos[] = { lightPos.x, lightPos.y, lightPos.z, 1.0 };
GLfloat ulColor[] = { slidersN.lightRed->value, slidersN.lightGreen->value, slidersN.lightBlue->value, 1.0 };
glLightfv(GL_LIGHT1, GL_POSITION, ulPos);
glLightfv(GL_LIGHT1, GL_AMBIENT, ulColor);
glLightfv(GL_LIGHT1, GL_DIFFUSE, ulColor);
glLightfv(GL_LIGHT1, GL_SPECULAR, ulColor);
glLightf(GL_LIGHT1, GL_CONSTANT_ATTENUATION, slidersN.lightConAtten->value * 5.0 + 0.01);
glLightf(GL_LIGHT1, GL_LINEAR_ATTENUATION, slidersN.lightLinAtten->value * 0.3);
glLightf(GL_LIGHT1, GL_QUADRATIC_ATTENUATION, slidersN.lightQuadAtten->value * 0.01);
// draw a little glowing orb where it stands
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glTranslatef(lightPos.x, lightPos.y, lightPos.z);
glEnable(GL_COLOR_MATERIAL);
glColor3f(1.0, 1.0, 1.0);
glMaterialfv(GL_FRONT, GL_EMISSION, ulColor);
glutSolidSphere(1.0, 8, 8);
GLfloat reset[] = { 0.0, 0.0, 0.0, 1.0 };
glMaterialfv(GL_FRONT, GL_EMISSION, reset);
glDisable(GL_COLOR_MATERIAL);
glPopMatrix();
// World
drawCow(settings.cowsPOV);
// Ground - with a low res grass texture
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, settings.textureFilter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, settings.textureFilter);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 128, 128, 0, GL_RGBA, GL_UNSIGNED_BYTE, grass_color_128);
glEnable(GL_TEXTURE_2D);
glBegin(GL_QUADS);
glColor3f(1.0, 1.0, 0.0);
glNormal3f(0.0, 5.0, 0.0);
glTexCoord2f(0.0, 0.0); glVertex3f(-50.0, 0.0, -50.0);
glTexCoord2f(0.0, 1.0); glVertex3f(-50.0, 0.0, 50.0);
glTexCoord2f(1.0, 1.0); glVertex3f(50.0, 0.0, 50.0);
glTexCoord2f(1.0, 0.0); glVertex3f(50.0, 0.0, -50.0);
glEnd();
glDisable(GL_TEXTURE_2D);
// draw some trees
glPushMatrix();
glTranslatef(25.0, 0.0, 25.0);
drawTree(3.0, 20.0);
glTranslatef(-50.0, 0.0, 0.0);
drawTree(2.0, 19.0);
glPopMatrix();
// Scary shady cube
glPushMatrix();
glTranslatef(0.0, 10.0, 50.0);
glEnable(GL_COLOR_MATERIAL);
glColor3f(0.05, 0.05, 0.1);
GLfloat mat[] = { 0.25, 0.25, 0.25, 0.4, 0.4, 0.4, 0.774597, 0.774597, 0.774597, 0.6 };
glMaterialf(GL_FRONT, GL_SHININESS, mat[9] * 128.0);
glMaterialfv(GL_FRONT, GL_AMBIENT, mat);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat + 3);
glMaterialfv(GL_FRONT, GL_SPECULAR, mat + 6);
glutSolidCube(20.0);
GLfloat matReset[] = {0.0, 0.0, 0.0};
glMaterialf(GL_FRONT, GL_SHININESS, 0.0);
glMaterialfv(GL_FRONT, GL_AMBIENT, matReset);
glMaterialfv(GL_FRONT, GL_DIFFUSE, matReset);
glMaterialfv(GL_FRONT, GL_SPECULAR, matReset);
glPopMatrix();
glColor3f(0.1, 0.3, 0.0);
glCallList(lists.grass);
glColor3f(1.0, 1.0, 1.0);
glDisable(GL_COLOR_MATERIAL);
}
void display(void) {
// Clear screen
glClearColor(0.0, 0.0, 0.0, 1.0); // Dark theme :)
glClear(GL_COLOR_BUFFER_BIT);
glClear(GL_DEPTH_BUFFER_BIT);
drawWorld();
drawUi();
glutSwapBuffers();
glFlush();
}
void mouseEvent(int button, int state, int x, int y) {
// save the last mouse position
lastMouseX = x; lastMouseY = y;
if(state == 0) lastMouseButton = button;
if(state == 0 && button == GLUT_LEFT_BUTTON) {
// check if a slider needs to be dragged
for(ui_slider* s = sliders; s < sliders + sizeof sliders / sizeof(ui_slider); s += 1) {
if(ui_slider_mouse_over(s, x, y)) {
ui_slider_onclick(s, x, y);
draggedSlider = s;
}
}
}
// release the dragged slider(if there was one)
else if(state == 1 && button == GLUT_LEFT_BUTTON) {
draggedSlider = NULL;
}
glutPostRedisplay();
}
void mouseMotionEvent(int x, int y) {
if(lastMouseButton == GLUT_LEFT_BUTTON) {
if(draggedSlider != NULL) { // drag a slider if needed
if(ui_slider_mouse_over(draggedSlider, x, y)) {
ui_slider_onclick(draggedSlider, x, y);
}
}
else {
// rotate the camera
float speed = slidersN.rotSpeed->value * 0.095 + 0.005;
camRot.y -= (x - lastMouseX) * speed;
if(camRot.y > 2 * PI) camRot.y -= 2 * PI;
else if(camRot.y < 0.0) camRot.y += 2 * PI;
camRot.x = fmin(PI * 0.5, fmax(-PI * 0.5, camRot.x - (y - lastMouseY) * speed));
}
}
else if(lastMouseButton == GLUT_MIDDLE_BUTTON) {
// drag the camera
vec3 moveX = rotateByCameraRotation(vec3_new(1.0, 0.0, 0.0));
vec3 moveY = rotateByCameraRotation(vec3_new(0.0, 1.0, 0.0));
if(settings.lockYMovement) {
moveX.y = 0;
moveY.y = 0;
}
float speed = slidersN.dragSpeed->value * 0.9 + 0.1;
camCenter = vec3_add(camCenter, vec3_mult(moveX, (lastMouseX - x) * speed));
camCenter = vec3_add(camCenter, vec3_mult(moveY, (y - lastMouseY) * speed));
}
lastMouseX = x; lastMouseY = y;
glutPostRedisplay();
}
void mouseWheelEvent(int wheel, int dir, int x, int y) {
// slide a slider, if no sliders slided the zoom the camera
char slided = 0;
for(ui_slider* s = sliders; s < sliders + sizeof sliders / sizeof(ui_slider); s += 1) {
if(ui_slider_mouse_over(s, x, y)) {
s->value += dir * 0.05;
if(s->value < 0.0) s->value = 0.0;
if(s->value > 1.0) s->value = 1.0;
slided = 1;
}
}
if(!slided) {
camRadius *= 1.0 - (dir * 0.1);
}
glutPostRedisplay();
}
void keyboardEvent(unsigned char c, int x, int y) {
if(c == '\e') { // exit help, or program
if(settings.showHelp) {
settings.showHelp = 0;
glutPostRedisplay();
}
else {
glutLeaveMainLoop();
exit(0);
}
}
onCowKeyboardInput(c);
// Move light source if applicable
if(settings.controlLight) {
// Controls: wasd - movement over xz plane, qe movement over y axle
// color and such shall be controlled using the sliders
float step = slidersN.lightMove->value * 9.0 + 1.0; // go from 1 to 10;
lightPos.x += ((c == 'd') - (c == 'a')) * step;
lightPos.y += ((c == 'q') - (c == 'e')) * step;
lightPos.z += ((c == 'w') - (c == 's')) * step;
glutPostRedisplay();
}
}
// main right click menu
void onMenuItem(int item) {
lastMouseButton = -1; // to prevent jumps in view position/rotation
switch(item) {
case 0: // Lock y
settings.lockYMovement ^= 1;
glutChangeToMenuEntry(3, settings.lockYMovement ? "Unlock Y position" : "Lock Y position", 0);
break;
case 1: // cow pov
settings.cowsPOV ^= 1;
glutPostRedisplay();
break;
case 2: // Show help
settings.showHelp ^= 1;
glutPostRedisplay();
break;
case 3: // reste view
camRadius = 40.0;
camCenter = vec3_new(0.0, 0.0, 0.0);
camRot = vec3_new(-PI * 0.125, 0.0, 0.0);
glutPostRedisplay();
break;
case 4: // quit
glutLeaveMainLoop();
exit(0);
case 5: // control light source
updateCowControl(COW_CONTROL_NONE);
settings.controlLight = 1;
}
}
// cow control sub menu
void onControlCowMenu(int item) {
lastMouseButton = GLUT_RIGHT_BUTTON;
settings.controlLight = 0;
updateCowControl(item);
}
// filter sub menu
void onFilterMenu(int item) {
lastMouseButton = GLUT_RIGHT_BUTTON;
GLenum filters[] = { GL_LINEAR, GL_NEAREST };
settings.textureFilter = filters[item];
}
void menuSetup() {
// control cow sub-menu
menus.cowControl = glutCreateMenu(onControlCowMenu);
glutAddMenuEntry("Movement", COW_CONTROL_MOVE);
glutAddMenuEntry("Head", COW_CONTROL_HEAD);
glutAddMenuEntry("Tail", COW_CONTROL_TAIL);
// filters
menus.filters = glutCreateMenu(onFilterMenu);
glutAddMenuEntry("Linear", 0);
glutAddMenuEntry("Nearest", 1);
menus.main = glutCreateMenu(onMenuItem);
glutAddSubMenu("Control Cow", menus.cowControl);
glutAddSubMenu("Texture filters", menus.filters);
glutAddMenuEntry("Lock Y position", 0);
glutAddMenuEntry("Change to Cow POV", 1);
glutAddMenuEntry("Reset view", 3);
glutAddMenuEntry("Control point light", 5);
glutAddMenuEntry("Help", 2);
glutAddMenuEntry("Quit", 4);
glutAttachMenu(GLUT_RIGHT_BUTTON);
}
int main(int argc, char** argv) {
/* initialize glut and window */
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
glutInitWindowSize(1280, 720);
glutInitWindowPosition(450, 450);
/* create window and set callbacks */
glutCreateWindow("World of Cow - Aviv Romem");
glutDisplayFunc(display);
glutMouseFunc(mouseEvent);
glutKeyboardFunc(keyboardEvent);
glutMouseWheelFunc(mouseWheelEvent);
glutMotionFunc(mouseMotionEvent);
menuSetup();
// set up some rendering related stuff
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
generateGrassVertexList();
glutMainLoop();
return 0;
}