/* * 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 bool isRunning = true; int previousFrameTime = 0; typedef enum displayMode_t { DISPLAY_MODE_WIREFRAME, DISPLAY_MODE_SOLID, DISPLAY_MODE_BOTH, } displayMode_t; bool displayVertex = false; bool doNotCull = false; enum displayMode_t currentDisplayMode = DISPLAY_MODE_SOLID; vec3_t cameraPosition = { .x = 0, .y = 0, .z = 0, }; triangle_t *projectedTriangles = NULL; double fovFactor = 640; 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_ARGB8888, SDL_TEXTUREACCESS_STREAMING, windowWidth, windowHeight); if (frameBufferTexture == NULL) { Log(TLOG_PANIC, NULL, "SDL Texture creation error: %s", SDL_GetError()); goto exit; } //loadOBJFile("assets/cube.obj"); loadCubeMeshData(); 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_BOTH; break; case SDLK_c: doNotCull = false; break; case SDLK_d: doNotCull = true; break; } break; default: break; } } vec2_t orthographicPointProjection(vec3_t point) { vec2_t ret; ret.x = point.x * fovFactor; ret.y = point.y * fovFactor; return ret; } vec2_t perspectivePointProjection(vec3_t point) { vec2_t ret; double divisor = point.z; if (divisor == 0) { divisor += 0.00001; } ret.x = (point.x / divisor) * (fovFactor/2.); ret.y = (point.y / divisor) * (fovFactor/2.); return ret; } void update() { uint32_t i, j; uint32_t faceCount; uint32_t timeToWait = FRAME_TARGET_TIME - (SDL_GetTicks() - previousFrameTime); matrix4_t worldMatrix; if (timeToWait <= FRAME_TARGET_TIME) { SDL_Delay(timeToWait); } previousFrameTime = SDL_GetTicks(); mesh.rotation.x += 0.01; mesh.rotation.y += 0.01; mesh.rotation.z += 0.02; //mesh.scale.x += 0.002; //mesh.scale.x += 0.001; mesh.translation.x += 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; vec4_t transformedVertices[3]; vec3_t vertices[3] = { mesh.vertices[mesh.faces[i].a - 1], mesh.vertices[mesh.faces[i].b - 1], mesh.vertices[mesh.faces[i].c - 1], }; for(j = 0; j < 3; j++) { transformedVertices[j] = vec4FromVec3(vertices[j]); transformedVertices[j] = mat4ProdVec4(worldMatrix, transformedVertices[j]); } if (!doNotCull) { vec3_t vectorBA = vec3SubVectors(vec3FromVec4(transformedVertices[1]), vec3FromVec4(transformedVertices[0])); vec3_t vectorCA = vec3SubVectors(vec3FromVec4(transformedVertices[2]), vec3FromVec4(transformedVertices[0])); vec3Normalize(&vectorBA); vec3Normalize(&vectorCA); vec3_t triangleNormal = vec3Cross(vectorBA, vectorCA); vec3Normalize(&triangleNormal); 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++) { vec2_t projected = perspectivePointProjection(vec3FromVec4(transformedVertices[j])); projected.x += (windowWidth / 2); projected.y += (windowHeight / 2); currentTriangle.points[j] = projected; } currentTriangle.colour = mesh.faces[i].colour; currentTriangle.averageDepth = (transformedVertices[0].z + transformedVertices[1].z + transformedVertices[2].z) / 3.0; arrayAdd(projectedTriangles, currentTriangle); } qsort(projectedTriangles, arrayGetSize(projectedTriangles), sizeof(triangle_t), compareTrianglesZOrder); } void render() { int i; SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); SDL_RenderClear(renderer); #if 1 int triangleCount = arrayGetSize(projectedTriangles); for(i = 0; i < triangleCount; i++) { switch(currentDisplayMode) { case DISPLAY_MODE_SOLID: 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_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; case DISPLAY_MODE_BOTH: 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); 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, 0xFF000000 | ~projectedTriangles[i].colour); 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); } } } #else drawTriangle(300, 100, 50, 400, 500, 700, 0xFF00FF00); drawFilledTriangle(300, 100, 50, 400, 500, 700, 0xFFFF0000); #endif renderFrameBuffer(); clearFrameBuffer(0xFF000000); 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); meshFree(); destroyWindow(); return 0; }