546 lines
15 KiB
C
546 lines
15 KiB
C
/* This file is part of ToaruOS and is released under the terms
|
|
* of the NCSA / University of Illinois License - see LICENSE.md
|
|
* Copyright (C) 2013-2014 Kevin Lange
|
|
*
|
|
* GL teapot with shaders.
|
|
*/
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <math.h>
|
|
#include <unistd.h>
|
|
|
|
#include <GL/gl.h>
|
|
#include <GL/glu.h>
|
|
#include <GL/osmesa.h>
|
|
#include <GL/glext.h>
|
|
|
|
#include "lib/yutani.h"
|
|
#include "lib/graphics.h"
|
|
#include "lib/pthread.h"
|
|
|
|
#define PI 3.141592654
|
|
#define TAO 6.28318531
|
|
|
|
/* Despite including the correct header, these are not defined */
|
|
GLuint glCreateShader(GLenum shaderType);
|
|
void glCompileShader(GLuint shader);
|
|
void glAttachShader(GLuint program, GLuint shader);
|
|
void glLinkProgram(GLuint program);
|
|
void glUseProgram(GLuint program);
|
|
GLuint glCreateProgram(void);
|
|
GLint glGetUniformLocation(GLuint program, const GLchar * name);
|
|
void glUniform1i(GLint location, GLint v0);
|
|
void glShaderSource(GLuint shader, GLsizei count, GLchar ** string, GLint * length);
|
|
|
|
GLuint texture_a; /* Diffuse texture */
|
|
GLuint texture_b; /* Environment spheremap */
|
|
|
|
int quit = 0;
|
|
|
|
/* Scene scale */
|
|
float scale = 1.0;
|
|
/* Object rotation */
|
|
float rot = 0.0;
|
|
/* Camera height */
|
|
float height = 1.0;
|
|
/* Where to point the camera */
|
|
float cam_offset = 1.0;
|
|
|
|
char rotation_paused = 0;
|
|
int win_width;
|
|
int win_height;
|
|
float x_light;
|
|
float y_light;
|
|
|
|
/* Normal vector definition */
|
|
typedef struct {
|
|
float x;
|
|
float y;
|
|
float z;
|
|
} normal_t ;
|
|
|
|
/* Vertex object */
|
|
typedef struct {
|
|
float x; /* Coordinates */
|
|
float y;
|
|
float z;
|
|
float u; /* Texture coordinates */
|
|
float v;
|
|
normal_t normal;
|
|
} vertex_t ;
|
|
|
|
/* Resizable array of vertices */
|
|
typedef struct {
|
|
uint32_t len;
|
|
uint32_t capacity;
|
|
vertex_t ** nodes;
|
|
} vertices_t;
|
|
|
|
/* Face definition (3 vertices and a normal vector) */
|
|
typedef struct {
|
|
vertex_t * a;
|
|
vertex_t * b;
|
|
vertex_t * c;
|
|
normal_t normal;
|
|
} face_t;
|
|
|
|
/* Resizable array of faces */
|
|
typedef struct {
|
|
uint32_t len;
|
|
uint32_t capacity;
|
|
face_t ** nodes;
|
|
} faces_t;
|
|
|
|
/* Model vertices */
|
|
vertices_t vertices = {.len = 0, .capacity = 0};
|
|
/* Model triangles */
|
|
faces_t faces = {.len = 0, .capacity = 0};
|
|
|
|
/* Initialize the model objects */
|
|
void init_model() {
|
|
/* We give an initial capacity of 16 for each */
|
|
vertices.capacity = 16;
|
|
vertices.nodes = (vertex_t **)malloc(sizeof(vertex_t *) * vertices.capacity);
|
|
faces.capacity = 16;
|
|
faces.nodes = (face_t **)malloc(sizeof(face_t *) * faces.capacity);
|
|
}
|
|
|
|
/* Add the given coordinates to the vertex list */
|
|
void add_vertex(float x, float y, float z) {
|
|
if (vertices.len == vertices.capacity) {
|
|
/* When we run out of space, increase by two */
|
|
vertices.capacity *= 2;
|
|
vertices.nodes = (vertex_t **)realloc(vertices.nodes, sizeof(vertex_t *) * vertices.capacity);
|
|
}
|
|
/* Create a new vertex in the list */
|
|
vertices.nodes[vertices.len] = malloc(sizeof(vertex_t));
|
|
vertices.nodes[vertices.len]->x = x * scale;
|
|
vertices.nodes[vertices.len]->y = y * scale;
|
|
vertices.nodes[vertices.len]->z = z * scale;
|
|
/* Set texture coordinates by cylindrical mapping */
|
|
float theta = atan2(z,x);
|
|
vertices.nodes[vertices.len]->u = (theta + PI) / (TAO);
|
|
vertices.nodes[vertices.len]->v = (y / 2.0);
|
|
/* Initialize normals to 0,0,0 */
|
|
vertices.nodes[vertices.len]->normal.x = 0.0f;
|
|
vertices.nodes[vertices.len]->normal.y = 0.0f;
|
|
vertices.nodes[vertices.len]->normal.z = 0.0f;
|
|
vertices.len++;
|
|
}
|
|
|
|
/* Add a face with the given vertices */
|
|
void add_face(int a, int b, int c) {
|
|
if (faces.len == faces.capacity) {
|
|
/* Double size when we run out... */
|
|
faces.capacity *= 2;
|
|
faces.nodes = (face_t **)realloc(faces.nodes, sizeof(face_t *) * faces.capacity);
|
|
}
|
|
if (vertices.len < a || vertices.len < b || vertices.len < c) {
|
|
/* Frick... */
|
|
fprintf(stderr, "ERROR: Haven't yet collected enough vertices for the face %d %d %d (have %d)!\n", a, b, c, vertices.len);
|
|
exit(1);
|
|
}
|
|
/* Create a new triangle */
|
|
faces.nodes[faces.len] = malloc(sizeof(face_t));
|
|
faces.nodes[faces.len]->a = vertices.nodes[a-1];
|
|
faces.nodes[faces.len]->b = vertices.nodes[b-1];
|
|
faces.nodes[faces.len]->c = vertices.nodes[c-1];
|
|
/* Calculate some normals */
|
|
vertex_t u = {.x = faces.nodes[faces.len]->b->x - faces.nodes[faces.len]->a->x,
|
|
.y = faces.nodes[faces.len]->b->y - faces.nodes[faces.len]->a->y,
|
|
.z = faces.nodes[faces.len]->b->z - faces.nodes[faces.len]->a->z};
|
|
vertex_t v = {.x = faces.nodes[faces.len]->c->x - faces.nodes[faces.len]->a->x,
|
|
.y = faces.nodes[faces.len]->c->y - faces.nodes[faces.len]->a->y,
|
|
.z = faces.nodes[faces.len]->c->z - faces.nodes[faces.len]->a->z};
|
|
/* Set the face normals */
|
|
faces.nodes[faces.len]->normal.x = ((u.y * v.z) - (u.z * v.y));
|
|
faces.nodes[faces.len]->normal.y = -((u.z * v.x) - (u.x * v.z));
|
|
faces.nodes[faces.len]->normal.z = ((u.x * v.y) - (u.y * v.x));
|
|
faces.len++;
|
|
}
|
|
|
|
void finish_normals() {
|
|
/* Loop through vertices and accumulate normals for them */
|
|
for (uint32_t i = 0; i < faces.len; ++i) {
|
|
/* Vertex a */
|
|
faces.nodes[i]->a->normal.x += faces.nodes[i]->normal.x;
|
|
faces.nodes[i]->a->normal.y += faces.nodes[i]->normal.y;
|
|
faces.nodes[i]->a->normal.z += faces.nodes[i]->normal.z;
|
|
/* Vertex b */
|
|
faces.nodes[i]->b->normal.x += faces.nodes[i]->normal.x;
|
|
faces.nodes[i]->b->normal.y += faces.nodes[i]->normal.y;
|
|
faces.nodes[i]->b->normal.z += faces.nodes[i]->normal.z;
|
|
/* Vertex c */
|
|
faces.nodes[i]->c->normal.x += faces.nodes[i]->normal.x;
|
|
faces.nodes[i]->c->normal.y += faces.nodes[i]->normal.y;
|
|
faces.nodes[i]->c->normal.z += faces.nodes[i]->normal.z;
|
|
}
|
|
}
|
|
|
|
/* Discard the rest of this line */
|
|
void toss(FILE * f) {
|
|
while (fgetc(f) != '\n');
|
|
}
|
|
|
|
/* Load a Wavefront Obj model */
|
|
void load_wavefront(char * filename) {
|
|
/* Open the file */
|
|
FILE * obj = fopen(filename, "r");
|
|
int collected = 0;
|
|
char d = ' ';
|
|
/* Initialize the lists */
|
|
init_model();
|
|
while (!feof(obj)) {
|
|
/* Scan in a line */
|
|
collected = fscanf(obj, "%c ", &d);
|
|
if (collected == 0) continue;
|
|
switch (d) {
|
|
case 'v':
|
|
{
|
|
/* Vertex */
|
|
float x, y, z;
|
|
collected = fscanf(obj, "%f %f %f\n", &x, &y, &z);
|
|
if (collected < 3) fprintf(stderr, "ERROR: Only collected %d points!\n", collected);
|
|
add_vertex(x, y, z);
|
|
}
|
|
break;
|
|
case 'f':
|
|
{
|
|
/* Face */
|
|
int a, b, c;
|
|
collected = fscanf(obj, "%d %d %d\n", &a, &b, &c);
|
|
if (collected < 3) fprintf(stderr, "ERROR: Only collected %d vertices!\n", collected);
|
|
add_face(a,b,c);
|
|
}
|
|
break;
|
|
default:
|
|
/* Something else that we don't care about */
|
|
toss(obj);
|
|
break;
|
|
}
|
|
}
|
|
/* Finalize the vertex normals */
|
|
finish_normals();
|
|
fclose(obj);
|
|
}
|
|
|
|
/* Vertex, fragment, program */
|
|
GLuint v, f, p;
|
|
|
|
/* Read a file into a buffer and return a pointer to the buffer */
|
|
char * readFile(char * filename, int32_t * size) {
|
|
FILE * tex;
|
|
char * texture;
|
|
tex = fopen(filename, "r");
|
|
fseek(tex, 0L, SEEK_END);
|
|
*size = ftell(tex);
|
|
texture = malloc(*size);
|
|
fseek(tex, 0L, SEEK_SET);
|
|
fread(texture, *size, 1, tex);
|
|
fclose(tex);
|
|
return texture;
|
|
}
|
|
|
|
/* Initialize the scene */
|
|
void init(char * object, char * diffuse, char * sphere) {
|
|
load_wavefront(object);
|
|
/* Check for GLEW compatibility */
|
|
#if 0
|
|
glewInit();
|
|
if (glewIsSupported("GL_VERSION_2_0")) {
|
|
printf("Ready.\n");
|
|
} else {
|
|
/* We don't have OpenGL 2.0 support! BAIL! */
|
|
printf("wtf?\n");
|
|
exit(1);
|
|
}
|
|
#endif
|
|
|
|
/* Some nice defaults */
|
|
glClearColor (0.0, 0.0, 0.0, 0.0);
|
|
glEnable(GL_DEPTH_TEST);
|
|
glEnable(GL_TEXTURE_2D);
|
|
|
|
/* Initialize the two textures */
|
|
char * texture;
|
|
int32_t size;
|
|
int dif_size, env_size;
|
|
glGenTextures(1,&texture_a);
|
|
glGenTextures(1,&texture_b);
|
|
/* Diffuse texture { */
|
|
/* The diffuse texture is a wood texture */
|
|
texture = readFile(diffuse, &size); /* We have stored are textures as raw RGBA */
|
|
dif_size = (int)sqrt(size / 4);
|
|
glActiveTexture(GL_TEXTURE0);
|
|
glBindTexture(GL_TEXTURE_2D, texture_a);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
|
|
glTexImage2D(GL_TEXTURE_2D, 0, 3, dif_size, dif_size, 0, GL_RGBA, GL_UNSIGNED_BYTE, texture);
|
|
free(texture);
|
|
/* } */
|
|
/* Sphere map texture { */
|
|
texture = readFile(sphere, &size);
|
|
env_size = (int)sqrt(size / 4);
|
|
glActiveTexture(GL_TEXTURE1);
|
|
glBindTexture(GL_TEXTURE_2D, texture_b);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
|
|
glTexImage2D(GL_TEXTURE_2D, 0, 3, env_size, env_size, 0, GL_RGBA, GL_UNSIGNED_BYTE, texture);
|
|
free(texture);
|
|
/* } */
|
|
|
|
/* Load in the shader programs */
|
|
char *vs = NULL,
|
|
*fs = NULL;
|
|
int32_t v_size, f_size;
|
|
v = glCreateShader(GL_VERTEX_SHADER);
|
|
f = glCreateShader(GL_FRAGMENT_SHADER);
|
|
vs = readFile("teapot.vert", &v_size); /* Vertex shader */
|
|
fs = readFile("teapot.frag", &f_size); /* Fragment shader */
|
|
glShaderSource(v, 1, &vs, (GLint *)&v_size); /* Load... */
|
|
glShaderSource(f, 1, &fs, (GLint *)&f_size);
|
|
free(vs); free(fs); /* Free the data blobs */
|
|
glCompileShader(v); /* Compile... */
|
|
glCompileShader(f);
|
|
p = glCreateProgram(); /* Create a program */
|
|
glAttachShader(p, v); /* Attach the two shaders */
|
|
glAttachShader(p, f);
|
|
glLinkProgram(p); /* Link it all together */
|
|
|
|
/* Use our shaders */
|
|
glUseProgram(p);
|
|
|
|
/* Set the texture sources */
|
|
GLint tex0 = glGetUniformLocation(p, "texture");
|
|
GLint tex1 = glGetUniformLocation(p, "spheremap");
|
|
glUniform1i(tex0, 0);
|
|
glUniform1i(tex1, 1);
|
|
|
|
/* Check for errors */
|
|
GLenum glErr;
|
|
int retCode = 0;
|
|
glErr = glGetError();
|
|
while (glErr != GL_NO_ERROR)
|
|
{
|
|
//printf("glError: %s\n", gluErrorString(glErr));
|
|
retCode = 1;
|
|
glErr = glGetError();
|
|
}
|
|
|
|
|
|
}
|
|
|
|
void lights(void) {
|
|
/* Basic moving lighting */
|
|
GLfloat white[] = {1.0,1.0,1.0,1.0};
|
|
float l_scale = 7.0;
|
|
GLfloat lpos[] = {l_scale * x_light, l_scale * y_light, 3.0};
|
|
|
|
glEnable(GL_LIGHTING);
|
|
glEnable(GL_LIGHT0);
|
|
|
|
glLightfv(GL_LIGHT0, GL_POSITION, lpos);
|
|
glLightfv(GL_LIGHT0, GL_AMBIENT, white);
|
|
glLightfv(GL_LIGHT0, GL_DIFFUSE, white);
|
|
glLightfv(GL_LIGHT0, GL_SPECULAR, white);
|
|
}
|
|
|
|
void display(void) {
|
|
glLoadIdentity ();
|
|
lights();
|
|
/* Point the camera */
|
|
gluLookAt(4.0 * sin(rot),height,-4.0 * cos(rot),
|
|
0.0,cam_offset,0.0,
|
|
0.0,100.0,0.0);
|
|
|
|
if (!rotation_paused) {
|
|
rot += 0.002;
|
|
}
|
|
|
|
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
/* Draw the teapot */
|
|
glBegin(GL_TRIANGLES);
|
|
for (uint32_t i = 0; i < faces.len; ++i) {
|
|
glNormal3f(faces.nodes[i]->a->normal.x, faces.nodes[i]->a->normal.y, faces.nodes[i]->a->normal.z);
|
|
glTexCoord2f(faces.nodes[i]->a->u,faces.nodes[i]->a->v);
|
|
glVertex3f(faces.nodes[i]->a->x, faces.nodes[i]->a->y, faces.nodes[i]->a->z);
|
|
glNormal3f(faces.nodes[i]->b->normal.x, faces.nodes[i]->b->normal.y, faces.nodes[i]->b->normal.z);
|
|
glTexCoord2f(faces.nodes[i]->b->u,faces.nodes[i]->b->v);
|
|
glVertex3f(faces.nodes[i]->b->x, faces.nodes[i]->b->y, faces.nodes[i]->b->z);
|
|
glNormal3f(faces.nodes[i]->c->normal.x, faces.nodes[i]->c->normal.y, faces.nodes[i]->c->normal.z);
|
|
glTexCoord2f(faces.nodes[i]->c->u,faces.nodes[i]->c->v);
|
|
glVertex3f(faces.nodes[i]->c->x, faces.nodes[i]->c->y, faces.nodes[i]->c->z);
|
|
}
|
|
glEnd();
|
|
|
|
glFlush ();
|
|
|
|
}
|
|
|
|
void reshape (int w, int h) {
|
|
/* Reshape the viewport properly */
|
|
win_width = w;
|
|
win_height = h;
|
|
glViewport (0, 0, (GLsizei) w, (GLsizei) h);
|
|
glMatrixMode (GL_PROJECTION);
|
|
glLoadIdentity ();
|
|
gluPerspective(90.0,(double)w / (double)h,0.0001,10.0);
|
|
glMatrixMode (GL_MODELVIEW);
|
|
}
|
|
|
|
int resize(gfx_context_t * ctx, OSMesaContext gl_ctx) {
|
|
|
|
if (!OSMesaMakeCurrent(gl_ctx, ctx->backbuffer, GL_UNSIGNED_BYTE, ctx->width, ctx->height))
|
|
return 1;
|
|
|
|
OSMesaPixelStore(OSMESA_Y_UP, 0);
|
|
|
|
reshape(ctx->width, ctx->height);
|
|
return 0;
|
|
}
|
|
|
|
void keyboard(unsigned char key, int x, int y)
|
|
{
|
|
switch (key) {
|
|
case 'w':
|
|
/* Raise camera */
|
|
height += 0.07;
|
|
break;
|
|
case 's':
|
|
/* Lower camera */
|
|
height -= 0.07;
|
|
break;
|
|
case 'p':
|
|
/* Pause / unpause object movement */
|
|
rotation_paused = !rotation_paused;
|
|
break;
|
|
case 'q':
|
|
quit = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void mouse(int x, int y) {
|
|
x_light = (x - (float)(win_width / 2)) / ((float)win_height) ;
|
|
y_light = (y - (float)(win_height / 2)) / ((float)win_height);
|
|
}
|
|
|
|
static yutani_t * yctx;
|
|
static yutani_window_t * wina;
|
|
static gfx_context_t * ctx;
|
|
|
|
void * draw_thread(void * glctx) {
|
|
while (!quit) {
|
|
display();
|
|
flip(ctx);
|
|
yutani_flip(yctx, wina);
|
|
syscall_yield();
|
|
}
|
|
|
|
pthread_exit(0);
|
|
}
|
|
|
|
|
|
int main(int argc, char** argv) {
|
|
/* default values */
|
|
char * filename = "teapot.obj";
|
|
char * diffuse = "wood.rgba";
|
|
char * sphere = "nvidia.rgba";
|
|
int c, index;
|
|
|
|
chdir("/opt/examples");
|
|
|
|
/* Parse some command-line arguments */
|
|
while ((c = getopt(argc, argv, "d:e:h:s:")) != -1) {
|
|
switch (c) {
|
|
case 'd':
|
|
diffuse = optarg;
|
|
break;
|
|
case 'e':
|
|
sphere = optarg;
|
|
break;
|
|
case 's':
|
|
/* Set scale */
|
|
scale = atof(optarg);
|
|
break;
|
|
case 'h':
|
|
cam_offset = atof(optarg);
|
|
break;
|
|
default:
|
|
/* Uh, that's it for -args */
|
|
printf("Unrecognized argument!\n");
|
|
break;
|
|
}
|
|
}
|
|
/* Get an optional filename from the last non-- parameter */
|
|
for (index = optind; index < argc; ++index) {
|
|
filename = argv[index];
|
|
}
|
|
|
|
printf("Press q to exit.\n");
|
|
|
|
yctx = yutani_init();
|
|
wina = yutani_window_create(yctx, 500, 500);
|
|
yutani_window_move(yctx, wina, 100, 100);
|
|
ctx = init_graphics_yutani_double_buffer(wina);
|
|
draw_fill(ctx, rgb(0,0,0));
|
|
yutani_window_update_shape(yctx, wina, YUTANI_SHAPE_THRESHOLD_HALF);
|
|
|
|
yutani_window_advertise_icon(yctx, wina, "GL Teapot", "teapot");
|
|
|
|
OSMesaContext gl_ctx = OSMesaCreateContext(OSMESA_BGRA, NULL);
|
|
if (resize(ctx, gl_ctx)) {
|
|
fprintf(stderr, "%s: Something bad happened.\n", argv[0]);
|
|
goto finish;
|
|
}
|
|
|
|
/* Load up the file, set everything else up */
|
|
init (filename, diffuse, sphere);
|
|
|
|
/* XXX add a method to query if there are available packets in pex */
|
|
pthread_t thread;
|
|
pthread_create(&thread, NULL, draw_thread, NULL);
|
|
|
|
while (!quit) {
|
|
yutani_msg_t * m = yutani_poll(yctx);
|
|
if (m) {
|
|
switch (m->type) {
|
|
case YUTANI_MSG_KEY_EVENT:
|
|
{
|
|
struct yutani_msg_key_event * ke = (void*)m->data;
|
|
if (ke->event.action == KEY_ACTION_DOWN) {
|
|
keyboard(ke->event.keycode, 0, 0);
|
|
}
|
|
}
|
|
break;
|
|
case YUTANI_MSG_WINDOW_MOUSE_EVENT:
|
|
{
|
|
struct yutani_msg_window_mouse_event * me = (void*)m->data;
|
|
if (me->command == YUTANI_MOUSE_EVENT_DOWN && me->buttons & YUTANI_MOUSE_BUTTON_LEFT) {
|
|
yutani_window_drag_start(yctx, wina);
|
|
}
|
|
}
|
|
break;
|
|
case YUTANI_MSG_SESSION_END:
|
|
quit = 1;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
free(m);
|
|
}
|
|
}
|
|
|
|
finish:
|
|
OSMesaDestroyContext(gl_ctx);
|
|
yutani_close(yctx, wina);
|
|
|
|
return 0;
|
|
}
|