main.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. 
  2. // TODO:
  3. // Test with multiple displays and high DPI mode.
  4. #include "../../DFPSR/includeFramework.h"
  5. using namespace dsr;
  6. // Get the application folder when possible, falling back on current directory on systems not offering the feature.
  7. static const String applicationFolder = file_getApplicationFolder();
  8. static const String mediaFolder = file_combinePaths(applicationFolder, U"media");
  9. static BasicResourcePool pool(mediaFolder);
  10. // Global variables
  11. static bool running = true;
  12. static double cameraYaw = 0.0f;
  13. static double cameraPitch = 0.0f;
  14. static FVector3D cameraPosition = FVector3D(0.0f, 0.0f, 0.0f);
  15. static bool showCursor = false;
  16. // The window handle
  17. static Window window;
  18. static int createRoomPart(Model model, const FVector3D &min, const FVector3D &max) {
  19. // Add positions
  20. model_addPoint(model, FVector3D(min.x, min.y, min.z)); // 0: Left-down-near
  21. model_addPoint(model, FVector3D(min.x, min.y, max.z)); // 1: Left-down-far
  22. model_addPoint(model, FVector3D(min.x, max.y, min.z)); // 2: Left-up-near
  23. model_addPoint(model, FVector3D(min.x, max.y, max.z)); // 3: Left-up-far
  24. model_addPoint(model, FVector3D(max.x, min.y, min.z)); // 4: Right-down-near
  25. model_addPoint(model, FVector3D(max.x, min.y, max.z)); // 5: Right-down-far
  26. model_addPoint(model, FVector3D(max.x, max.y, min.z)); // 6: Right-up-near
  27. model_addPoint(model, FVector3D(max.x, max.y, max.z)); // 7: Right-up-far
  28. // Create a part for the polygons
  29. int part = model_addEmptyPart(model, U"cube");
  30. // Polygons using default texture coordinates on the 4 corners of the texture
  31. model_addQuad(model, part, 1, 0, 2, 3); // Left quad
  32. model_addQuad(model, part, 4, 5, 7, 6); // Right quad
  33. model_addQuad(model, part, 0, 4, 6, 2); // Front quad
  34. model_addQuad(model, part, 5, 1, 3, 7); // Back quad
  35. model_addQuad(model, part, 2, 6, 7, 3); // Top quad
  36. model_addQuad(model, part, 1, 5, 4, 0); // Bottom quad
  37. return part;
  38. }
  39. static Model createRoomModel(const FVector3D &min, const FVector3D &max) {
  40. Model result = model_create();
  41. createRoomPart(result, min, max);
  42. return result;
  43. }
  44. static IVector2D cursorOrigin;
  45. static int32_t cursorLimitX = 10;
  46. static int32_t cursorLimitY = 10;
  47. static IVector2D previousCursorPosition;
  48. static bool cursorWasReset = false;
  49. static bool firstMouseEvent = true;
  50. static bool moveForward = false;
  51. static bool moveBackward = false;
  52. static bool moveUp = false;
  53. static bool moveDown = false;
  54. static bool moveLeft = false;
  55. static bool moveRight = false;
  56. static bool moveFaster = false;
  57. // The maximum camera pitch in both positive and negative direction.
  58. // Goes a bit outside of 90 degrees (1.57079633 radians) just to show that it is possible when calculating the up vector instead of hardcoding it to a constant.
  59. static const double maxPitch = 1.8;
  60. // Room coordinates.
  61. static const FVector3D roomMinimum = FVector3D(-10.0f);
  62. static const FVector3D roomMaximum = FVector3D(10.0f);
  63. static const float cameraCollisionRadius = 1.0f;
  64. static const FVector3D cameraMinimum = roomMinimum + cameraCollisionRadius;
  65. static const FVector3D cameraMaximum = roomMaximum - cameraCollisionRadius;
  66. DSR_MAIN_CALLER(dsrMain)
  67. void dsrMain(List<String> args) {
  68. // Create a full-screen window
  69. window = window_create_fullscreen(U"David Piuva's Software Renderer - Camera example");
  70. // Hide the cursor
  71. window_setCursorVisibility(window, false);
  72. // Tell the application to terminate when the window is closed
  73. window_setCloseEvent(window, []() {
  74. running = false;
  75. });
  76. // Get whole window key events
  77. window_setKeyboardEvent(window, [](const KeyboardEvent& event) {
  78. DsrKey key = event.dsrKey;
  79. if (event.keyboardEventType == KeyboardEventType::KeyDown) {
  80. if (key >= DsrKey_1 && key <= DsrKey_9) {
  81. // TODO: Create a smooth transition between resolutions.
  82. window_setPixelScale(window, key - DsrKey_0);
  83. } else if (key == DsrKey_F11) {
  84. window_setFullScreen(window, !window_isFullScreen(window));
  85. window_setCursorVisibility(window, !window_isFullScreen(window));
  86. } else if (key == DsrKey_Escape) {
  87. running = false;
  88. } else if (key == DsrKey_C) {
  89. // Press C to toggle visibility the cursor and debug drawing.
  90. showCursor = !showCursor;
  91. window_setCursorVisibility(window, showCursor);
  92. } else if (key == DsrKey_W) {
  93. moveForward = true;
  94. } else if (key == DsrKey_S) {
  95. moveBackward = true;
  96. } else if (key == DsrKey_E) {
  97. moveUp = true;
  98. } else if (key == DsrKey_Q) {
  99. moveDown = true;
  100. } else if (key == DsrKey_A) {
  101. moveLeft = true;
  102. } else if (key == DsrKey_D) {
  103. moveRight = true;
  104. } else if (key == DsrKey_Shift) {
  105. moveFaster = true;
  106. }
  107. } else if (event.keyboardEventType == KeyboardEventType::KeyUp) {
  108. if (key == DsrKey_W) {
  109. moveForward = false;
  110. } else if (key == DsrKey_S) {
  111. moveBackward = false;
  112. } else if (key == DsrKey_E) {
  113. moveUp = false;
  114. } else if (key == DsrKey_Q) {
  115. moveDown = false;
  116. } else if (key == DsrKey_A) {
  117. moveLeft = false;
  118. } else if (key == DsrKey_D) {
  119. moveRight = false;
  120. } else if (key == DsrKey_Shift) {
  121. moveFaster = false;
  122. }
  123. }
  124. });
  125. // Get whole window mouse events
  126. window_setMouseEvent(window, [](const MouseEvent& event) {
  127. if (firstMouseEvent) {
  128. // Ignore motion from the first mouse event, because it has no previous cursor position to compare against.
  129. firstMouseEvent = false;
  130. } else {
  131. IVector2D movement = event.position - previousCursorPosition;
  132. IVector2D offset = event.position - cursorOrigin;
  133. if (cursorWasReset && offset.x == 0 && offset.y == 0) {
  134. // The first cursor at the image center after a reset is ignored.
  135. cursorWasReset = false;
  136. } else {
  137. // TODO: Adjust mouse sensitivity somehow.
  138. double radiansPerCanvasPixel = 0.005 * double(window_getPixelScale(window));
  139. cameraYaw += double(movement.x) * radiansPerCanvasPixel;
  140. cameraPitch -= double(movement.y) * radiansPerCanvasPixel;
  141. if (cameraPitch > maxPitch) cameraPitch = maxPitch;
  142. if (cameraPitch < -maxPitch) cameraPitch = -maxPitch;
  143. if (offset.x < -cursorLimitX || offset.y < -cursorLimitY || offset.x > cursorLimitX || offset.y > cursorLimitY) {
  144. // The cursor traveled outside of the box, so it is moved to the center.
  145. if (window_setCursorPosition(window, cursorOrigin.x, cursorOrigin.y)) {
  146. // If successful, remember that the cursor was reset, so that the next mouse move event going to the center can be ignored.
  147. cursorWasReset = true;
  148. }
  149. }
  150. }
  151. }
  152. previousCursorPosition = event.position;
  153. });
  154. // Create a room model
  155. Model roomModel = createRoomModel(roomMinimum, roomMaximum);
  156. model_setDiffuseMapByName(roomModel, 0, pool, "Grid");
  157. // Create a renderer for multi-threading
  158. Renderer worker = renderer_create();
  159. double lastTime = 0.0;
  160. while(running) {
  161. // Measure time
  162. double time = time_getSeconds();
  163. double timePerFrame = time - lastTime;
  164. // Fetch mouse and keyboard events from the window.
  165. window_executeEvents(window);
  166. // Request buffers after executing the events, to get newly allocated buffers after resize events
  167. ImageRgbaU8 colorBuffer = window_getCanvas(window);
  168. // For a sky box, the depth buffer can be replaced with ImageF32() as an empty image handle.
  169. //ImageF32 depthBuffer;
  170. // But for demonstration purposes, we render the room using a depth buffer.
  171. ImageF32 depthBuffer = window_getDepthBuffer(window);
  172. // Get target size
  173. int targetWidth = image_getWidth(colorBuffer);
  174. int targetHeight = image_getHeight(colorBuffer);
  175. // Reset the mouse to the center of the canvas when getting too far out.
  176. cursorOrigin = IVector2D(targetWidth / 2, targetHeight / 2);
  177. cursorLimitX = targetWidth / 4;
  178. cursorLimitY = targetHeight / 4;
  179. // No need to paint the background for indoor scenes, because everything will be covered by geometry.
  180. // Any gap will be undefined behavior because some window backends use double buffering while others do not.
  181. // One way to make sure that this does not happen, is to let the debug version clear the color with a flashing color while looking for holes.
  182. //image_fill(colorBuffer, ColorRgbaI32(0, 0, 0, 0));
  183. // Clear the depth buffer
  184. image_fill(depthBuffer, 0.0f); // Infinite reciprocal depth using zero
  185. // Calculate camera orientation from pitch and yaw in radians.
  186. FVector3D cameraForwardDirection = FVector3D(sin(cameraYaw) * cos(cameraPitch), sin(cameraPitch), cos(cameraYaw) * cos(cameraPitch));
  187. FVector3D cameraUpDirection = FVector3D(-sin(cameraYaw) * sin(cameraPitch), cos(cameraPitch), -cos(cameraYaw) * sin(cameraPitch));
  188. FMatrix3x3 cameraRotation = FMatrix3x3::makeAxisSystem(cameraForwardDirection, cameraUpDirection);
  189. Camera camera = Camera::createPerspective(Transform3D(cameraPosition, cameraRotation), targetWidth, targetHeight);
  190. // Move the camera.
  191. double speed = moveFaster ? 40.0 : 10.0;
  192. double moveOffset = speed * timePerFrame;
  193. if (moveForward) cameraPosition = cameraPosition + cameraForwardDirection * moveOffset;
  194. if (moveBackward) cameraPosition = cameraPosition - cameraForwardDirection * moveOffset;
  195. if (moveUp) cameraPosition = cameraPosition + cameraUpDirection * moveOffset;
  196. if (moveDown) cameraPosition = cameraPosition - cameraUpDirection * moveOffset;
  197. if (moveLeft) cameraPosition = cameraPosition - cameraRotation.xAxis * moveOffset;
  198. if (moveRight) cameraPosition = cameraPosition + cameraRotation.xAxis * moveOffset;
  199. // Collide against walls.
  200. if (cameraPosition.x < cameraMinimum.x) cameraPosition.x = cameraMinimum.x;
  201. if (cameraPosition.y < cameraMinimum.y) cameraPosition.y = cameraMinimum.y;
  202. if (cameraPosition.z < cameraMinimum.z) cameraPosition.z = cameraMinimum.z;
  203. if (cameraPosition.x > cameraMaximum.x) cameraPosition.x = cameraMaximum.x;
  204. if (cameraPosition.y > cameraMaximum.y) cameraPosition.y = cameraMaximum.y;
  205. if (cameraPosition.z > cameraMaximum.z) cameraPosition.z = cameraMaximum.z;
  206. // Begin render batch
  207. renderer_begin(worker, colorBuffer, depthBuffer);
  208. // Projected triangles from the room's model
  209. renderer_giveTask(worker, roomModel, Transform3D(), camera);
  210. // Render the projected triangles
  211. renderer_end(worker);
  212. // Debug draw the camera rotation system, which is toggled using the C button.
  213. if (showCursor) {
  214. IVector2D writer = IVector2D(10, 10);
  215. font_printLine(colorBuffer, font_getDefault(), string_combine(U"cameraYaw = ", cameraYaw, U" radians"), writer, ColorRgbaI32(255, 255, 255, 255)); writer.y += 20;
  216. font_printLine(colorBuffer, font_getDefault(), string_combine(U"cameraPitch = ", cameraPitch, U" radians"), writer, ColorRgbaI32(255, 255, 255, 255)); writer.y += 20;
  217. font_printLine(colorBuffer, font_getDefault(), string_combine(U"forward = ", cameraForwardDirection), writer, ColorRgbaI32(255, 255, 255, 255)); writer.y += 20;
  218. font_printLine(colorBuffer, font_getDefault(), string_combine(U"up = ", cameraUpDirection), writer, ColorRgbaI32(255, 255, 255, 255)); writer.y += 20;
  219. // Draw the region that the cursor can move within without jumping to the center.
  220. int32_t left = cursorOrigin.x - cursorLimitX;
  221. int32_t right = cursorOrigin.x + cursorLimitX;
  222. int32_t top = cursorOrigin.y - cursorLimitY;
  223. int32_t bottom = cursorOrigin.y + cursorLimitY;
  224. draw_line(colorBuffer, left, top, right, top, ColorRgbaI32(255, 255, 255, 255));
  225. draw_line(colorBuffer, left, bottom, right, bottom, ColorRgbaI32(255, 255, 255, 255));
  226. draw_line(colorBuffer, left, top, left, bottom, ColorRgbaI32(255, 255, 255, 255));
  227. draw_line(colorBuffer, right, top, right, bottom, ColorRgbaI32(255, 255, 255, 255));
  228. } else {
  229. int32_t crosshairRadius = cursorLimitY / 16;
  230. int32_t left = cursorOrigin.x - crosshairRadius;
  231. int32_t right = cursorOrigin.x + crosshairRadius;
  232. int32_t top = cursorOrigin.y - crosshairRadius;
  233. int32_t bottom = cursorOrigin.y + crosshairRadius;
  234. draw_line(colorBuffer, left, cursorOrigin.y, right, cursorOrigin.y, ColorRgbaI32(255, 255, 255, 255));
  235. draw_line(colorBuffer, cursorOrigin.x, top, cursorOrigin.x, bottom, ColorRgbaI32(255, 255, 255, 255));
  236. }
  237. // Upload canvas to window.
  238. window_showCanvas(window);
  239. lastTime = time;
  240. }
  241. }