#if 0 #!/bin/sh FILE=`echo $0 | sed "s/\.cp*$//"` CC="gcc -g -Wall " $CC `sdl-config --cflags` $FILE.c -o $FILE `sdl-config --libs` -lGL exit #endif #include #include #include #include #include "SDL.h" #include "SDL_opengl.h" #define BONE_ABSOLUTE_ANGLE 0x01 #define BONE_ABSOLUTE_POSITION 0x02 #define MAX_CHCOUNT 8 #define MAX_BONECOUNT 20 #define MAX_KFCOUNT 20 #define MAX_VXCOUNT 4 #define MAX_MESHVXCOUNT (MAX_VXCOUNT * MAX_BONECOUNT) #define RAD2DEG(a) (((a) * 180.0) / M_PI) char *currentName = NULL; typedef struct { float x, y, r, g, b; } Vertex; typedef struct _Keyframe { Uint32 time; float angle, length; } Keyframe; typedef struct _Bone { char name[20]; /* Just for the sake of the example */ float x, /* Starting point x */ y, /* Starting point y */ a, /* Angle, in radians */ l, /* Length of the bone */ offA, /* Offset values for interpolation */ offL; Uint8 flags; /* Bone flags */ Uint8 childCount; /* Number of children */ struct _Bone *child[MAX_CHCOUNT], /* Pointers to children */ *parent; /* Parent bone */ Uint32 keyframeCount; Keyframe keyframe[MAX_KFCOUNT]; Uint32 vertexCount; Vertex vertex[MAX_VXCOUNT]; } Bone; typedef struct { Vertex v; /* Info on this vertex */ int boneCount; /* Number of bones this vertex is connected to*/ float weight[MAX_BONECOUNT]; /* Weight for each bone connected */ Bone *bone[MAX_BONECOUNT]; /* Pointer to connected bones */ } BoneVertex; typedef struct { int vertexCount; /* Number of vertexes in this mesh */ BoneVertex v[MAX_MESHVXCOUNT]; /* Vertices of the mesh */ } Mesh; /* Create a bone and return it's address */ Bone *boneAddChild(Bone *root, float x, float y, float a, float l, Uint8 flags, char *name) { Bone *t; int i; if (!root) /* If there is no root, create a new */ { if (!(root = (Bone *)malloc(sizeof(Bone)))) return NULL; root->parent = NULL; } else if (root->childCount < MAX_CHCOUNT) /* If there is space for another child */ { if (!(t = (Bone *)malloc(sizeof(Bone)))) return NULL; t->parent = root; root->child[root->childCount] = t; /* Set the pointer */ root->childCount++; /* Increment the childCounter */ root = t; /* Change the root */ } else /* Can't add a child */ return NULL; /* Set data */ root->x = x; root->y = y; root->a = a; root->l = l; root->flags = flags; root->childCount = 0; if (name) strcpy(root->name, name); else strcpy(root->name, "Bone"); for (i = 0; i < MAX_CHCOUNT; i++) root->child[i] = NULL; return root; } /* Dump on stdout the bone structure. Root of the tree should have level 1 */ void boneDumpTree(Bone *root, Uint8 level) { int i; if (!root) return; for (i = 0; i < level; i++) printf("#"); /* We print # to signal the level of this bone. */ printf(" %4.4f %4.4f %4.4f %4.4f %d %s", root->x, root->y, root->a, root->l, root->flags, root->name); /* Now print animation info */ for (i = 0; i < root->keyframeCount; i++) printf(" %d %4.4f %4.4f", root->keyframe[i].time, root->keyframe[i].angle, root->keyframe[i].length); printf("\n"); /* Recursively call this on my childs */ for (i = 0; i < root->childCount; i++) boneDumpTree(root->child[i], level + 1); } Bone *boneLoadStructure(char *path) { Bone *root, *temp; FILE *file; float x, y, angle, length; int unusedChildrenCount, depth, actualLevel, flags; Uint32 time; char name[20], depthStr[20], animBuf[1024], buffer[1024], *ptr, *token; Keyframe *k; if (!(file = fopen(path, "r"))) { fprintf(stderr, "Can't open file %s for reading\n", path); return NULL; } root = NULL; temp = NULL; actualLevel = 0; while (!feof(file)) { memset(animBuf, 0, 1024); fgets(buffer, 1024, file); sscanf(buffer, "%s %f %f %f %f %d %d %s %[^\n]", depthStr, &x, &y, &angle, &length, &flags, &unusedChildrenCount, name, animBuf); /* Avoid empty strings */ if (strlen(buffer) < 3) continue; /* Calculate the depth */ depth = strlen(depthStr) - 1; if (depth < 0 || depth > MAX_CHCOUNT) { fprintf(stderr, "Wrong bone depth (%s)\n", depthStr); return NULL; } for (; actualLevel > depth; actualLevel--) temp = temp->parent; if (!root && !depth) { root = boneAddChild(NULL, x, y, angle, length, flags, name); temp = root; } else temp = boneAddChild(temp, x, y, angle, length, flags, name); /* Now check for animation data */ if (strlen(animBuf) > 3) { ptr = animBuf; while ((token = strtok(ptr, " "))) { ptr = NULL; sscanf(token, "%d", &time); token = strtok(ptr, " "); sscanf(token, "%f", &angle); token = strtok(ptr, " "); sscanf(token, "%f", &length); printf("Read %d %f %f\n", time, angle, length); if (temp->keyframeCount >= MAX_KFCOUNT) { fprintf(stderr, "Can't add more keyframes\n"); continue; } k = &(temp->keyframe[temp->keyframeCount]); k->time = time; k->angle = angle; k->length = length; temp->keyframeCount++; } } actualLevel++; } return root; } /* Free the bones */ Bone *boneFreeTree(Bone *root) { int i; if (!root) return NULL; for (i = 0; i < root->childCount; i++) boneFreeTree(root->child[i]); free(root); return NULL; } Bone *boneFindByName(Bone *root, char *name) { int i; Bone *p; /* No bone */ if (!root) return NULL; if (!strcmp(root->name, name)) return root; for (i = 0; i < root->childCount; i++) { /* Search recursively */ p = boneFindByName(root->child[i], name); /* Found a bone in this subtree! */ if (p) return p; } /* No such bone */ return NULL; } void boneListNames(Bone *root, char names[MAX_BONECOUNT][20]) { int i, present; if (!root) return; /* Check if this name is already in the list */ present = 0; for (i = 0; (i < MAX_BONECOUNT) && (names[i][0] != '\0'); i++) if (!strcmp(names[i], root->name)) { present = 1; break; } /* If itsn't present and if there is space in list */ if (!present && (i < MAX_BONECOUNT)) { strcpy(names[i], root->name); if (i + 1 < MAX_BONECOUNT) names[i + 1][0] = '\0'; } /* Now fill the list with subtree's names */ for (i = 0; i < root->childCount; i++) boneListNames(root->child[i], names); } void boneGenQuads(Bone *root) { int i; if (!root) return; root->vertex[0].x = 0.0; root->vertex[0].y = 5.0; root->vertex[1].x = 0.0; root->vertex[1].y = -5.0; root->vertex[2].x = root->l; root->vertex[2].y = -5.0; root->vertex[3].x = root->l; root->vertex[3].y = 5.0; for (i = 0; i < 4; i++) { root->vertex[i].r = (rand() % 256) / 256.0; root->vertex[i].g = (rand() % 256) / 256.0; root->vertex[i].b = (rand() % 256) / 256.0; } for (i = 0; i < root->childCount; i++) boneGenQuads(root->child[i]); } void boneDraw(Bone *root, int selected) { int i; glPushMatrix(); if (!strcmp(root->name, currentName)) selected = 1; /* Draw this bone * 1. Rotate the matrix * 2. Translate to coords * 3. Draw the line * 4. Reach the end position (translate again) */ glTranslatef(root->x, root->y, 0.0); glRotatef(RAD2DEG(root->a), 0.0, 0.0, 1.0); glBegin(GL_QUADS); for (i = 0; i < 4; i++) { glColor3f(root->vertex[i].r, root->vertex[i].g, root->vertex[i].b); glVertex2f(root->vertex[i].x, root->vertex[i].y); } glEnd(); glBegin(GL_LINES); if (selected) glColor3f(1.0, 0.0, 0.0); else glColor3f(0.0, 1.0, 0.0); glVertex2f(0, 0); if (selected) glColor3f(1.0, 1.0, 0.0); else glColor3f(0.0, 0.0, 1.0); glVertex2f(root->l, 0); glEnd(); /* Translate to reach the new starting position */ glTranslatef(root->l, 0.0, 0.0); /* Call function on my children */ for (i = 0; i < root->childCount; i++) boneDraw(root->child[i], selected); glPopMatrix(); } /* Returns 1 if there are other frames to be played, 0 if we reached the end */ int boneAnimate(Bone *root, int time) { int i, others = 0; float ang, len, tim; /* Check for keyframes */ for (i = 0; i < root->keyframeCount; i++) if (root->keyframe[i].time == time) { /* Find the index for the interpolation */ if (i != root->keyframeCount - 1) { tim = root->keyframe[i + 1].time - root->keyframe[i].time; ang = root->keyframe[i + 1].angle - root->keyframe[i].angle; len = root->keyframe[i + 1].length - root->keyframe[i].length; root->offA = ang / tim; root->offL = len / tim; // printf("len: %f (off: %f)\n", len, root->offL); } else { root->offA = 0; root->offL = 0; } } else if (root->keyframe[i].time > time) others = 1; /* Change animation */ root->a += root->offA; root->l += root->offL; /* Call on other bones */ for (i = 0; i < root->childCount; i++) if (boneAnimate(root->child[i], time)) others = 1; return others; } void meshLoadData(char *file, Mesh *mesh, Bone *root) { int i, j; char buffer[256], blist[256], *tok, *str; FILE *fd = fopen(file, "r"); float x, y; fgets(buffer, 256, fd); sscanf(buffer, "%d\n", &(mesh->vertexCount)); for (i = 0; i < mesh->vertexCount; i++) { fgets(buffer, 256, fd); sscanf(buffer, "%f %f %[^\n]\n", &x, &y, blist); mesh->v[i].v.x = x; mesh->v[i].v.y = y; str = blist; j = 0; while ((tok = strtok(str, " "))) { str = NULL; mesh->v[i].bone[j] = boneFindByName(root, tok); printf("Vertex %d bone %s", j, mesh->v[i].bone[j]->name); tok = strtok(NULL, " "); mesh->v[i].weight[j] = atof(tok); printf(" is weighted %f\n", mesh->v[i].weight[j]); j++; } mesh->v[i].boneCount = j; printf("This vertex has %d relations\n", j); } fclose(fd); } void getBoneParentMatrix(Bone *b) { if (!b) return; if (b->parent) { getBoneParentMatrix(b->parent); glTranslatef(b->parent->l, 0.0, 0.0); } glTranslatef(b->x, b->y, 0.0); glRotatef(RAD2DEG(b->a), 0.0, 0.0, 1.0); } void getBoneMatrix(Bone *b, float m[16]) { if (!b) return; glPushMatrix(); glLoadIdentity(); if (b->parent) { getBoneParentMatrix(b->parent); glTranslatef(b->parent->l, 0.0, 0.0); } /* Now we are at the end of parent's bone * rotate for this bone and * get the matrix and * return */ glRotatef(RAD2DEG(b->a), 0.0, 0.0, 1.0); glGetFloatv(GL_MODELVIEW_MATRIX, m); glPopMatrix(); } float getBoneAngle(Bone *b) { if (!b) return 0; return b->a + getBoneAngle(b->parent); } /* This function take the tree of bones and the mesh to draw * For each vertex, calculate it's translation based on the bone movement * and the weight of the vertex. */ void meshDraw(Mesh *mesh) { int i, j, n; float v[MAX_VXCOUNT * MAX_BONECOUNT][2], /* End vertexes */ m[16], tmp[4]; n = mesh->vertexCount; glPointSize(3.0); tmp[0] = tmp[1] = 0.0; tmp[2] = 1.0; tmp[3] = 1.0; /* w is always 1.0 */ /* Processing loop */ for (i = 0; i < n; i++) { v[i][0] = v[i][1] = 0.0; tmp[0] = mesh->v[i].v.x; tmp[1] = mesh->v[i].v.y; for (j = 0; j < mesh->v[i].boneCount; j++) { glPushMatrix(); glLoadIdentity(); /* Get the jth bone position */ getBoneMatrix(mesh->v[i].bone[j], m); glTranslatef(m[12], m[13], 0.0); glRotatef(RAD2DEG(getBoneAngle(mesh->v[i].bone[j])), 0.0, 0.0, 1.0); glGetFloatv(GL_MODELVIEW_MATRIX, m); glPopMatrix(); v[i][0] += (tmp[0] * m[0] + tmp[1] * m[4] + m[12]) * mesh->v[i].weight[j]; v[i][1] += (tmp[0] * m[1] + tmp[1] * m[5] + m[13]) * mesh->v[i].weight[j]; } } /* Draw loop */ glPushAttrib(GL_ALL_ATTRIB_BITS); glBegin(GL_POINTS); for (i = 0; i < n; i++) glVertex2f(v[i][0], v[i][1]); glEnd(); glPopAttrib(); } int main(int argc, char **argv) { SDL_Event sdlEv; Uint32 sdlVideoFlags = SDL_OPENGL; Uint8 quit; /* We need one parameter: the structure file */ if (argc < 3) { fprintf(stderr, "This program require 2 filenames as parameter\n"); return EXIT_FAILURE; } /* Initialize */ if (SDL_Init(SDL_INIT_VIDEO) < 0) { fprintf(stderr, "SDL_Init: %s\n", SDL_GetError()); exit(EXIT_FAILURE); } atexit(SDL_Quit); SDL_EnableKeyRepeat(200, 20); /* Start graphic system with OGL */ SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5); SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 5); SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 5); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); if (!SDL_SetVideoMode(400, 400, 0, sdlVideoFlags)) { fprintf(stderr, "SDL_SetVideoMode: %s\n", SDL_GetError()); exit(EXIT_FAILURE); } glShadeModel(GL_SMOOTH); glViewport(0, 0, 400, 400); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(-200, 200, -200, 200, -1, 1); glMatrixMode(GL_MODELVIEW); /* Application Initialization */ Bone *root, *p; int i, frameNum, animating, nameIndex = 0; char names[MAX_BONECOUNT][20]; names[0][0] = '\0'; Mesh body; root = boneLoadStructure(argv[1]); boneListNames(root, names); currentName = names[nameIndex]; for (i = 0; (i < MAX_BONECOUNT) && (names[i][0] != '\0'); i++) printf("Bone name: %s\n", names[i]); glLineWidth(3.0); animating = 0; frameNum = 0; boneGenQuads(root); meshLoadData(argv[2], &body, root); /* Main loop */ quit = 0; while (!quit) { while (SDL_PollEvent(&sdlEv)) switch (sdlEv.type) { case SDL_QUIT: quit = 1; break; case SDL_KEYDOWN: switch (sdlEv.key.keysym.sym) { /* Start/end animation */ case SDLK_a: animating = !animating; break; /* Next bone in the tree */ case SDLK_n: if ((nameIndex < MAX_BONECOUNT) && (names[nameIndex][0] != 0)) nameIndex++; else nameIndex = 0; break; /* Dump the tree */ case SDLK_d: printf("[FRAME]\n"); boneDumpTree(root, 1); break; /* Previous bone in the tree */ case SDLK_p: if (nameIndex > 0) nameIndex--; break; /* Change angle */ case SDLK_LEFT: p = boneFindByName(root, currentName); if (p) p->a += 0.1; break; case SDLK_RIGHT: p = boneFindByName(root, currentName); if (p) p->a -= 0.1; break; /* Change length */ case SDLK_UP: p = boneFindByName(root, currentName); if (p) p->l += 0.4; break; case SDLK_DOWN: p = boneFindByName(root, currentName); if (p) p->l -= 0.4; break; default: break; } currentName = names[nameIndex]; break; case SDL_MOUSEBUTTONDOWN: if (sdlEv.button.button == SDL_BUTTON_LEFT) { printf("Coords: %f %f\n", (float)sdlEv.button.x - 200, (200.0 - (float)sdlEv.button.y)); fflush(stdout); root->x = (float)sdlEv.button.x - 200.0; root->y = 200.0 - (float)sdlEv.button.y; /* We have to flip the y */ } break; default: break; } glClear(GL_COLOR_BUFFER_BIT); glLoadIdentity(); /* Handle the frame animation */ if (animating) if (!boneAnimate(root, frameNum++)) /* Returns 0 if we are at the last frame */ frameNum = 0; /* Loop if no other frames to play */ boneDraw(root, 0); glColor3f(1.0, 1.0, 1.0); meshDraw(&body); SDL_GL_SwapBuffers(); SDL_Delay(50); } return EXIT_SUCCESS; }