summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main.c420
1 files changed, 420 insertions, 0 deletions
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..1c017f0
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,420 @@
1#include <model.h>
2
3#include <filesystem.h>
4// TODO: Update math/camera to explicitly expose ortho/perspective camera parameters,
5// not just a baked matrix.
6//#include <math/camera.h>
7#include <math/spatial3.h>
8#include <math/vec2.h>
9#include <swgfx.h>
10#include <SDL3/SDL.h>
11#include <SDL3/SDL_timer.h>
12
13#include <assert.h>
14#include <stdio.h>
15#include <stdlib.h>
16
17static constexpr int BufferWidth = 160;
18static constexpr int BufferHeight = 120;
19static constexpr sgVec2i BufferDims = (sgVec2i){.x = BufferWidth, .y = BufferHeight};
20static constexpr R Aspect = (R)BufferWidth / (R)BufferHeight;
21
22static const char* WindowTitle = "GAME";
23// Window dimensions must be an integer scaling of buffer dimensions.
24// TODO: Make window dimensions a function of DPI.
25static constexpr int WindowWidth = 640;
26static constexpr int WindowHeight = 480;
27static constexpr sgVec2i WindowDims = (sgVec2i){.x = WindowWidth, .y = WindowHeight};
28
29static const R Fovy = (R)(90 * TO_RAD);
30
31#define DEBUG_EVENT_LOOP 1
32
33#ifdef DEBUG_EVENT_LOOP
34#define EVENT_LOOP_PRINT printf
35#else
36#define EVENT_LOOP_PRINT(...)
37#endif // DEBUG_EVENT_LOOP
38
39typedef struct CameraCommand {
40 bool CameraMoveLeft : 1;
41 bool CameraMoveRight : 1;
42 bool CameraMoveForward : 1;
43 bool CameraMoveBackward : 1;
44 bool CameraSlow : 1; // When true, move more slowly.
45 bool CameraRotate : 1; // When true, subsequent mouse movements cause
46 // the camera to rotate.
47} CameraCommand;
48
49typedef struct CameraController {
50 R speed; // Camera movement speed.
51 R rotation_speed; // Controls the degree with which mouse movements rotate
52 // the camera.
53 vec2 prev_mouse_position; // Mouse position in the previous frame.
54} CameraController;
55
56typedef struct Camera {
57 Spatial3 spatial;
58 R fovy;
59 R aspect;
60 R near;
61 R far;
62} Camera;
63
64typedef struct State {
65 SDL_Window* window;
66 swgfx* gfx;
67 sgPixel* colour;
68 Model* model;
69 Camera camera;
70 CameraController camera_controller;
71 Uint64 last_tick;
72} State;
73
74static sgVec3 SgVec3FromMathVec3(vec3 v) {
75 return (sgVec3){v.x, v.y, v.z};
76}
77
78static CameraCommand CameraCommandFromInput(
79 const bool* keyboard_state, const SDL_MouseButtonFlags mouse_flags) {
80 assert(keyboard_state);
81 if (keyboard_state[SDL_SCANCODE_W]) {
82 printf("W: %d\n", keyboard_state[SDL_SCANCODE_W]);
83 }
84 return (CameraCommand){
85 .CameraMoveLeft = keyboard_state[SDL_SCANCODE_A],
86 .CameraMoveRight = keyboard_state[SDL_SCANCODE_D],
87 .CameraMoveForward = keyboard_state[SDL_SCANCODE_W],
88 .CameraMoveBackward = keyboard_state[SDL_SCANCODE_S],
89 .CameraSlow = keyboard_state[SDL_SCANCODE_LSHIFT],
90 .CameraRotate = mouse_flags & SDL_BUTTON_MASK(SDL_BUTTON_LEFT),
91 };
92}
93
94static void UpdateCamera(
95 CameraController* controller, R dt, vec2 mouse_position,
96 CameraCommand command, Camera* camera) {
97 assert(controller);
98 assert(camera);
99
100 Spatial3* cam = &camera->spatial;
101
102 // Translation.
103 const R move_x = (R)(command.CameraMoveLeft ? -1 : 0) +
104 (R)(command.CameraMoveRight ? 1 : 0);
105 const R move_y = (R)(command.CameraMoveForward ? 1 : 0) +
106 (R)(command.CameraMoveBackward ? -1 : 0);
107 const R speed_factor = command.CameraSlow ? 0.3f : 1.f;
108 const vec2 translation = vec2_scale(
109 vec2_normalize(vec2_make(move_x, move_y)),
110 controller->speed * speed_factor * dt);
111 spatial3_move_right(cam, translation.x);
112 spatial3_move_forwards(cam, translation.y);
113
114 // Rotation.
115 if (command.CameraRotate) {
116 const vec2 mouse_delta =
117 vec2_sub(mouse_position, controller->prev_mouse_position);
118
119 const vec2 rotation =
120 vec2_scale(mouse_delta, controller->rotation_speed * dt);
121
122 spatial3_global_yaw(cam, -rotation.x);
123 spatial3_pitch(cam, -rotation.y);
124 }
125
126 // Update controller state.
127 controller->prev_mouse_position = mouse_position;
128}
129
130static bool Update(State* state, R dt) {
131 assert(state);
132
133 int num_keys = 0;
134 const bool* keyboard_state = SDL_GetKeyboardState(&num_keys);
135
136 vec2 mouse = {0};
137 const SDL_MouseButtonFlags mouse_flags = SDL_GetMouseState(&mouse.x, &mouse.y);
138
139 const CameraCommand cmd = CameraCommandFromInput(keyboard_state, mouse_flags);
140 UpdateCamera(&state->camera_controller, dt, mouse, cmd, &state->camera);
141
142 return true;
143}
144
145static void RenderIndexedModel(swgfx* gfx, const IndexedModel* model) {
146 assert(gfx);
147 assert(model);
148 const sgTriIdx* tris = (const sgTriIdx*)(model->data + model->offsetTris);
149 const sgVec3* positions = (const sgVec3*)(model->data + model->offsetPositions);
150 sgTrianglesIndexedNonUniform(gfx, model->numTris, tris, positions);
151}
152
153static void RenderModel(swgfx* gfx, const Model* model) {
154 assert(gfx);
155 assert(model);
156 switch (model->type) {
157 case ModelTypeIndexed: RenderIndexedModel(gfx, &model->indexed); break;
158 case ModelTypeFlat: /* TODO: Render flat models. */ break;
159 default: assert(false); break;
160 }
161}
162
163static void RenderTriangle2d(swgfx* gfx) {
164 assert(gfx);
165 const sgVec2 p0 = (sgVec2){20, 20};
166 const sgVec2 p1 = (sgVec2){80, 20};
167 const sgVec2 p2 = (sgVec2){50, 50};
168 const sgTri2 tri = (sgTri2){p0, p1, p2};
169 sgTriangles2(gfx, 1, &tri);
170}
171
172static void Checkerboard(swgfx* gfx, int width, int height) {
173 assert(gfx);
174 const sgPixel colour = (sgPixel){255, 0, 255, 255};
175 for (int y = 0; y < height; ++y) {
176 for (int x = 0; x < width; ++x) {
177 if (((x ^ y) & 1) == 1) {
178 const sgVec2i position = (sgVec2i){x, y};
179 sgPixels(gfx, 1, &position, colour);
180 }
181 }
182 }
183}
184
185static bool Render(State* state) {
186 assert(state);
187 assert(state->window);
188 assert(state->gfx);
189
190 // Locking/unlocking SDL software surfaces is not necessary.
191 // Probably also best to avoid SDL_BlitSurface(); it does pixel format
192 // conversion while blitting one pixel at a time. Instead, make the UI pixel
193 // format match the SDL window's and write to SDL's back buffer directly.
194 SDL_Surface* window_surface = SDL_GetWindowSurface(state->window);
195 assert(window_surface);
196
197 // Until we make the window resizable, assert dimensions for safety.
198 assert(window_surface->w == WindowWidth);
199 assert(window_surface->h == WindowHeight);
200
201#ifdef DEBUG_EVENT_LOOP
202 EVENT_LOOP_PRINT(
203 "Render: window surface: %dx%d\n",
204 window_surface->w, window_surface->h);
205#endif
206
207 const Camera* cam = &state->camera;
208
209 sgColourBuffer(state->gfx, BufferDims, state->colour);
210 sgClear(state->gfx);
211 sgViewport(state->gfx, 0, 0, BufferWidth, BufferHeight);
212 sgCheck(state->gfx);
213 // TODO: For easier debugging, overlay the checkerboard on top of the
214 // other rendered items with alpha blending.
215 //Checkerboard(state->gfx, BufferWidth, BufferHeight);
216 //RenderTriangle2d(state->gfx);
217 sgModelId(state->gfx);
218 sgView(state->gfx, SgVec3FromMathVec3(cam->spatial.p), SgVec3FromMathVec3(cam->spatial.f));
219 sgPerspective(state->gfx, cam->fovy, cam->aspect, cam->near, cam->far);
220 RenderModel(state->gfx, state->model);
221 /*sgIdx indices[3] = {0, 1, 2};
222 sgVec3 positions[3] = {
223 (sgVec3){0, 0, 0},
224 (sgVec3){5, 2, 0},
225 (sgVec3){8, 8, 0},
226 };
227 sgTrianglesIndexed(state->gfx, 3, indices, positions);*/
228 sgPresent(state->gfx, WindowDims, window_surface->pixels);
229
230 if (!SDL_UpdateWindowSurface(state->window)) {
231 return false;
232 }
233
234 return true;
235}
236
237static bool Resize(State* state) {
238 assert(state);
239
240 // int width, height;
241 // SDL_GetWindowSize(state->window, &width, &height);
242
243 const SDL_Surface* window_surface = SDL_GetWindowSurface(state->window);
244 if (!window_surface) {
245 return false;
246 }
247 const int width = window_surface->w;
248 const int height = window_surface->h;
249
250 EVENT_LOOP_PRINT("Resize: %dx%d\n", width, height);
251
252 return true;
253}
254
255static bool Initialize(State* state) {
256 assert(state);
257
258 if ((state->window = SDL_CreateWindow(
259 WindowTitle,
260 WindowWidth,
261 WindowHeight,
262 0)) == NULL) {
263 fprintf(stderr, "SDL_CreateWindow failed\n");
264 return false;
265 }
266
267 if (!(state->gfx = sgNew())) {
268 fprintf(stderr, "sgNew failed\n");
269 return false;
270 }
271
272 if (!(state->colour = SG_ALIGN_ALLOC(BufferWidth * BufferHeight, sgPixel))) {
273 fprintf(stderr, "Failed to allocate colour buffer\n");
274 return false;
275 }
276
277 sgColourBuffer(state->gfx, BufferDims, state->colour);
278
279 const char* model_path = "/home/jeanne/blender/box.mdl";
280 if (!(state->model = read_file(model_path))) {
281 fprintf(stderr, "Failed to load model: [%s]\n", model_path);
282 return false;
283 }
284
285 Camera* camera = &state->camera;
286 camera->fovy = Fovy;
287 camera->aspect = Aspect;
288 camera->near = 0.1f;
289 camera->far = 1000.f;
290 camera->spatial = spatial3_make();
291 camera->spatial.p = vec3_make(0, 1, 10);
292
293 state->camera_controller = (CameraController){
294 .speed = 7.f,
295 .rotation_speed = (R)(90 * TO_RAD),
296 };
297
298 state->last_tick = SDL_GetPerformanceCounter();
299
300 return true;
301}
302
303static void Shutdown(State* state) {
304 assert(state);
305
306 if (state->model) {
307 free(state->model);
308 state->model = nullptr;
309 }
310
311 if (state->colour) {
312 SG_FREE(&state->colour);
313 }
314
315 if (state->gfx) {
316 sgDel(&state->gfx);
317 }
318
319 if (state->window) {
320 SDL_DestroyWindow(state->window);
321 state->window = nullptr;
322 }
323}
324
325static R GetDeltaTime(State* state) {
326 assert(state);
327 constexpr Uint64 NS_IN_SEC = 1'000'000'000;
328 const Uint64 this_tick = SDL_GetPerformanceCounter();
329 const Uint64 freq = SDL_GetPerformanceFrequency();
330 const Uint64 elapsed_ns = (this_tick - state->last_tick) * NS_IN_SEC / freq;
331 const R elapsed_sec = (R)elapsed_ns / (R)NS_IN_SEC;
332 state->last_tick = this_tick;
333 return elapsed_sec;
334}
335
336int main() {
337 bool success = false;
338 // Controls whether we should keep running.
339 bool running = true;
340
341 State state = {0};
342
343 if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS)) {
344 fprintf(stderr, "SDL_Init failed\n");
345 goto cleanup;
346 }
347
348 if (!Initialize(&state)) {
349 fprintf(stderr, "Initialization failed\n");
350 goto cleanup;
351 }
352
353 if (!Resize(&state)) {
354 goto cleanup;
355 }
356
357 success = true;
358
359 while (success && running) {
360 EVENT_LOOP_PRINT("loop\n");
361
362 // Handle events.
363 SDL_Event event = {0};
364 while (SDL_PollEvent(&event)) {
365 if ((event.type == SDL_EVENT_QUIT) ||
366 (event.type == SDL_EVENT_WINDOW_CLOSE_REQUESTED)) {
367 running = false;
368 break;
369 } else if ((event.window.type == SDL_EVENT_WINDOW_DISPLAY_CHANGED) ||
370 (event.window.type == SDL_EVENT_WINDOW_RESIZED) ||
371 (event.window.type == SDL_EVENT_WINDOW_MOVED)) {
372 // When the window is maximized, an SDL_WINDOWEVENT_MOVED comes in
373 // before an SDL_WINDOWEVENT_SIZE_CHANGED with the window already
374 // resized. This is unfortunate because we cannot rely on the latter
375 // event alone to handle resizing.
376 if (!Resize(&state)) {
377 success = false;
378 break;
379 }
380 } else if (event.type == SDL_EVENT_KEY_DOWN) {
381 if (event.key.mod & SDL_KMOD_LCTRL) {
382 switch (event.key.key) {
383 // Exit.
384 case SDLK_C:
385 case SDLK_D:
386 running = false;
387 break;
388 default:
389 break;
390 }
391 }
392 } else if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
393 //
394 } else if (event.type == SDL_EVENT_MOUSE_BUTTON_UP) {
395 //
396 } else if (event.type == SDL_EVENT_MOUSE_WHEEL) {
397 //
398 } else {
399 EVENT_LOOP_PRINT("event.window.type = %d\n", event.window.type);
400 }
401 } // events
402
403 // Draw and update if needed.
404 if (success && running) {
405 const R dt = GetDeltaTime(&state);
406 success = Update(&state, dt);
407 }
408 if (success && running) {
409 success = Render(&state);
410 }
411 } // loop
412
413cleanup:
414 if (!success) {
415 fprintf(stderr, "%s\n", SDL_GetError());
416 }
417 Shutdown(&state);
418 SDL_Quit();
419 return success ? 0 : 1;
420}