// sceneGraph.c #include "sceneGraph.h" #include "objectHandler.h" #include #include #include #include #include #define STB_IMAGE_IMPLEMENTATION #include "../lib/stb_image.h" #define SCG_LINE_BUFFER_SIZE 1024 #define SCG_KEYWORD_BUFFER_SIZE 32 #define KEYWORD_DEFINE_MODEL "model" #define KEYWORD_DEFINE_NODE "obj" #define KEYWORD_DEFINE_OBJ_FILE "file" #define KEYWORD_DEFINE_TEXTURE_FILE "texture" #define KEYWORD_DEFINE_SECONDARY_TEXTURE_FILE "texture2" #define KEYWORD_DEFINE_NORMAL_MAP_FILE "normal" #define KEYWORD_DEFINE_SHININESS "shininess" #define KEYWORD_USE_MODEL "use" #define KEYWORD_DEFINE_PARENT "parent" #define KEYWORD_DEFINE_NAME "name" #define KEYWORD_SCALE "scale" #define KEYWORD_TRANSLATE "translate" #define KEYWORD_ROTATE_X "rotateX" #define KEYWORD_ROTATE_Y "rotateY" #define KEYWORD_ROTATE_Z "rotateZ" void (*renderFunction)(SceneNode*); void setNodeRenderFunction(void (*newRenderFunction)(SceneNode*)) { renderFunction = newRenderFunction; } SceneNode* createSceneNode(int id) { SceneNode* node = (SceneNode*)malloc(sizeof(SceneNode)); identity(&node->transformation); identity(&node->worldTransformation); node->id = id; node->children = NULL; node->numChildren = 0; node->model = NULL; node->name = NULL; return node; } void addChild(SceneNode* parent, SceneNode* child) { parent->children = (SceneNode**)realloc(parent->children, sizeof(SceneNode*) * (parent->numChildren + 1)); parent->children[parent->numChildren] = child; parent->numChildren++; } void updateSceneNode(SceneNode* node, mat4* parentTransformation) { if (parentTransformation) { multiply(&node->worldTransformation, parentTransformation, &node->transformation); } for (int i = 0; i < node->numChildren; i++) { updateSceneNode(node->children[i], &node->worldTransformation); } } void renderSceneNode(SceneNode* node) { if (node->model) { renderFunction(node); } for (int i = 0; i < node->numChildren; i++) { renderSceneNode(node->children[i]); } } void freeSceneNode(SceneNode* node) { for (int i = 0; i < node->numChildren; i++) { freeSceneNode(node->children[i]); } free(node->children); free(node); } void loadTexture(char* textureFile, GLuint* texture) { int width, height, nrChannels; unsigned char* image = stbi_load(textureFile, &width, &height, &nrChannels, 0); // default: 3 channels, RGB GLenum channelFormats[] = { 0, GL_RED, GL_RG, GL_RGB, GL_RGBA }; GLenum format = channelFormats[nrChannels]; glGenTextures(1, texture); glBindTexture(GL_TEXTURE_2D, *texture); printf("%s - %d\n", textureFile, nrChannels); glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, image); // load texture using previously determined format ----- ^^^^^^ glGenerateMipmap(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, 0); stbi_image_free(image); } SceneNode* findNode(int id, SceneNode* root) { if (root->id == id) return root; for (int i = 0; i < root->numChildren; i++) { SceneNode* node = findNode(id, root->children[i]); if (node) return node; } return NULL; } SceneNode* findNodeByName(char* name, SceneNode* root) { if (root->name && strcmp(root->name, name) == 0) return root; for (int i = 0; i < root->numChildren; i++) { SceneNode* node = findNodeByName(name, root->children[i]); if (node) return node; } return NULL; } void printSceneGraph(SceneNode* node, int level) { for (int i = 0; i < level; i++) { printf(" - "); } if (node->name) { printf("%s\n", node->name); } else { printf("%d\n", node->id); } for (int i = 0; i < node->numChildren; i++) { printSceneGraph(node->children[i], level + 1); } } SceneNode* loadSceneGraphFromFile(char* path) { FILE* fp = fopen(path, "r"); if (fp == NULL) { fprintf(stderr, "File could not be opened: %s\n", path); } char buf[SCG_LINE_BUFFER_SIZE]; char keyword[SCG_KEYWORD_BUFFER_SIZE]; // generate keyword buffer format string using SCG_KEYWORD_BUFFER_SIZE // when SCK_KEYWORD_BUFFER_SIZE is 32 keywordBufferFormat will contain "%32s" // avoid buffer overflow while reading buffer while also allowing for dynamic buffer scaling // there is probably a better solution for this but I couldn't find one :( char* keywordBufferFormat = (char*)malloc(sizeof(char) * (4 + (int)log10((double)SCG_KEYWORD_BUFFER_SIZE))); sprintf(keywordBufferFormat, "%%%ds", SCG_KEYWORD_BUFFER_SIZE); SceneNode* root = createSceneNode(-1); root->name = "ROOT"; int maxModelId = 0; int maxObjId = 0; while (fgets(buf, SCG_LINE_BUFFER_SIZE, fp)) { if (buf[0] == '#') continue; sscanf(buf, keywordBufferFormat, keyword); // printf("%s\n", keyword); if (strcmp(keyword, KEYWORD_DEFINE_MODEL) == 0) { int modelId = 0; sscanf(buf, KEYWORD_DEFINE_MODEL " %d", &modelId); if (modelId > maxModelId) { maxModelId = modelId; } } } Model* models = (Model*)malloc(sizeof(Model) * (maxModelId + 1)); int currentModel = 0; SceneNode* currentNode = NULL; bool currentNodeHasParent = false; char filepathBuffer[1024]; rewind(fp); int currentLine = 0; while (fgets(buf, SCG_LINE_BUFFER_SIZE, fp)) { currentLine++; if (buf[0] == '#') continue; if (buf[0] == '\r' || buf[0] == '\n' || buf[0] == '\0') continue; sscanf(buf, keywordBufferFormat, keyword); if (strcmp(keyword, KEYWORD_DEFINE_MODEL) == 0) { sscanf(buf, KEYWORD_DEFINE_MODEL " %d", ¤tModel); models[currentModel].objectData = NULL; models[currentModel].texture = -1; models[currentModel].secondaryTexture = -1; models[currentModel].normalMap = -1; models[currentModel].shininess = 10.0f; continue; } if (strcmp(keyword, KEYWORD_DEFINE_OBJ_FILE) == 0) { sscanf(buf, KEYWORD_DEFINE_OBJ_FILE " %s", filepathBuffer); models[currentModel].objectData = readSingleObjFile(filepathBuffer); continue; } if (strcmp(keyword, KEYWORD_DEFINE_TEXTURE_FILE) == 0) { sscanf(buf, KEYWORD_DEFINE_TEXTURE_FILE " %s", filepathBuffer); loadTexture(filepathBuffer, &models[currentModel].texture); continue; } if (strcmp(keyword, KEYWORD_DEFINE_SECONDARY_TEXTURE_FILE) == 0) { sscanf(buf, KEYWORD_DEFINE_SECONDARY_TEXTURE_FILE " %s", filepathBuffer); loadTexture(filepathBuffer, &models[currentModel].secondaryTexture); continue; } if (strcmp(keyword, KEYWORD_DEFINE_NORMAL_MAP_FILE) == 0) { sscanf(buf, KEYWORD_DEFINE_NORMAL_MAP_FILE " %s", filepathBuffer); loadTexture(filepathBuffer, &models[currentModel].normalMap); continue; } if (strcmp(keyword, KEYWORD_DEFINE_SHININESS) == 0) { sscanf(buf, KEYWORD_DEFINE_SHININESS " %f", &models[currentModel].shininess); continue; } if (strcmp(keyword, KEYWORD_DEFINE_NODE) == 0) { if (currentNode && !currentNodeHasParent) { addChild(root, currentNode); } currentNode = NULL; currentNodeHasParent = false; int nodeId = 0; sscanf(buf, KEYWORD_DEFINE_NODE " %d", &nodeId); if (findNode(nodeId, root)) { fprintf(stderr, "redeclaration of objet with id %d - line %d\n", nodeId, currentLine); return NULL; } currentNode = createSceneNode(nodeId); continue; } if (strcmp(keyword, KEYWORD_DEFINE_NAME) == 0) { if (!currentNode) { fprintf(stderr, "no node selected, can't assign name - line %d\n", currentLine); return NULL; } sscanf(buf, KEYWORD_DEFINE_NAME " %s", filepathBuffer); char* name = (char*)malloc(strlen(filepathBuffer) * sizeof(char) + 1); strcpy(name, filepathBuffer); currentNode->name = name; continue; } if (strcmp(keyword, KEYWORD_DEFINE_PARENT) == 0) { if (!currentNode) { fprintf(stderr, "no node selected, can't assign parent - line %d\n", currentLine); return NULL; } if (currentNodeHasParent) { fprintf(stderr, "selected node already has parent - line %d\n", currentLine); return NULL; } int parentId = 0; sscanf(buf, KEYWORD_DEFINE_PARENT " %d", &parentId); SceneNode* parent = findNode(parentId, root); if (!parent) { fprintf(stderr, "parent node with id %d not found - line %d\n", parentId, currentLine); return NULL; } addChild(parent, currentNode); currentNodeHasParent = true; continue; } if (strcmp(keyword, KEYWORD_USE_MODEL) == 0) { if (!currentNode) { fprintf(stderr, "no node selected to assign model to - line %d\n", currentLine); return NULL; } int usedModel = 0; sscanf(buf, KEYWORD_USE_MODEL " %d", &usedModel); if (usedModel > maxModelId || usedModel < 0) { fprintf(stderr, "model with id %d not found - line %d\n", usedModel, currentLine); return NULL; } currentNode->model = &models[usedModel]; } if (strcmp(keyword, KEYWORD_TRANSLATE) == 0) { if (!currentNode) { fprintf(stderr, "no node selected to assign position to - line %d\n", currentLine); return NULL; } vec3 translation; sscanf(buf, KEYWORD_TRANSLATE " %f %f %f", &translation.x, &translation.y, &translation.z); translate(¤tNode->transformation, ¤tNode->transformation, &translation); } if (strcmp(keyword, KEYWORD_SCALE) == 0) { if (!currentNode) { fprintf(stderr, "no node selected to assign scale to - line %d\n", currentLine); return NULL; } vec3 translation; sscanf(buf, KEYWORD_SCALE " %f %f %f", &translation.x, &translation.y, &translation.z); scale(¤tNode->transformation, ¤tNode->transformation, &translation); } if (strcmp(keyword, KEYWORD_ROTATE_X) == 0) { if (!currentNode) { fprintf(stderr, "no node selected to assign rotationX to - line %d\n", currentLine); return NULL; } GLfloat angle; sscanf(buf, KEYWORD_ROTATE_X " %f", &angle); rotateX(¤tNode->transformation, ¤tNode->transformation, angle); } if (strcmp(keyword, KEYWORD_ROTATE_Y) == 0) { if (!currentNode) { fprintf(stderr, "no node selected to assign rotationY to - line %d\n", currentLine); return NULL; } GLfloat angle; sscanf(buf, KEYWORD_ROTATE_Y " %f", &angle); rotateY(¤tNode->transformation, ¤tNode->transformation, angle); } if (strcmp(keyword, KEYWORD_ROTATE_Z) == 0) { if (!currentNode) { fprintf(stderr, "no node selected to assign rotationZ to - line %d\n", currentLine); return NULL; } GLfloat angle; sscanf(buf, KEYWORD_ROTATE_Z " %f", &angle); rotateZ(¤tNode->transformation, ¤tNode->transformation, angle); } } if (currentNode && !currentNodeHasParent) { addChild(root, currentNode); } updateSceneNode(root, NULL); printSceneGraph(root, 0); return root; }