diff --git a/examples/cube/Makefile b/examples/cube/Makefile new file mode 100644 index 0000000..39249a3 --- /dev/null +++ b/examples/cube/Makefile @@ -0,0 +1,13 @@ +obj = cube.o vmath.o +bin = cube + +CC = gcc +CFLAGS = -pedantic -Wall -g -I../.. +LDFLAGS = -L../.. -lX11 -lGL -lGLU -lm -lspnav + +$(bin): $(obj) + $(CC) -o $@ $(obj) $(LDFLAGS) + +.PHONY: clean +clean: + rm -f $(obj) $(bin) diff --git a/examples/cube/cube.c b/examples/cube/cube.c new file mode 100644 index 0000000..47e98c7 --- /dev/null +++ b/examples/cube/cube.c @@ -0,0 +1,313 @@ +/* This example demonstrates how to use libspnav to get space navigator input, + * and use that to rotate and translate a 3D cube. The magellan X11 protocol is + * used (spnav_x11_open) which is compatible with both spacenavd and + * 3Dconnexion's 3dxsrv. + * + * The code is a bit cluttered with X11 and GLX calls, so the interesting bits + * are marked with XXX comments. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "vmath.h" + +#define SQ(x) ((x) * (x)) + +int create_gfx(int xsz, int ysz); +void destroy_gfx(void); +void set_window_title(const char *title); +void redraw(void); +void draw_cube(void); +int handle_event(XEvent *xev); + + +Display *dpy; +Atom wm_prot, wm_del_win; +GLXContext ctx; +Window win; + +vec3_t pos = {0, 0, -6}; +quat_t rot = {0, 0, 0, 1}; + +int redisplay; + +int main(void) +{ + if(!(dpy = XOpenDisplay(0))) { + fprintf(stderr, "failed to connect to the X server"); + return 1; + } + + if(create_gfx(512, 512) == -1) { + return 1; + } + + /* XXX: This actually registers our window with the driver for receiving + * motion/button events through the 3dxsrv-compatible X11 protocol. + */ + if(spnav_x11_open(dpy, win) == -1) { + fprintf(stderr, "failed to connect to the space navigator daemon\n"); + return 1; + } + + glEnable(GL_DEPTH_TEST); + glEnable(GL_CULL_FACE); + + for(;;) { + XEvent xev; + XNextEvent(dpy, &xev); + + if(handle_event(&xev) != 0) { + destroy_gfx(); + XCloseDisplay(dpy); + return 0; + } + + if(redisplay) { + redraw(); + redisplay = 0; + } + } + return 0; +} + +int create_gfx(int xsz, int ysz) +{ + int scr; + Window root; + XVisualInfo *vis; + XSetWindowAttributes xattr; + unsigned int events; + XClassHint class_hint; + + int attr[] = { + GLX_RGBA, GLX_DOUBLEBUFFER, + GLX_RED_SIZE, 8, + GLX_GREEN_SIZE, 8, + GLX_BLUE_SIZE, 8, + GLX_DEPTH_SIZE, 24, + None + }; + + wm_prot = XInternAtom(dpy, "WM_PROTOCOLS", False); + wm_del_win = XInternAtom(dpy, "WM_DELETE_WINDOW", False); + + scr = DefaultScreen(dpy); + root = RootWindow(dpy, scr); + + if(!(vis = glXChooseVisual(dpy, scr, attr))) { + fprintf(stderr, "requested GLX visual is not available\n"); + return -1; + } + + if(!(ctx = glXCreateContext(dpy, vis, 0, True))) { + fprintf(stderr, "failed to create GLX context\n"); + XFree(vis); + return -1; + } + + xattr.background_pixel = xattr.border_pixel = BlackPixel(dpy, scr); + xattr.colormap = XCreateColormap(dpy, root, vis->visual, AllocNone); + + if(!(win = XCreateWindow(dpy, root, 0, 0, xsz, ysz, 0, vis->depth, InputOutput, + vis->visual, CWColormap | CWBackPixel | CWBorderPixel, &xattr))) { + fprintf(stderr, "failed to create X window\n"); + return -1; + } + XFree(vis); + + /* set the window event mask */ + events = ExposureMask | StructureNotifyMask | KeyPressMask | KeyReleaseMask | + ButtonReleaseMask | ButtonPressMask | PointerMotionMask; + XSelectInput(dpy, win, events); + + XSetWMProtocols(dpy, win, &wm_del_win, 1); + + set_window_title("libspnav cube"); + + class_hint.res_name = "cube"; + class_hint.res_class = "cube"; + XSetClassHint(dpy, win, &class_hint); + + if(glXMakeCurrent(dpy, win, ctx) == False) { + fprintf(stderr, "glXMakeCurrent failed\n"); + glXDestroyContext(dpy, ctx); + XDestroyWindow(dpy, win); + return -1; + } + + XMapWindow(dpy, win); + XFlush(dpy); + + return 0; +} + +void destroy_gfx(void) +{ + glXDestroyContext(dpy, ctx); + XDestroyWindow(dpy, win); + glXMakeCurrent(dpy, None, 0); +} + +void set_window_title(const char *title) +{ + XTextProperty wm_name; + + XStringListToTextProperty((char**)&title, 1, &wm_name); + XSetWMName(dpy, win, &wm_name); + XSetWMIconName(dpy, win, &wm_name); + XFree(wm_name.value); +} + +void redraw(void) +{ + mat4_t xform; + + quat_to_mat(xform, rot); + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + glTranslatef(pos.x, pos.y, pos.z); + glMultTransposeMatrixf((float*)xform); + + draw_cube(); + + glXSwapBuffers(dpy, win); +} + +void draw_cube(void) +{ + glBegin(GL_QUADS); + /* face +Z */ + glNormal3f(0, 0, 1); + glColor3f(1, 0, 0); + glVertex3f(-1, -1, 1); + glVertex3f(1, -1, 1); + glVertex3f(1, 1, 1); + glVertex3f(-1, 1, 1); + /* face +X */ + glNormal3f(1, 0, 0); + glColor3f(0, 1, 0); + glVertex3f(1, -1, 1); + glVertex3f(1, -1, -1); + glVertex3f(1, 1, -1); + glVertex3f(1, 1, 1); + /* face -Z */ + glNormal3f(0, 0, -1); + glColor3f(0, 0, 1); + glVertex3f(1, -1, -1); + glVertex3f(-1, -1, -1); + glVertex3f(-1, 1, -1); + glVertex3f(1, 1, -1); + /* face -X */ + glNormal3f(-1, 0, 0); + glColor3f(1, 1, 0); + glVertex3f(-1, -1, -1); + glVertex3f(-1, -1, 1); + glVertex3f(-1, 1, 1); + glVertex3f(-1, 1, -1); + /* face +Y */ + glNormal3f(0, 1, 0); + glColor3f(0, 1, 1); + glVertex3f(-1, 1, 1); + glVertex3f(1, 1, 1); + glVertex3f(1, 1, -1); + glVertex3f(-1, 1, -1); + /* face -Y */ + glNormal3f(0, -1, 0); + glColor3f(1, 0, 1); + glVertex3f(-1, -1, -1); + glVertex3f(1, -1, -1); + glVertex3f(1, -1, 1); + glVertex3f(-1, -1, 1); + glEnd(); +} + +int handle_event(XEvent *xev) +{ + static int win_mapped; + KeySym sym; + spnav_event spev; + + switch(xev->type) { + case MapNotify: + win_mapped = 1; + break; + + case UnmapNotify: + win_mapped = 0; + break; + + case Expose: + if(win_mapped && xev->xexpose.count == 0) { + redraw(); + } + break; + + case ClientMessage: + /* XXX check if the event is a spacenav event */ + if(spnav_x11_event(xev, &spev)) { + /* if so deal with motion and button events */ + if(spev.type == SPNAV_EVENT_MOTION) { + /* apply axis/angle rotation to the quaternion */ + float angle = 0.000005 * sqrt(SQ(spev.motion.rx) + SQ(spev.motion.ry) + SQ(spev.motion.rz)); + rot = quat_rotate(rot, angle, -spev.motion.rx, -spev.motion.ry, spev.motion.rz); + rot = quat_normalize(rot); + + /* add translation */ + pos.x += spev.motion.x * 0.001; + pos.y += spev.motion.y * 0.001; + pos.z -= spev.motion.z * 0.001; + + redisplay = 1; + } else { + /* on button press, reset the cube */ + if(spev.button.press) { + pos = v3_cons(0, 0, -6); + rot = quat_cons(1, 0, 0, 0); + + redisplay = 1; + } + } + /* finally remove any other queued motion events */ + spnav_remove_events(SPNAV_EVENT_MOTION); + + } else if(xev->xclient.message_type == wm_prot) { + if(xev->xclient.data.l[0] == wm_del_win) { + return 1; + } + } + break; + + case KeyPress: + sym = XLookupKeysym((XKeyEvent*)&xev->xkey, 0); + if((sym & 0xff) == 27) { + return 1; + } + + case ConfigureNotify: + { + int x = xev->xconfigure.width; + int y = xev->xconfigure.height; + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + gluPerspective(45.0, (float)x / (float)y, 1.0, 1000.0); + + glViewport(0, 0, x, y); + } + break; + + default: + break; + } + return 0; +} diff --git a/examples/cube/vmath.c b/examples/cube/vmath.c new file mode 100644 index 0000000..c08877d --- /dev/null +++ b/examples/cube/vmath.c @@ -0,0 +1,16 @@ +#include +#include "vmath.h" + +quat_t quat_rotate(quat_t q, float angle, float x, float y, float z) +{ + quat_t rq; + float half_angle = angle * 0.5; + float sin_half = sin(half_angle); + + rq.w = cos(half_angle); + rq.x = x * sin_half; + rq.y = y * sin_half; + rq.z = z * sin_half; + + return quat_mul(q, rq); +} diff --git a/examples/cube/vmath.h b/examples/cube/vmath.h new file mode 100644 index 0000000..327620c --- /dev/null +++ b/examples/cube/vmath.h @@ -0,0 +1,32 @@ +#ifndef VMATH_H_ +#define VMATH_H_ + +typedef struct { float x, y, z; } vec3_t; +typedef struct { float x, y, z, w; } vec4_t; + +typedef vec4_t quat_t; + +typedef float mat4_t[4][4]; + +/* vector functions */ +static inline vec3_t v3_cons(float x, float y, float z); +static inline float v3_dot(vec3_t v1, vec3_t v2); + +/* quaternion functions */ +static inline quat_t quat_cons(float s, float x, float y, float z); +static inline vec3_t quat_vec(quat_t q); +static inline quat_t quat_mul(quat_t q1, quat_t q2); +static inline quat_t quat_normalize(quat_t q); +static inline void quat_to_mat(mat4_t res, quat_t q); +quat_t quat_rotate(quat_t q, float angle, float x, float y, float z); + +/* matrix functions */ +static inline void m4_cons(mat4_t m, + float m11, float m12, float m13, float m14, + float m21, float m22, float m23, float m24, + float m31, float m32, float m33, float m34, + float m41, float m42, float m43, float m44); + +#include "vmath.inl" + +#endif /* VMATH_H_ */ diff --git a/examples/cube/vmath.inl b/examples/cube/vmath.inl new file mode 100644 index 0000000..95dc8c3 --- /dev/null +++ b/examples/cube/vmath.inl @@ -0,0 +1,78 @@ +/* vector functions */ +static inline vec3_t v3_cons(float x, float y, float z) +{ + vec3_t res; + res.x = x; + res.y = y; + res.z = z; + return res; +} + +static inline vec3_t quat_vec(quat_t q) +{ + vec3_t v; + v.x = q.x; + v.y = q.y; + v.z = q.z; + return v; +} + +static inline float v3_dot(vec3_t v1, vec3_t v2) +{ + return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z; +} + +/* quaternion functions */ +static inline quat_t quat_cons(float s, float x, float y, float z) +{ + quat_t q; + q.x = x; + q.y = y; + q.z = z; + q.w = s; + return q; +} + +static inline quat_t quat_mul(quat_t q1, quat_t q2) +{ + quat_t res; + vec3_t v1 = quat_vec(q1); + vec3_t v2 = quat_vec(q2); + + res.w = q1.w * q2.w - v3_dot(v1, v2); + res.x = v2.x * q1.w + v1.x * q2.w + (v1.y * v2.z - v1.z * v2.y); + res.y = v2.y * q1.w + v1.y * q2.w + (v1.z * v2.x - v1.x * v2.z); + res.z = v2.z * q1.w + v1.z * q2.w + (v1.x * v2.y - v1.y * v2.x); + return res; +} + +static inline quat_t quat_normalize(quat_t q) +{ + float len = sqrt(q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w); + q.x /= len; + q.y /= len; + q.z /= len; + q.w /= len; + return q; +} + +static inline void quat_to_mat(mat4_t res, quat_t q) +{ + m4_cons(res, 1.0 - 2.0 * q.y*q.y - 2.0 * q.z*q.z, 2.0 * q.x * q.y + 2.0 * q.w * q.z, 2.0 * q.z * q.x - 2.0 * q.w * q.y, 0, + 2.0 * q.x * q.y - 2.0 * q.w * q.z, 1.0 - 2.0 * q.x*q.x - 2.0 * q.z*q.z, 2.0 * q.y * q.z + 2.0 * q.w * q.x, 0, + 2.0 * q.z * q.x + 2.0 * q.w * q.y, 2.0 * q.y * q.z - 2.0 * q.w * q.x, 1.0 - 2.0 * q.x*q.x - 2.0 * q.y*q.y, 0, + 0, 0, 0, 1); +} + +/* matrix functions */ +static inline void m4_cons(mat4_t m, + float m11, float m12, float m13, float m14, + float m21, float m22, float m23, float m24, + float m31, float m32, float m33, float m34, + float m41, float m42, float m43, float m44) +{ + m[0][0] = m11; m[0][1] = m12; m[0][2] = m13; m[0][3] = m14; + m[1][0] = m21; m[1][1] = m22; m[1][2] = m23; m[1][3] = m24; + m[2][0] = m31; m[2][1] = m32; m[2][2] = m33; m[2][3] = m34; + m[3][0] = m41; m[3][1] = m42; m[3][2] = m43; m[3][3] = m44; +} diff --git a/examples/Makefile b/examples/simple/Makefile similarity index 79% rename from examples/Makefile rename to examples/simple/Makefile index bf13c70..0cb7ac1 100644 --- a/examples/Makefile +++ b/examples/simple/Makefile @@ -1,6 +1,6 @@ CC = gcc -CFLAGS = -pedantic -Wall -g -I.. -LDFLAGS = -L.. -lspnav -lX11 +CFLAGS = -pedantic -Wall -g -I../.. +LDFLAGS = -L../.. -lspnav -lX11 .PHONY: all all: simple_x11 simple_af_unix diff --git a/examples/simple.c b/examples/simple/simple.c similarity index 100% rename from examples/simple.c rename to examples/simple/simple.c