main.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487
  1. /*
  2. * 3D Engine
  3. * main.c:
  4. * Based on pikuma.com 3D software renderer in C
  5. * Copyright (c) 2021 986-Studio. All rights reserved.
  6. *
  7. * Created by Manoël Trapier on 01/03/2021.
  8. */
  9. #include <stdio.h>
  10. #include <stdint.h>
  11. #include <stdbool.h>
  12. #include <libc.h>
  13. #include <SDL.h>
  14. #include <log.h>
  15. #include <3dengine.h>
  16. #include <array.h>
  17. #include <matrix.h>
  18. #include <triangle.h>
  19. #include <display.h>
  20. #include <vector.h>
  21. #include <mesh.h>
  22. #include <light.h>
  23. #include <camera.h>
  24. #include <clipping.h>
  25. bool isRunning = true;
  26. int previousFrameTime = 0;
  27. double deltaTime = 0;
  28. typedef enum displayMode_t
  29. {
  30. DISPLAY_MODE_WIREFRAME,
  31. DISPLAY_MODE_SOLID,
  32. DISPLAY_MODE_SOLID_WIRE,
  33. DISPLAY_MODE_TEXTURED,
  34. DISPLAY_MODE_TEXTURED_WIRE,
  35. } displayMode_t;
  36. bool displayVertex = false;
  37. bool doNotCull = false;
  38. bool doZBuffer = true;
  39. bool doClipping = true;
  40. bool doPerspectiveCorrection = true;
  41. static mesh_t worldMesh[10];
  42. static int meshCount = 0;
  43. static int windowHeight;
  44. static int windowWidth;
  45. static camera_t camera;
  46. static light_t globalLight;
  47. static enum displayMode_t currentDisplayMode = DISPLAY_MODE_TEXTURED;
  48. static matrix4_t projectionMatrix;
  49. static double updateTime, renderTime, waitTime;
  50. static int trianglesCount, trianglesTotal;
  51. static triangle_t *projectedTriangles = NULL;
  52. static const vec3_t origin = {0, 0, 0 };
  53. void loadNewMesh(const char *obj, const char *tex)
  54. {
  55. /* Load 3D model */
  56. loadOBJFile(&worldMesh[meshCount],obj);
  57. /* Load texture */
  58. loadTextureDateFromPng(&worldMesh[meshCount].texture, tex);
  59. meshCount++;
  60. }
  61. void setup()
  62. {
  63. int i;
  64. double FOV = 60;
  65. double aspectRatioY = (double)windowHeight / windowWidth;
  66. double aspectRatioX = (double)windowWidth / windowHeight;
  67. double zNear = 1;
  68. double zFar = 20;
  69. projectionMatrix = mat4Perspective(aspectRatioY, FOV, zNear, zFar);
  70. initFrustumPlanes(FOV, aspectRatioX, zNear, zFar);
  71. createLight(&globalLight, vec3(0, 0, 1));
  72. /* Load object */
  73. loadNewMesh("assets/runway.obj", "assets/runway.png");
  74. worldMesh[0].scale = vec3(1, 1, 1);
  75. worldMesh[0].translation = vec3(0, -1.5, 23);
  76. worldMesh[0].rotation = vec3(0, 0, 0);
  77. loadNewMesh("assets/f22.obj", "assets/f22.png");
  78. worldMesh[1].scale = vec3(1, 1, 1);
  79. worldMesh[1].translation = vec3(0, -1.3, 5);
  80. worldMesh[1].rotation = vec3(0, -M_PI/2., 0);
  81. loadNewMesh("assets/efa.obj", "assets/efa.png");
  82. worldMesh[2].scale = vec3(1, 1, 1);
  83. worldMesh[2].translation = vec3(-2, -1.3, 9);
  84. worldMesh[2].rotation = vec3(0, -M_PI/2., 0);
  85. loadNewMesh("assets/f117.obj", "assets/f117.png");
  86. worldMesh[3].scale = vec3(1, 1, 1);
  87. worldMesh[3].translation = vec3(2, -1.3, 9);
  88. worldMesh[3].rotation = vec3(0, -M_PI/2., 0);
  89. trianglesTotal = 0;
  90. for(i = 0; i < meshCount; i++)
  91. {
  92. trianglesTotal += arrayGetSize(worldMesh[i].faces);
  93. }
  94. createCamera(&camera);
  95. cameraSetTarget(&camera, vec3(0, 0, 1));
  96. cameraSetUp(&camera, vec3(0, 1, 0));
  97. }
  98. void processInput()
  99. {
  100. SDL_Event event;
  101. while(SDL_PollEvent(&event))
  102. {
  103. switch (event.type)
  104. {
  105. case SDL_QUIT:isRunning = false;
  106. break;
  107. case SDL_KEYDOWN:
  108. switch (event.key.keysym.sym)
  109. {
  110. case SDLK_1:
  111. displayVertex = true;
  112. currentDisplayMode = DISPLAY_MODE_WIREFRAME;
  113. break;
  114. case SDLK_2:
  115. displayVertex = false;
  116. currentDisplayMode = DISPLAY_MODE_WIREFRAME;
  117. break;
  118. case SDLK_3:
  119. displayVertex = false;
  120. currentDisplayMode = DISPLAY_MODE_SOLID;
  121. break;
  122. case SDLK_4:
  123. displayVertex = false;
  124. currentDisplayMode = DISPLAY_MODE_SOLID_WIRE;
  125. break;
  126. case SDLK_5:
  127. displayVertex = false;
  128. currentDisplayMode = DISPLAY_MODE_TEXTURED;
  129. break;
  130. case SDLK_6:
  131. displayVertex = false;
  132. currentDisplayMode = DISPLAY_MODE_TEXTURED_WIRE;
  133. break;
  134. case SDLK_ESCAPE: isRunning = false; break;
  135. case SDLK_u: doNotCull = false; break;
  136. case SDLK_j: doNotCull = true; break;
  137. case SDLK_k: doPerspectiveCorrection = false; break;
  138. case SDLK_i: doPerspectiveCorrection = true; break;
  139. case SDLK_o: doZBuffer = true; break;
  140. case SDLK_l: doZBuffer = false; break;
  141. case SDLK_y: doClipping = true; break;
  142. case SDLK_h: doClipping = false; break;
  143. case SDLK_w: cameraMoveForward(&camera, 5 * deltaTime); break;
  144. case SDLK_d: cameraRotateBy(&camera, 1.0 * deltaTime, 0); break;
  145. case SDLK_s: cameraMoveBackward(&camera, 5 * deltaTime); break;
  146. case SDLK_a: cameraRotateBy(&camera, -1.0 * deltaTime, 0); break;
  147. case SDLK_q: cameraRotateBy(&camera, 0, -1.0 * deltaTime); break;
  148. case SDLK_e: cameraRotateBy(&camera, 0, 1.0 * deltaTime); break;
  149. case SDLK_UP: cameraMoveRelative(&camera, 0, 3.0 * deltaTime, 0); break;
  150. case SDLK_DOWN: cameraMoveRelative(&camera, 0, -3.0 * deltaTime, 0); break;
  151. }
  152. break;
  153. default:
  154. break;
  155. }
  156. }
  157. }
  158. uint32_t getMicroSecTime()
  159. {
  160. struct timeval curTime;
  161. gettimeofday(&curTime, NULL);
  162. return ((curTime.tv_sec) * 1000 * 1000) + (curTime.tv_usec);
  163. }
  164. static void projectTriangle(triangle_t *workTriangle, vec3_t *triangleNormal, texture_t *texture, colour_t colour)
  165. {
  166. int j;
  167. struct triangle_t currentTriangle = *workTriangle;
  168. for (j = 0 ; j < 3 ; j++)
  169. {
  170. vec4_t projected = mat4ProdVec4Project(projectionMatrix, workTriangle->points[j]);
  171. projected.x *= windowWidth / 2.;
  172. projected.y *= windowHeight / 2.;
  173. /* Screen Y axis increase down, 3D world Y grows up, so we need to invert. */
  174. projected.y *= -1.0;
  175. projected.x += (windowWidth / 2.);
  176. projected.y += (windowHeight / 2.);
  177. currentTriangle.points[j].x = projected.x;
  178. currentTriangle.points[j].y = projected.y;
  179. currentTriangle.points[j].w = projected.w;
  180. currentTriangle.points[j].z = projected.z;
  181. }
  182. if (doNotCull)
  183. {
  184. currentTriangle.colour = colour;
  185. }
  186. else
  187. {
  188. double lightLevel = -vec3Dot(*triangleNormal, globalLight.direction);
  189. currentTriangle.colour = applyLight(colour, lightLevel);
  190. }
  191. currentTriangle.averageDepth = (workTriangle->points[0].z +
  192. workTriangle->points[1].z +
  193. workTriangle->points[2].z) / 3.0;
  194. currentTriangle.texture = texture;
  195. arrayAdd(projectedTriangles, currentTriangle);
  196. }
  197. static void processMesh(mesh_t *mesh, matrix4_t *worldMatrix)
  198. {
  199. uint32_t i, j;
  200. uint32_t faceCount;
  201. uint32_t numberOfClippedTriangles;
  202. triangle_t trianglesAfterClipping[MAX_NUM_POLYGON_TRIANGLES];
  203. faceCount = arrayGetSize(mesh->faces);
  204. matrix4_t transformMatrix = mat4Identity;
  205. transformMatrix = mat4ProdMat4(mat4Scale(mesh->scale.x, mesh->scale.y, mesh->scale.z), transformMatrix);
  206. transformMatrix = mat4ProdMat4(mat4RotationZ(mesh->rotation.z), transformMatrix);
  207. transformMatrix = mat4ProdMat4(mat4RotationY(mesh->rotation.y), transformMatrix);
  208. transformMatrix = mat4ProdMat4(mat4RotationX(mesh->rotation.x), transformMatrix);
  209. transformMatrix = mat4ProdMat4(mat4Translate(mesh->translation.x, mesh->translation.y, mesh->translation.z), transformMatrix);
  210. transformMatrix = mat4ProdMat4(*worldMatrix, transformMatrix);
  211. for(i = 0; i < faceCount; i++)
  212. {
  213. polygon_t polygon;
  214. vec4_t transformedVertices[3];
  215. vec3_t triangleNormal;
  216. vec3_t vertices[3] =
  217. {
  218. mesh->vertices[mesh->faces[i].a],
  219. mesh->vertices[mesh->faces[i].b],
  220. mesh->vertices[mesh->faces[i].c],
  221. };
  222. for(j = 0; j < 3; j++)
  223. {
  224. transformedVertices[j] = vec4FromVec3(vertices[j]);
  225. transformedVertices[j] = mat4ProdVec4(transformMatrix, transformedVertices[j]);
  226. }
  227. triangleNormal = getTriangleNormal(transformedVertices);
  228. if (!doNotCull)
  229. {
  230. vec3_t cameraRay = vec3SubVectors(origin, vec3FromVec4(transformedVertices[0]));
  231. vec3Normalize(&cameraRay);
  232. double cameraDotTriangle = vec3Dot(triangleNormal, cameraRay);
  233. if (cameraDotTriangle < 0)
  234. {
  235. continue;
  236. }
  237. }
  238. if (doClipping)
  239. {
  240. polygon = createPolygonFromTriangle(vec3FromVec4(transformedVertices[0]),
  241. vec3FromVec4(transformedVertices[1]),
  242. vec3FromVec4(transformedVertices[2]),
  243. mesh->faces[i].a_uv,
  244. mesh->faces[i].b_uv,
  245. mesh->faces[i].c_uv);
  246. clipPolygon(&polygon);
  247. createTriangleFromPolygon(&polygon, trianglesAfterClipping, &numberOfClippedTriangles);
  248. for(j = 0; j < numberOfClippedTriangles; j++)
  249. {
  250. projectTriangle(&trianglesAfterClipping[j], &triangleNormal, &mesh->texture, mesh->faces[i].colour);
  251. }
  252. }
  253. else
  254. {
  255. triangle_t workTriangle =
  256. {
  257. .points =
  258. {
  259. [0] = transformedVertices[0],
  260. [1] = transformedVertices[1],
  261. [2] = transformedVertices[2]
  262. },
  263. .textureCoordinates =
  264. {
  265. [0] = { mesh->faces[i].a_uv.u, mesh->faces[i].a_uv.v },
  266. [1] = { mesh->faces[i].b_uv.u, mesh->faces[i].b_uv.v },
  267. [2] = { mesh->faces[i].c_uv.u, mesh->faces[i].c_uv.v }
  268. }
  269. };
  270. projectTriangle(&workTriangle, &triangleNormal, &mesh->texture, mesh->faces[i].colour);
  271. };
  272. }
  273. }
  274. void update()
  275. {
  276. int i;
  277. uint32_t timeToWait = FRAME_TARGET_TIME - (SDL_GetTicks() - previousFrameTime);
  278. matrix4_t worldMatrix;
  279. uint32_t ticks = getMicroSecTime();
  280. if (timeToWait <= FRAME_TARGET_TIME)
  281. {
  282. SDL_Delay(timeToWait);
  283. }
  284. deltaTime = (SDL_GetTicks() - previousFrameTime) / 1000.0;
  285. previousFrameTime = SDL_GetTicks();
  286. waitTime = (waitTime + (getMicroSecTime() - ticks) / 1000.) / 2.0;
  287. ticks = getMicroSecTime();
  288. worldMatrix = cameraGetViewMatrix(&camera);
  289. arrayEmpty(projectedTriangles);
  290. for(i = 0; i < meshCount; i++)
  291. {
  292. processMesh(&worldMesh[i], &worldMatrix);
  293. }
  294. if (!doZBuffer)
  295. {
  296. trianglesCount = arrayGetSize(projectedTriangles);
  297. qsort(projectedTriangles, trianglesCount, sizeof(triangle_t), compareTrianglesZOrder);
  298. }
  299. updateTime = (updateTime + (getMicroSecTime() - ticks) / 1000.) / 2.0;
  300. }
  301. void render()
  302. {
  303. int i;
  304. uint32_t ticks = getMicroSecTime();
  305. clearFrameBuffer(MAKE_RGB(0, 0, 0));
  306. clearZBuffer();
  307. drawGrid(20, MAKE_RGB(27, 2, 27));
  308. int triangleCount = arrayGetSize(projectedTriangles);
  309. for(i = 0; i < triangleCount; i++)
  310. {
  311. switch(currentDisplayMode)
  312. {
  313. case DISPLAY_MODE_SOLID:
  314. case DISPLAY_MODE_SOLID_WIRE:
  315. drawFilledTriangle(&projectedTriangles[i]);
  316. break;
  317. case DISPLAY_MODE_TEXTURED:
  318. case DISPLAY_MODE_TEXTURED_WIRE:
  319. drawTextureTriangle(&projectedTriangles[i]);
  320. break;
  321. default:
  322. break;
  323. }
  324. switch (currentDisplayMode)
  325. {
  326. case DISPLAY_MODE_SOLID_WIRE:
  327. case DISPLAY_MODE_TEXTURED_WIRE:
  328. case DISPLAY_MODE_WIREFRAME:
  329. drawTriangle(projectedTriangles[i].points[0].x, projectedTriangles[i].points[0].y,
  330. projectedTriangles[i].points[1].x, projectedTriangles[i].points[1].y,
  331. projectedTriangles[i].points[2].x, projectedTriangles[i].points[2].y,
  332. projectedTriangles[i].colour);
  333. break;
  334. default:
  335. break;
  336. }
  337. if (displayVertex)
  338. {
  339. int j;
  340. for(j = 0; j < 3; j++)
  341. {
  342. drawRectangle(projectedTriangles[i].points[j].x - 1,
  343. projectedTriangles[i].points[j].y - 1,
  344. 3, 3,
  345. MAKE_RGB(255, 0, 0));
  346. }
  347. }
  348. }
  349. renderFrameBuffer();
  350. renderTime = (renderTime + (getMicroSecTime() - ticks) / 1000.) / 2.0;
  351. drawText(5, 5, 0x11FF22, "Wait time: %02.2f ms", waitTime);
  352. drawText(5, 17, 0x11FF22, "Update time: %02.2f ms", updateTime);
  353. drawText(5, 29, 0x11FF22, "Render time: %02.2f ms", renderTime);
  354. drawText(5, 41, 0x11FF22, "FPS: %.1f | dT: %.3f", 1000 / (waitTime + updateTime + renderTime), deltaTime);
  355. drawText(5, 53, 0x11FF22, "Triangles: %d / %d", triangleCount, trianglesTotal);
  356. drawText(5, 65, 0x11FF22, "Cull: %c | PeCo: %c | ZB: %c, | CP: %c",
  357. doNotCull?'N':'Y', doPerspectiveCorrection ? 'Y' : 'N', doZBuffer ? 'Y' : 'N', doClipping ? 'Y' : 'N');
  358. displayWindowRender();
  359. }
  360. int main(int argc, char *argv[])
  361. {
  362. int param_i, i;
  363. bool fullScreen = false;
  364. MAX_DEBUG_LEVEL = TLOG_DEBUG;
  365. Log(TLOG_ALWAYS, NULL, "Booting 3D Engine (version %s)!", VERSION);
  366. windowWidth = 320;
  367. windowHeight = 240;
  368. for (param_i = 1 ; (param_i < argc) && (argv[param_i][0] == '-') ; param_i++)
  369. {
  370. switch (argv[param_i][1])
  371. {
  372. default: exit(-1); /* Option not recognized */
  373. case 'f': fullScreen = true; break;
  374. case 'w': windowWidth = atoi(argv[++param_i]); break;
  375. case 'h': windowHeight = atoi(argv[++param_i]); break;
  376. #ifdef DYNA_LOG_LEVEL
  377. case 'l': MAX_DEBUG_LEVEL = atoi(argv[++param_i]); break;
  378. #endif
  379. case '-': goto no_more_params; /* Could use break, but this is more clear */
  380. }
  381. }
  382. no_more_params:
  383. isRunning = initialiseWindow(windowWidth, windowHeight, fullScreen);
  384. setup();
  385. while (isRunning)
  386. {
  387. processInput();
  388. update();
  389. render();
  390. }
  391. arrayFree(projectedTriangles);
  392. for(i = 0; i < meshCount; i++)
  393. {
  394. textureCleanup(&worldMesh[i].texture);
  395. meshFree(&worldMesh[i]);
  396. }
  397. destroyWindow();
  398. return 0;
  399. }