#include <GLFW/glfw3.h>
#ifdef __APPLE__
#include <OpenGL/glu.h>
#include <OpenGL/gl.h>
// Tell Xcode IDE to not gripe about OpenGL deprecation
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#else
#include <GL/glu.h>
#include <GL/gl.h>
#endif
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include "steve_data.h"
#include "main.h"
#include "draw_steve.h"
#define GL_PI 3.1415927f
#define Cos(th) cos(GL_PI / 180 * (th))
#define Sin(th) sin(GL_PI / 180 * (th))
#define ORTHO 0
#define PERSP 1
#define RENDER_FROM_HAND_DRAWN 0
#define RENDER_FROM_RAW_PIXEL_DATA 1
// Flag for projection mode (default: orthographic)
GLenum projection = ORTHO;
GLenum render_method = RENDER_FROM_HAND_DRAWN;
/* arm/leg sway rate */
GLfloat r_roc = 0.5f;
// Rotation amounts
GLfloat xRot = 0.0f;
GLfloat yRot = 0.0f;
// Orthographic projection range
GLfloat range = 100.0f;
// Perspective projection field of view
GLfloat fov = 55.0f;
// Framebuffer width and height
GLint fb_width;
GLint fb_height;
void updateProjection(GLfloat w, GLfloat h)
{
// Switch to projection matrix
glMatrixMode(GL_PROJECTION);
// Reset the matrix
glLoadIdentity();
// Get the aspect ratio of the current window
GLfloat aspectRatio = w / h;
switch (projection)
{
// Perspective projection
case PERSP:
gluPerspective(fov, aspectRatio, range / 4, range * 4);
break;
// Orthographic projection
case ORTHO:
if (w <= h)
glOrtho(-range, range,
-range / aspectRatio, range / aspectRatio,
-range, range);
else
glOrtho(-range * aspectRatio, range * aspectRatio,
-range, range,
-range, range);
break;
}
// Switch to modelview matrix
glMatrixMode(GL_MODELVIEW);
// Reset the matrix
glLoadIdentity();
}
void changeSize(GLFWwindow *window, int w, int h)
{
// Set height to 1 in case it's 0 to avoid divide-by-zero
if (h == 0)
h = 1;
// Set the viewport to match the framebuffer size
glViewport(0, 0, w, h);
// Update framebuffer size
fb_height = h;
fb_width = w;
// Update projection
updateProjection(w, h);
}
void drawAxes(float length)
{
glBegin(GL_LINES);
// X axis (Red)
glColor3f(1.0f, 0.0f, 0.0f);
glVertex3f(0, 0, 0);
glVertex3f(length, 0, 0);
// Y axis (Green)
glColor3f(0.0f, 1.0f, 0.0f);
glVertex3f(0, 0, 0);
glVertex3f(0, length, 0);
// Z axis (Blue)
glColor3f(0.0f, 0.0f, 1.0f);
glVertex3f(0, 0, 0);
glVertex3f(0, 0, length);
glEnd();
}
void keyCallback(GLFWwindow *window, int key, int scancode, int action, int mods)
{
if (action == GLFW_PRESS || action == GLFW_REPEAT)
{
switch (key)
{
case GLFW_KEY_UP:
xRot -= 5.0f;
break;
case GLFW_KEY_DOWN:
xRot += 5.0f;
break;
case GLFW_KEY_LEFT:
yRot -= 5.0f;
break;
case GLFW_KEY_RIGHT:
yRot += 5.0f;
break;
case GLFW_KEY_PAGE_UP:
range -= 5.0f;
break;
case GLFW_KEY_PAGE_DOWN:
range += 5.0f;
break;
case GLFW_KEY_LEFT_BRACKET:
fov--;
break;
case GLFW_KEY_RIGHT_BRACKET:
fov++;
break;
case GLFW_KEY_P:
projection = 1 - projection;
break;
case GLFW_KEY_R:
render_method = 1 - render_method;
break;
case GLFW_KEY_W:
r_roc += 0.1f;
/* 64 arbitrarily chosen (minecraft stack limit) */
if (r_roc > 64.0f ) { r_roc = 64.0f; }
break;
case GLFW_KEY_S:
r_roc -= 0.1f;
if (r_roc < 0.0f) { r_roc = 0.0f; }
break;
case GLFW_KEY_ESCAPE:
case GLFW_KEY_Q:
glfwSetWindowShouldClose(window, 1);
break;
}
// Print parameters
printf(" xRot = %6.2f |"
" yRot = %6.2f |"
" range = %6.2f |"
" FOV = %6.2f\n"
" r_roc = %6.2f\n"
" Render method = %s\n"
" --------------\n",
xRot, yRot, range, fov, r_roc,
render_method == RENDER_FROM_HAND_DRAWN ? "\"Hand-drawn\"" : "from raw pixel data");
// Make sure range is not negative (better: greater than 5)
if (range < 5.0f)
range = 5.0f;
// Make sure FOV is between 1 -- 179 degrees
if (fov < 1.0f)
fov = 1.0f;
else if (fov > 179.0f)
fov = 179.0f;
// Make sure rotation amount is smaller than 360 degrees
xRot = (GLint)xRot % 360;
yRot = (GLint)yRot % 360;
// Update projection
updateProjection(fb_width, fb_height);
}
}
void printMenu()
{
puts(("Key bindings:\n"
" - up/down: Rotate around X-axis;\n"
" - left/right: Rotate around Y-axis;\n"
" - page up/down: Zoom in/out (change range);\n"
" - [ / ]: Decrease/increase field of view (perspective);\n"
" - P: Toggle between orthographic and perspective;\n"
" - R: Toggle between hand-drawn and raw pixel data rendering;\n"
" - W/S: Increase/decrease arm/leg sway rate;\n"
" - Q / esc: Exit.\n"));
}
void drawCube(GLfloat x, GLfloat y, GLfloat z,
GLfloat sx, GLfloat sy, GLfloat sz,
GLfloat th, Color color)
{
// Push current matrix to save transformation
glPushMatrix();
// Translate amount x, y, z
glTranslatef(x, y, z);
// Rotate around Y-axis for th degrees
glRotatef(th, 0.0f, 1.0f, 0.0f);
// Scale the object by sx, sy, sz
glScalef(sx, sy, sz);
// Draw cube
glBegin(GL_QUADS);
// Front face
glColor3f(color.r / 255.0f, color.g / 255.0f, color.b / 255.0f);
glVertex3f(-1, -1, 1);
glVertex3f(1, -1, 1);
glVertex3f(1, 1, 1);
glVertex3f(-1, 1, 1);
// Back face
glColor3f(color.r / 255.0f, color.g / 255.0f, color.b / 255.0f);
glVertex3f(-1, -1, -1);
glVertex3f(-1, 1, -1);
glVertex3f(1, 1, -1);
glVertex3f(1, -1, -1);
// Top face
glColor3f(color.r / 255.0f, color.g / 255.0f, color.b / 255.0f);
glVertex3f(-1, 1, -1);
glVertex3f(-1, 1, 1);
glVertex3f(1, 1, 1);
glVertex3f(1, 1, -1);
// Bottom face
glColor3f(color.r / 255.0f, color.g / 255.0f, color.b / 255.0f);
glVertex3f(-1, -1, -1);
glVertex3f(1, -1, -1);
glVertex3f(1, -1, 1);
glVertex3f(-1, -1, 1);
// Right face
glColor3f(color.r / 255.0f, color.g / 255.0f, color.b / 255.0f);
glVertex3f(1, -1, -1);
glVertex3f(1, 1, -1);
glVertex3f(1, 1, 1);
glVertex3f(1, -1, 1);
// Left face
glColor3f(color.r / 255.0f, color.g / 255.0f, color.b / 255.0f);
glVertex3f(-1, -1, -1);
glVertex3f(-1, -1, 1);
glVertex3f(-1, 1, 1);
glVertex3f(-1, 1, -1);
glEnd();
// Pop matrix to restore previous transformation
glPopMatrix();
}
void render()
{
// Clear the color buffer and depth buffer
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Push current matrix to save transformation
// from previous frame
glPushMatrix();
if (projection == ORTHO)
{
// Rotate xRot degrees around X-axis
glRotatef(xRot, 1.0f, 0.0f, 0.0f);
// Rotate yRot degrees around Y-axis
glRotatef(yRot, 0.0f, 1.0f, 0.0f);
}
else if (projection == PERSP)
{
GLdouble Ex = range * Cos(xRot) * Sin(yRot);
GLdouble Ey = range * Sin(xRot);
GLdouble Ez = range * Cos(xRot) * Cos(yRot);
GLdouble up = (Cos(xRot) >= 0) ? 1.0 : -1.0;
gluLookAt(Ex, Ey, Ez, // Eye position
0, 0, 0, // Looking into origin (0,0,0)
0, up, 0 // The up vector
);
}
draw_steve();
// Pop matrix to restore previous transformation
glPopMatrix();
}
void draw_steve()
{
/* render from hand-drawn geometry */
if (render_method == RENDER_FROM_HAND_DRAWN)
{
/* start the arms and legs at 45 degrees */
static float rotation = -45.0f;
static float dir = 1.0f;
rotation += dir * r_roc;
if (rotation >= 45.0f)
{
rotation = 45.0f;
dir = -1.0f;
}
else if (rotation <= -45.0f)
{
rotation = -45.0f;
dir = 1.0f;
}
float legsX = STEVE_BASE + OFFSET_UNIT * 16;
float hipY = STEVE_BASE + OFFSET_UNIT * S_LEG_ROWS;
draw_leg(legsX, hipY, STEVE_BASE, rotation);
draw_leg(legsX + OFFSET_UNIT * S_LEG_WIDTH, hipY, STEVE_BASE, -rotation);
float chestX = legsX;
float chestY = hipY + OFFSET_UNIT;
/* y of the chest's top row */
float shoulderY = chestY + (S_CHEST_ROWS - 1) * OFFSET_UNIT;
draw_chest(chestX, chestY, STEVE_BASE);
/* left arm*/
draw_arm(chestX - S_ARM_WIDTH * OFFSET_UNIT, shoulderY, STEVE_BASE, -rotation);
/* right */
draw_arm(chestX + S_TOROSO_WIDTH * OFFSET_UNIT, shoulderY, STEVE_BASE, rotation);
draw_head(chestX, shoulderY + OFFSET_UNIT, STEVE_BASE);
return;
}
/* render from raw pixel data */
for (int i = 0; i < STEVE_H; i++)
{
for (int j = 0; j < STEVE_W; j++)
{
Pixel pixel = steve[i * STEVE_W + j];
if (pixel.r == 0 && pixel.g == 0 && pixel.b == 0)
{
continue; /* Skip drawing if the pixel is black because we didn't load an alpha channel */
}
/* depth is 4 units whereas head is 8 units */
int depth = i + 8 >= STEVE_H ? 16 : 8;
drawCube(STEVE_BASE + (OFFSET_UNIT * j), STEVE_BASE + (OFFSET_UNIT * i), STEVE_BASE, depth, SIZE_UNIT, SIZE_UNIT, 90.0f, (Color){pixel.r, pixel.g, pixel.b});
}
}
}
void setUp()
{
// Enable depth testC
glEnable(GL_DEPTH_TEST);
}
int main()
{
// Initialize GLFW
if (!glfwInit())
{
return EXIT_FAILURE;
}
// Create window
GLFWwindow *window = glfwCreateWindow(800, 600, "I am... Steve 🤨", NULL, NULL);
if (!window)
{
glfwTerminate();
return EXIT_FAILURE;
}
// Make context current
glfwMakeContextCurrent(window);
// Set key callback
glfwSetKeyCallback(window, keyCallback);
// Set window size change callback
glfwSetFramebufferSizeCallback(window, changeSize);
// Make the window size consistent with framebuffer size
glfwGetFramebufferSize(window, &fb_width, &fb_height);
changeSize(window, fb_width, fb_height);
// Print the menu for key bindings
printMenu();
// Set up
setUp();
// The main loop
while (!glfwWindowShouldClose(window))
{
// Draw the scene in the framebuffer
render();
// Swap out framebuffer to the screen
glfwSwapBuffers(window);
// To process window evens
glfwPollEvents();
}
// Cleanup
glfwDestroyWindow(window);
glfwTerminate();
return EXIT_SUCCESS;
}