src/gl/utils.c
#include <math.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include "utils.h"
#include "TinyPngOut.h"
Various helper functions
A helper function to write a PPM file from a RGB buffer. Downsampling by averaging is performed if samples is larger than one.
void gl_write_image (FILE * fp, const GLubyte * buffer,
unsigned width, unsigned height, unsigned samples)
{
const GLubyte *ptr = buffer;
if (samples < 1)
samples = 1;
if (samples > 4)
samples = 4;
width /= samples, height /= samples;
fprintf (fp, "P6 %d %d 255\n", width, height);
int x, y, j, k;
for (y = height - 1; y >= 0; y--)
for (x = 0; x < width; x++) {
int r = 0, g = 0, b = 0;
for (j = 0; j < samples; j++)
for (k = 0; k < samples; k++) {
int i = (((y*samples + j)*width + x)*samples + k)*4;
if (ptr)
r += ptr[i], g += ptr[i+1], b += ptr[i+2];
}
fputc (r/samples/samples, fp); /* write red */
fputc (g/samples/samples, fp); /* write green */
fputc (b/samples/samples, fp); /* write blue */
}
}
A helper function to write a PNG file from a RGB buffer. Downsampling by averaging is performed if samples is larger than one.
void gl_write_image_png (FILE * fp, const GLubyte * buffer,
unsigned width, unsigned height, unsigned samples)
{
const GLubyte *ptr = buffer;
if (samples < 1)
samples = 1;
if (samples > 4)
samples = 4;
width /= samples, height /= samples;
struct TinyPngOut pngout;
enum TinyPngOut_Status status = TinyPngOut_init (&pngout, width, height, fp);
if (status != TINYPNGOUT_OK) {
fprintf (stderr, "error: TinyPngOut init failed\n");
return;
}
int x, y, j, k;
for (y = height - 1; y >= 0; y--)
for (x = 0; x < width; x++) {
int r = 0, g = 0, b = 0;
for (j = 0; j < samples; j++)
for (k = 0; k < samples; k++) {
int i = (((y*samples + j)*width + x)*samples + k)*4;
if (ptr)
r += ptr[i], g += ptr[i+1], b += ptr[i+2];
}
uint8_t pixel[3] = { r/samples/samples, g/samples/samples, b/samples/samples };
status = TinyPngOut_write (&pngout, pixel, 1);
if (status != TINYPNGOUT_OK) {
fprintf (stderr, "error: TinyPngOut write failed\n");
return;
}
}
}
This is the basic OpenGL setup.
void init_gl() {
GLfloat light0_pos[4] = { 0.0, 0.0, 50.0, 0.0 };
glDisable (GL_CULL_FACE);
glEnable (GL_DEPTH_TEST);
glEnable (GL_NORMALIZE);
/* speedups */
glEnable (GL_DITHER);
glShadeModel (GL_SMOOTH);
glHint (GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
glHint (GL_POLYGON_SMOOTH_HINT, GL_FASTEST);
/* light */
glLightModeli (GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
glLightfv (GL_LIGHT0, GL_POSITION, light0_pos);
GLfloat diffuse[4] = { 0.8, 0.8, 0.8, 1 };
glLightfv (GL_LIGHT0, GL_DIFFUSE, diffuse);
glEnable (GL_LIGHT0);
glEnable (GL_LIGHTING);
glColorMaterial (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
glEnable (GL_COLOR_MATERIAL);
}
void gl_draw_texture (GLuint id, int width, int height)
{
glMatrixMode (GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glOrtho (0.0, width, 0.0, height, -1.0, 1.0);
glMatrixMode (GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
glDisable (GL_LIGHTING);
glColor3f (1,1,1);
glEnable (GL_TEXTURE_2D);
glBindTexture (GL_TEXTURE_2D, id);
glBegin(GL_QUADS);
glTexCoord2f(0, 0); glVertex3f (0, 0, 0);
glTexCoord2f(0, 1); glVertex3f (0, 100, 0);
glTexCoord2f(1, 1); glVertex3f (100, 100, 0);
glTexCoord2f(1, 0); glVertex3f (100, 0, 0);
glEnd();
glDisable(GL_TEXTURE_2D);
glPopMatrix();
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
}
#define RC(r,c) m[(r)+(c)*4]
#define RCM(m,r,c) (m)[(r)+(c)*4]
void matrix_multiply (float * m, const float * n)
{
float o[16];
int i;
for (i = 0; i < 16; i++) o[i] = m[i];
RC(0,0)=RCM(o,0,0)*RCM(n,0,0)+RCM(o,0,1)*RCM(n,1,0)+
RCM(o,0,2)*RCM(n,2,0)+RCM(o,0,3)*RCM(n,3,0);
RC(0,1)=RCM(o,0,0)*RCM(n,0,1)+RCM(o,0,1)*RCM(n,1,1)+
RCM(o,0,2)*RCM(n,2,1)+RCM(o,0,3)*RCM(n,3,1);
RC(0,2)=RCM(o,0,0)*RCM(n,0,2)+RCM(o,0,1)*RCM(n,1,2)+
RCM(o,0,2)*RCM(n,2,2)+RCM(o,0,3)*RCM(n,3,2);
RC(0,3)=RCM(o,0,0)*RCM(n,0,3)+RCM(o,0,1)*RCM(n,1,3)+
RCM(o,0,2)*RCM(n,2,3)+RCM(o,0,3)*RCM(n,3,3);
RC(1,0)=RCM(o,1,0)*RCM(n,0,0)+RCM(o,1,1)*RCM(n,1,0)+
RCM(o,1,2)*RCM(n,2,0)+RCM(o,1,3)*RCM(n,3,0);
RC(1,1)=RCM(o,1,0)*RCM(n,0,1)+RCM(o,1,1)*RCM(n,1,1)+
RCM(o,1,2)*RCM(n,2,1)+RCM(o,1,3)*RCM(n,3,1);
RC(1,2)=RCM(o,1,0)*RCM(n,0,2)+RCM(o,1,1)*RCM(n,1,2)+
RCM(o,1,2)*RCM(n,2,2)+RCM(o,1,3)*RCM(n,3,2);
RC(1,3)=RCM(o,1,0)*RCM(n,0,3)+RCM(o,1,1)*RCM(n,1,3)+
RCM(o,1,2)*RCM(n,2,3)+RCM(o,1,3)*RCM(n,3,3);
RC(2,0)=RCM(o,2,0)*RCM(n,0,0)+RCM(o,2,1)*RCM(n,1,0)+
RCM(o,2,2)*RCM(n,2,0)+RCM(o,2,3)*RCM(n,3,0);
RC(2,1)=RCM(o,2,0)*RCM(n,0,1)+RCM(o,2,1)*RCM(n,1,1)+
RCM(o,2,2)*RCM(n,2,1)+RCM(o,2,3)*RCM(n,3,1);
RC(2,2)=RCM(o,2,0)*RCM(n,0,2)+RCM(o,2,1)*RCM(n,1,2)+
RCM(o,2,2)*RCM(n,2,2)+RCM(o,2,3)*RCM(n,3,2);
RC(2,3)=RCM(o,2,0)*RCM(n,0,3)+RCM(o,2,1)*RCM(n,1,3)+
RCM(o,2,2)*RCM(n,2,3)+RCM(o,2,3)*RCM(n,3,3);
RC(3,0)=RCM(o,3,0)*RCM(n,0,0)+RCM(o,3,1)*RCM(n,1,0)+
RCM(o,3,2)*RCM(n,2,0)+RCM(o,3,3)*RCM(n,3,0);
RC(3,1)=RCM(o,3,0)*RCM(n,0,1)+RCM(o,3,1)*RCM(n,1,1)+
RCM(o,3,2)*RCM(n,2,1)+RCM(o,3,3)*RCM(n,3,1);
RC(3,2)=RCM(o,3,0)*RCM(n,0,2)+RCM(o,3,1)*RCM(n,1,2)+
RCM(o,3,2)*RCM(n,2,2)+RCM(o,3,3)*RCM(n,3,2);
RC(3,3)=RCM(o,3,0)*RCM(n,0,3)+RCM(o,3,1)*RCM(n,1,3)+
RCM(o,3,2)*RCM(n,2,3)+RCM(o,3,3)*RCM(n,3,3);
}
void vector_multiply (float * v, const float * m)
{
float o[4];
int i;
for (i = 0; i < 4; i++) o[i] = v[i];
v[0]=RC(0,0)*o[0]+RC(0,1)*o[1]+RC(0,2)*o[2]+RC(0,3)*o[3];
v[1]=RC(1,0)*o[0]+RC(1,1)*o[1]+RC(1,2)*o[2]+RC(1,3)*o[3];
v[2]=RC(2,0)*o[0]+RC(2,1)*o[1]+RC(2,2)*o[2]+RC(2,3)*o[3];
v[3]=RC(3,0)*o[0]+RC(3,1)*o[1]+RC(3,2)*o[2]+RC(3,3)*o[3];
}
void gl_check_error()
{
switch (glGetError()) {
case GL_NO_ERROR: return;
case GL_INVALID_ENUM: fprintf (stderr, "OpenGL: invalid enum\n"); break;
case GL_INVALID_VALUE: fprintf (stderr, "OpenGL: invalid value\n"); break;
case GL_INVALID_OPERATION: fprintf (stderr, "OpenGL: invalid operation\n");
break;
case GL_INVALID_FRAMEBUFFER_OPERATION:
fprintf (stderr, "OpenGL: invalid framebuffer operation\n"); break;
case GL_OUT_OF_MEMORY:
fprintf (stderr, "OpenGL: out of memory\n"); break;
case GL_STACK_UNDERFLOW:
fprintf (stderr, "OpenGL: stack underflow\n"); break;
case GL_STACK_OVERFLOW:
fprintf (stderr, "OpenGL: stack overflow\n"); break;
}
abort();
}
void gl_get_frustum (Frustum * f)
{
GLint v[4];
glGetIntegerv (GL_VIEWPORT, v);
gl_check_error();
f->width = v[2];
glGetFloatv (GL_MODELVIEW_MATRIX, f->m);
gl_check_error();
glGetFloatv (GL_PROJECTION_MATRIX, f->p);
gl_check_error();
float p[16];
int i;
for (i = 0; i < 16; i++) p[i] = f->p[i];
matrix_multiply (p, f->m);
/* right */
f->n[0][0] = p[3] - p[0];
f->n[0][1] = p[7] - p[4];
f->n[0][2] = p[11] - p[8];
f->d[0] = p[15] - p[12];
/* left */
f->n[1][0] = p[3] + p[0];
f->n[1][1] = p[7] + p[4];
f->n[1][2] = p[11] + p[8];
f->d[1] = p[15] + p[12];
/* top */
f->n[2][0] = p[3] - p[1];
f->n[2][1] = p[7] - p[5];
f->n[2][2] = p[11] - p[9];
f->d[2] = p[15] - p[13];
/* bottom */
f->n[3][0] = p[3] + p[1];
f->n[3][1] = p[7] + p[5];
f->n[3][2] = p[11] + p[9];
f->d[3] = p[15] + p[13];
/* front */
f->n[4][0] = p[3] + p[2];
f->n[4][1] = p[7] + p[6];
f->n[4][2] = p[11] + p[10];
f->d[4] = p[15] + p[14];
/* back */
f->n[5][0] = p[3] - p[2];
f->n[5][1] = p[7] - p[6];
f->n[5][2] = p[11] - p[10];
f->d[5] = p[15] - p[14];
for (i = 0; i < 6; i++) {
float n = sqrt(f->n[i][0]*f->n[i][0] +
f->n[i][1]*f->n[i][1] +
f->n[i][2]*f->n[i][2]);
if (n > 0.) {
f->n[i][0] /= n; f->n[i][1] /= n; f->n[i][2] /= n;
f->d[i] /= n;
}
}
}
/*
Returns 0 if the sphere is outside the view frustum, 1, if it is
inside and -1 if it is partly inside.
*/
int sphere_in_frustum (double x, double y, double z, double r, Frustum * f)
{
int I1 = 0, i, I = 1;
for (i = 0; i < 6; i++) {
double d = f->n[i][0]*x + f->n[i][1]*y + f->n[i][2]*z + f->d[i];
if (d < -r) {
I = 0;
break;
}
if (d < r)
I = -1;
}
if (I == 1)
return 1;
if (I == -1)
I1 = -1;
return I1;
}
/*
Returns the diameter (in pixels) of a sphere projected on the
screen.
*/
float sphere_diameter (double x, double y, double z, double r, Frustum * f)
{
float v[4];
v[0] = x; v[1] = y; v[2] = z; v[3] = 1.;
vector_multiply (v, f->m);
v[0] = r;
vector_multiply (v, f->p);
float rp = v[3] == 0. ? 0 : v[0]*f->width/v[3];
return rp;
}
/*
Replacement for gluPerspective in GLU from:
https://www.khronos.org/opengl/wiki/GluPerspective_code
*/
static
void glhFrustumf2(double *matrix, double left, double right,
double bottom, double top,
double znear, double zfar)
{
double temp, temp2, temp3, temp4;
temp = 2.0 * znear;
temp2 = right - left;
temp3 = top - bottom;
temp4 = zfar - znear;
matrix[0] = temp / temp2;
matrix[1] = 0.0;
matrix[2] = 0.0;
matrix[3] = 0.0;
matrix[4] = 0.0;
matrix[5] = temp / temp3;
matrix[6] = 0.0;
matrix[7] = 0.0;
matrix[8] = (right + left) / temp2;
matrix[9] = (top + bottom) / temp3;
matrix[10] = (-zfar - znear) / temp4;
matrix[11] = -1.0;
matrix[12] = 0.0;
matrix[13] = 0.0;
matrix[14] = (-temp * zfar) / temp4;
matrix[15] = 0.0;
}
void gl_perspective (double fovy, double aspect, double znear, double zfar)
{
double matrix[16];
double ymax, xmax;
ymax = znear * tanf(fovy*M_PI/360.0);
// ymin = -ymax;
// xmin = -ymax * aspectRatio;
xmax = ymax * aspect;
glhFrustumf2 (matrix, -xmax, xmax, -ymax, ymax, znear, zfar);
glMatrixMode (GL_PROJECTION);
glLoadMatrixd (matrix);
}
// derived from: Mesa-7.8.2/src/glu/sgi/libutil/project.c:234
int gl_project (float objx, float objy, float objz,
const float modelMatrix[16],
const float projMatrix[16],
const int viewport[4],
float *winx, float *winy, float *winz)
{
float in[4] = { objx, objy, objz, 1. };
vector_multiply (in, modelMatrix);
vector_multiply (in, projMatrix);
if (in[3] == 0.0) return 0;
*winx = viewport[0] + viewport[2]*(in[0]/in[3] + 1.)/2.;
*winy = viewport[1] + viewport[3]*(in[1]/in[3] + 1.)/2.;
*winz = (in[2]/in[3] + 1.)/2.;
return 1;
}