/* * 3D Engine * main.c: * Based on pikuma.com 3D software renderer in C * Copyright (c) 2021 986-Studio. All rights reserved. * * Created by Manoƫl Trapier on 01/03/2021. */ #include #include #include #include #include #include #include #include #include #include #include #include #include bool isRunning = true; int previousFrameTime = 0; typedef enum displayMode_t { DISPLAY_MODE_WIREFRAME, DISPLAY_MODE_SOLID, DISPLAY_MODE_SOLID_WIRE, DISPLAY_MODE_TEXTURED, DISPLAY_MODE_TEXTURED_WIRE, } displayMode_t; bool displayVertex = false; bool doNotCull = false; enum displayMode_t currentDisplayMode = DISPLAY_MODE_SOLID; matrix4_t projectionMatrix; light_t globalLight; double updateTime, renderTime, waitTime; int trianglesCount, trianglesTotal; vec3_t cameraPosition = { .x = 0, .y = 0, .z = 0, }; triangle_t *projectedTriangles = NULL; void setup() { frameBuffer = (uint32_t *)calloc(windowWidth * windowHeight, sizeof(uint32_t)); if (!frameBuffer) { Log(TLOG_PANIC, NULL, "Memory allocation error."); goto exit; } frameBufferTexture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STREAMING, windowWidth, windowHeight); if (frameBufferTexture == NULL) { Log(TLOG_PANIC, NULL, "SDL Texture creation error: %s", SDL_GetError()); goto exit; } projectionMatrix = mat4Perspective((double)windowHeight / windowWidth, 90, 0.1, 100); globalLight.direction.x = 0; globalLight.direction.y = 0; globalLight.direction.z = 1; vec3Normalize(&globalLight.direction); /* Load texture */ //meshTexture = (colour_t *)REDBRICK_TEXTURE; loadTextureDateFromPng("assets/cube.png"); /* Load object */ loadOBJFile("assets/f22.obj"); //loadCubeMeshData(); trianglesTotal = arrayGetSize(mesh.faces); exit: return; } void processInput() { SDL_Event event; SDL_PollEvent(&event); switch (event.type) { case SDL_QUIT:isRunning = false; break; case SDL_KEYDOWN: switch(event.key.keysym.sym) { case SDLK_ESCAPE: isRunning = false; break; case SDLK_1: displayVertex = true; currentDisplayMode = DISPLAY_MODE_WIREFRAME; break; case SDLK_2: displayVertex = false; currentDisplayMode = DISPLAY_MODE_WIREFRAME; break; case SDLK_3: displayVertex = false; currentDisplayMode = DISPLAY_MODE_SOLID; break; case SDLK_4: displayVertex = false; currentDisplayMode = DISPLAY_MODE_SOLID_WIRE; break; case SDLK_5: displayVertex = false; currentDisplayMode = DISPLAY_MODE_TEXTURED; break; case SDLK_6: displayVertex = false; currentDisplayMode = DISPLAY_MODE_TEXTURED_WIRE; break; case SDLK_c: doNotCull = false; break; case SDLK_d: doNotCull = true; break; case SDLK_v: doPerpectiveCorrection = false; break; case SDLK_f: doPerpectiveCorrection = true; break; } break; default: break; } } uint32_t getMicroSecTime() { struct timeval curTime; gettimeofday(&curTime, NULL); return ((curTime.tv_sec) * 1000 * 1000) + (curTime.tv_usec); } void update() { uint32_t i, j; uint32_t faceCount; uint32_t timeToWait = FRAME_TARGET_TIME - (SDL_GetTicks() - previousFrameTime); matrix4_t worldMatrix; uint32_t ticks = getMicroSecTime(); if (timeToWait <= FRAME_TARGET_TIME) { SDL_Delay(timeToWait); } previousFrameTime = SDL_GetTicks(); waitTime = (waitTime + (getMicroSecTime() - ticks) / 1000.) / 2.0; ticks = getMicroSecTime(); mesh.rotation.x += 0.001; mesh.rotation.y += 0.01; mesh.translation.z = 5; worldMatrix = mat4Identity; worldMatrix = mat4ProdMat4(mat4Scale(mesh.scale.x, mesh.scale.y, mesh.scale.z), worldMatrix); worldMatrix = mat4ProdMat4(mat4RotationZ(mesh.rotation.z), worldMatrix); worldMatrix = mat4ProdMat4(mat4RotationY(mesh.rotation.y), worldMatrix); worldMatrix = mat4ProdMat4(mat4RotationX(mesh.rotation.x), worldMatrix); worldMatrix = mat4ProdMat4(mat4Translate(mesh.translation.x, mesh.translation.y, mesh.translation.z), worldMatrix); arrayEmpty(projectedTriangles); faceCount = arrayGetSize(mesh.faces); for(i = 0; i < faceCount; i++) { triangle_t currentTriangle = { .textureCoordinates = { [0] = { mesh.faces[i].a_uv.u, mesh.faces[i].a_uv.v }, [1] = { mesh.faces[i].b_uv.u, mesh.faces[i].b_uv.v }, [2] = { mesh.faces[i].c_uv.u, mesh.faces[i].c_uv.v } } }; vec4_t transformedVertices[3]; vec3_t triangleNormal; vec3_t vertices[3] = { mesh.vertices[mesh.faces[i].a], mesh.vertices[mesh.faces[i].b], mesh.vertices[mesh.faces[i].c], }; for(j = 0; j < 3; j++) { transformedVertices[j] = vec4FromVec3(vertices[j]); transformedVertices[j] = mat4ProdVec4(worldMatrix, transformedVertices[j]); } vec3_t vectorBA = vec3SubVectors(vec3FromVec4(transformedVertices[1]), vec3FromVec4(transformedVertices[0])); vec3_t vectorCA = vec3SubVectors(vec3FromVec4(transformedVertices[2]), vec3FromVec4(transformedVertices[0])); vec3Normalize(&vectorBA); vec3Normalize(&vectorCA); triangleNormal = vec3Cross(vectorBA, vectorCA); vec3Normalize(&triangleNormal); if (!doNotCull) { vec3_t cameraRay = vec3SubVectors(cameraPosition, vec3FromVec4(transformedVertices[0])); vec3Normalize(&cameraRay); double cameraDotTriangle = vec3Dot(triangleNormal, cameraRay); if (cameraDotTriangle < 0) { continue; } } for (j = 0 ; j < 3 ; j++) { vec4_t projected = mat4ProdVec4Project(projectionMatrix, transformedVertices[j]); projected.x *= windowWidth / 2.; projected.y *= windowHeight / 2.; /* Screen Y axis increase down, 3D world Y grows up, so we need to invert. */ projected.y *= -1.0; projected.x += (windowWidth / 2.); projected.y += (windowHeight / 2.); currentTriangle.points[j].x = projected.x; currentTriangle.points[j].y = projected.y; currentTriangle.points[j].w = projected.w; currentTriangle.points[j].z = projected.z; } double lightLevel = -vec3Dot(triangleNormal, globalLight.direction); currentTriangle.colour = applyLight(mesh.faces[i].colour, lightLevel); currentTriangle.averageDepth = (transformedVertices[0].z + transformedVertices[1].z + transformedVertices[2].z) / 3.0; currentTriangle.texture = meshTexture; arrayAdd(projectedTriangles, currentTriangle); } trianglesCount = arrayGetSize(projectedTriangles); qsort(projectedTriangles, trianglesCount, sizeof(triangle_t), compareTrianglesZOrder); updateTime = (updateTime + (getMicroSecTime() - ticks) / 1000.) / 2.0; } void render() { int i; uint32_t ticks = getMicroSecTime(); SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); SDL_RenderClear(renderer); int triangleCount = arrayGetSize(projectedTriangles); for(i = 0; i < triangleCount; i++) { switch(currentDisplayMode) { case DISPLAY_MODE_SOLID: case DISPLAY_MODE_SOLID_WIRE: drawFilledTriangle(projectedTriangles[i].points[0].x, projectedTriangles[i].points[0].y, projectedTriangles[i].points[1].x, projectedTriangles[i].points[1].y, projectedTriangles[i].points[2].x, projectedTriangles[i].points[2].y, projectedTriangles[i].colour); break; case DISPLAY_MODE_TEXTURED: case DISPLAY_MODE_TEXTURED_WIRE: drawTextureTriangle(&projectedTriangles[i]); break; default: break; } switch (currentDisplayMode) { case DISPLAY_MODE_SOLID_WIRE: case DISPLAY_MODE_TEXTURED_WIRE: case DISPLAY_MODE_WIREFRAME: drawTriangle(projectedTriangles[i].points[0].x, projectedTriangles[i].points[0].y, projectedTriangles[i].points[1].x, projectedTriangles[i].points[1].y, projectedTriangles[i].points[2].x, projectedTriangles[i].points[2].y, projectedTriangles[i].colour); break; default: break; } if (displayVertex) { int j; for(j = 0; j < 3; j++) { drawRectangle(projectedTriangles[i].points[j].x - 1, projectedTriangles[i].points[j].y - 1, 3, 3, 0xFFFF0000); } } } renderFrameBuffer(); clearFrameBuffer(0xFF000000); renderTime = (renderTime + (getMicroSecTime() - ticks) / 1000.) / 2.0; drawText(5, 5, 0x11FF22, "Wait time: %02.2f ms", waitTime); drawText(5, 17, 0x11FF22, "Update time: %02.2f ms", updateTime); drawText(5, 29, 0x11FF22, "Render time: %02.2f ms", renderTime); drawText(5, 41, 0x11FF22, "FPS: %.2f", 1000 / (waitTime + updateTime + renderTime)); drawText(5, 53, 0x11FF22, "Triangles: %d / %d", triangleCount, trianglesTotal); drawText(5, 65, 0x11FF22, "Culling: %s / PerspCorrection: %s", doNotCull?"disabled":"enabled", doPerpectiveCorrection?"enabled":"disabled"); SDL_RenderPresent(renderer); } int main(int argc, char *argv[]) { int param_i; bool fullScreen = false; MAX_DEBUG_LEVEL = TLOG_DEBUG; Log(TLOG_ALWAYS, NULL, "Booting 3D Engine (version %s)!", VERSION); for (param_i = 1 ; (param_i < argc) && (argv[param_i][0] == '-') ; param_i++) { switch (argv[param_i][1]) { default: exit(-1); /* Option not recognized */ case 'f': fullScreen = true; break; case 'w': windowWidth = atoi(argv[++param_i]); case 'h': windowHeight = atoi(argv[++param_i]); break; #ifdef DYNA_LOG_LEVEL case 'l': MAX_DEBUG_LEVEL = atoi(argv[++param_i]); break; #endif case '-': goto no_more_params; /* Could use break, but this is more clear */ } } no_more_params: isRunning = initialiseWindow(fullScreen); setup(); while (isRunning) { processInput(); update(); render(); } arrayFree(projectedTriangles); textureCleanup(); meshFree(); destroyWindow(); return 0; }