123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458 |
- /*
- * 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 <stdio.h>
- #include <stdint.h>
- #include <stdbool.h>
- #include <libc.h>
- #include <SDL.h>
- #include <log.h>
- #include <3dengine.h>
- #include <array.h>
- #include <matrix.h>
- #include <triangle.h>
- #include <display.h>
- #include <vector.h>
- #include <mesh.h>
- #include <light.h>
- #include <camera.h>
- #include <clipping.h>
- bool isRunning = true;
- int previousFrameTime = 0;
- double deltaTime = 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;
- bool doZBuffer = true;
- bool doClipping = true;
- bool doPerspectiveCorrection = true;
- static mesh_t mesh;
- static int windowHeight;
- static int windowWidth;
- static enum displayMode_t currentDisplayMode = DISPLAY_MODE_WIREFRAME;
- static matrix4_t projectionMatrix;
- static matrix4_t viewMatrix;
- static light_t globalLight;
- static double updateTime, renderTime, waitTime;
- static int trianglesCount, trianglesTotal;
- static triangle_t *projectedTriangles = NULL;
- static const vec3_t origin = {0, 0, 0 };
- void setup()
- {
- double FOV = 60;
- double aspectRatioY = (double)windowHeight / windowWidth;
- double aspectRatioX = (double)windowWidth / windowHeight;
- double zNear = 1;
- double zFar = 20;
- projectionMatrix = mat4Perspective(aspectRatioY, FOV, zNear, zFar);
- initFrustumPlanes(FOV, aspectRatioX, zNear, zFar);
- globalLight.direction.x = 0;
- globalLight.direction.y = 0;
- globalLight.direction.z = 1;
- vec3Normalize(&globalLight.direction);
- /* Load object */
- loadOBJFile(&mesh,"assets/dragon.obj");
- //loadCubeMeshData(&mesh);
- /* Load texture */
- loadTextureDateFromPng(&mesh.texture, "assets/f117.png");
- trianglesTotal = arrayGetSize(mesh.faces);
- }
- void processInput()
- {
- SDL_Event event;
- while(SDL_PollEvent(&event))
- {
- switch (event.type)
- {
- case SDL_QUIT:isRunning = false;
- break;
- case SDL_KEYDOWN:
- switch (event.key.keysym.sym)
- {
- 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_ESCAPE: isRunning = false; break;
- case SDLK_u: doNotCull = false; break;
- case SDLK_j: doNotCull = true; break;
- case SDLK_k: doPerspectiveCorrection = false; break;
- case SDLK_i: doPerspectiveCorrection = true; break;
- case SDLK_o: doZBuffer = true; break;
- case SDLK_l: doZBuffer = false; break;
- case SDLK_y: doClipping = true; break;
- case SDLK_h: doClipping = false; break;
- case SDLK_w:
- camera.forwardVelocity = vec3ScalarMult(camera.direction, 5 * deltaTime);
- camera.position = vec3AddVectors(camera.position, camera.forwardVelocity);
- break;
- case SDLK_d: camera.yawAngle += 1.0 * deltaTime; break;
- case SDLK_s:
- camera.forwardVelocity = vec3ScalarMult(camera.direction, 5 * deltaTime);
- camera.position = vec3SubVectors(camera.position, camera.forwardVelocity);
- break;
- case SDLK_a: camera.yawAngle -= 1.0 * deltaTime; break;
- case SDLK_UP: camera.position.y += 3.0 * deltaTime; break;
- case SDLK_DOWN: camera.position.y -= 3.0 * deltaTime; break;
- }
- break;
- default:
- break;
- }
- }
- }
- uint32_t getMicroSecTime()
- {
- struct timeval curTime;
- gettimeofday(&curTime, NULL);
- return ((curTime.tv_sec) * 1000 * 1000) + (curTime.tv_usec);
- }
- static void projectTriangle(triangle_t *workTriangle, vec3_t *triangleNormal, texture_t *texture, colour_t colour)
- {
- int j;
- struct triangle_t currentTriangle = *workTriangle;
- for (j = 0 ; j < 3 ; j++)
- {
- vec4_t projected = mat4ProdVec4Project(projectionMatrix, workTriangle->points[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;
- }
- if (doNotCull)
- {
- currentTriangle.colour = colour;
- }
- else
- {
- double lightLevel = -vec3Dot(*triangleNormal, globalLight.direction);
- currentTriangle.colour = applyLight(colour, lightLevel);
- }
- currentTriangle.averageDepth = (workTriangle->points[0].z +
- workTriangle->points[1].z +
- workTriangle->points[2].z) / 3.0;
- currentTriangle.texture = texture;
- arrayAdd(projectedTriangles, currentTriangle);
- }
- void update()
- {
- uint32_t i, j;
- uint32_t faceCount;
- uint32_t numberOfClippedTriangles;
- uint32_t timeToWait = FRAME_TARGET_TIME - (SDL_GetTicks() - previousFrameTime);
- matrix4_t worldMatrix;
- uint32_t ticks = getMicroSecTime();
- triangle_t trianglesAfterClipping[MAX_NUM_POLYGON_TRIANGLES];
- if (timeToWait <= FRAME_TARGET_TIME)
- {
- SDL_Delay(timeToWait);
- }
- deltaTime = (SDL_GetTicks() - previousFrameTime) / 1000.0;
- previousFrameTime = SDL_GetTicks();
- waitTime = (waitTime + (getMicroSecTime() - ticks) / 1000.) / 2.0;
- ticks = getMicroSecTime();
- mesh.translation.z = 5;
- vec3_t upDirection = { 0, 1, 0 };
- vec3_t lookAtTarget = { 0, 0, 1 };
- camera.direction = vec3FromVec4(mat4ProdVec4(mat4RotationY(camera.yawAngle), vec4FromVec3(lookAtTarget)));
- lookAtTarget = vec3AddVectors(camera.position, camera.direction);
- viewMatrix = mat4LookAt(camera.position, lookAtTarget, upDirection);
- 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);
- /* Last apply the view matrix */
- worldMatrix = mat4ProdMat4(viewMatrix, worldMatrix);
- arrayEmpty(projectedTriangles);
- faceCount = arrayGetSize(mesh.faces);
- for(i = 0; i < faceCount; i++)
- {
- polygon_t polygon;
- 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(origin, vec3FromVec4(transformedVertices[0]));
- vec3Normalize(&cameraRay);
- double cameraDotTriangle = vec3Dot(triangleNormal, cameraRay);
- if (cameraDotTriangle < 0)
- {
- continue;
- }
- }
- if (doClipping)
- {
- polygon = createPolygonFromTriangle(vec3FromVec4(transformedVertices[0]),
- vec3FromVec4(transformedVertices[1]),
- vec3FromVec4(transformedVertices[2]),
- mesh.faces[i].a_uv,
- mesh.faces[i].b_uv,
- mesh.faces[i].c_uv);
- clipPolygon(&polygon);
- createTriangleFromPolygon(&polygon, trianglesAfterClipping, &numberOfClippedTriangles);
- for(j = 0; j < numberOfClippedTriangles; j++)
- {
- projectTriangle(&trianglesAfterClipping[j], &triangleNormal, &mesh.texture, mesh.faces[i].colour);
- }
- }
- else
- {
- triangle_t workTriangle =
- {
- .points =
- {
- [0] = transformedVertices[0],
- [1] = transformedVertices[1],
- [2] = transformedVertices[2]
- },
- .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 }
- }
- };
- projectTriangle(&workTriangle, &triangleNormal, &mesh.texture, mesh.faces[i].colour);
- };
- }
- if (!doZBuffer)
- {
- 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();
- clearFrameBuffer(MAKE_RGB(0, 0, 0));
- clearZBuffer();
- drawGrid(20, MAKE_RGB(27, 2, 27));
- int triangleCount = arrayGetSize(projectedTriangles);
- for(i = 0; i < triangleCount; i++)
- {
- switch(currentDisplayMode)
- {
- case DISPLAY_MODE_SOLID:
- case DISPLAY_MODE_SOLID_WIRE:
- drawFilledTriangle(&projectedTriangles[i]);
- 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,
- MAKE_RGB(255, 0, 0));
- }
- }
- }
- renderFrameBuffer();
- 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: %.1f | dT: %.3f", 1000 / (waitTime + updateTime + renderTime), deltaTime);
- drawText(5, 53, 0x11FF22, "Triangles: %d / %d", triangleCount, trianglesTotal);
- drawText(5, 65, 0x11FF22, "Cull: %c | PeCo: %c | ZB: %c, | CP: %c",
- doNotCull?'N':'Y', doPerspectiveCorrection ? 'Y' : 'N', doZBuffer ? 'Y' : 'N', doClipping ? 'Y' : 'N');
- displayWindowRender();
- }
- 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);
- windowWidth = 320;
- windowHeight = 240;
- 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]); break;
- 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(windowWidth, windowHeight, fullScreen);
- setup();
- while (isRunning)
- {
- processInput();
- update();
- render();
- }
- arrayFree(projectedTriangles);
- textureCleanup(&mesh.texture);
- meshFree(&mesh);
- destroyWindow();
- return 0;
- }
|