kit/
lib.rs

1//! A foundational layer for gamedev designed to be a starting point for jams.
2//! Draws inspiration from ggez and Love2d, but can do 3D rendering.
3
4#![allow(dead_code)]
5#![allow(unused)]
6
7pub mod geometry;
8pub mod graphics;
9mod math;
10
11// re-exporting for convenient importing by consumers
12pub use math::*;
13
14// re-exporting for convenient obfuscation - I may replace sokol_app with winit
15pub use sokol::app::SAppDesc as KAppDesc;
16pub use sokol::app::SAppKeycode as Keycode;
17
18use sokol::app::*;
19use sokol::gfx::*;
20
21// ----------------------------------------------------------------------------
22// drawing structures and utils
23
24// pub(crate) const BYTES_MODEL_BUFF_V (size_of::<MeshVert>() * MAX_MODEL_VERTS)
25// pub(crate) const BYTES_MODEL_BUFF_I (size_of::<u32>() * MAX_MODEL_VERTS)
26pub(crate) const MAX_QUADS: usize = 4000;
27pub(crate) const MAX_POINTS: usize = 15000;
28pub(crate) const MAX_LINES: usize = 1000;
29pub(crate) const MAX_IMAGES: usize = 100;
30pub(crate) const MAX_MESHES: usize = 200;
31
32#[derive(Default, Copy, Clone)]
33pub struct Texture {
34  pub id: usize,
35  pub w: u32,
36  pub h: u32,
37}
38
39// TODO move to game layer?
40#[derive(Default, Copy, Clone)]
41pub struct TextureFrameDesc {
42  pub x: u32,
43  pub y: u32,
44  pub w: u32,
45  pub h: u32,
46  pub offset: V2,
47}
48
49/// Defines a subset of a texture that can be drawn. Can be used to define the
50/// placement of a single sprite within a spritesheet.
51///
52/// Internally, stores uv coordinates related to the texture, so access to the
53/// loaded texture must exist so that the full width and height of the texture
54/// can be known. Note that uv coordinates are relative to the lower left corner
55/// of the image.
56///
57/// TODO (wesh) consider whether it would be better to remove direct tie to the
58/// texture_id in order to allow the same sprite dimensions to be reused with
59/// different (equally sized) images.
60#[derive(Default, Clone, Copy)]
61pub struct Sprite {
62  pub(crate) img_id: usize,
63  pub(crate) corners: QuadCorners,
64}
65
66/// Primarily used for images, this expresses a point within the
67/// image that will be aligned to the image's position coordinates
68/// and which will be the center of any scaling or rotation applied
69/// to the image.
70///
71/// Pivot point coordinates, like Sprites and Quad uvs, are relative
72/// to the lower-left corner of the Sprite in question.
73#[derive(Copy, Clone)]
74pub enum Pivot {
75  Center,
76  Px(V2),
77  Percent(V2),
78}
79
80#[derive(Default, Copy, Clone)]
81pub(crate) struct DrawPoint {
82  pub pos: V3,
83  pub color: V4,
84}
85
86impl DrawPoint {
87  pub fn new(x: f32, y: f32, z: f32, color: V4) -> DrawPoint {
88    let pos = V3 { x, y, z };
89    DrawPoint { pos, color }
90  }
91}
92
93#[derive(Default, Copy, Clone)]
94pub(crate) struct DrawLine {
95  pub point_a: V3,
96  pub color_a: V4,
97  pub point_b: V3,
98  pub color_b: V4, // TODO do I *really* need gradient lines?
99}
100
101#[derive(Default, Copy, Clone)]
102pub(crate) struct QuadVert {
103  pub pos: V3,
104  pub uv: V2,
105}
106
107impl QuadVert {
108  pub fn new(x: f32, y: f32, z: f32, uvx: f32, uvy: f32) -> QuadVert {
109    let pos = V3 { x, y, z };
110    let uv = V2 { x: uvx, y: uvy };
111    QuadVert { pos, uv }
112  }
113}
114
115pub(crate) type QuadCorners = [QuadVert; 4];
116
117#[derive(Default, Copy, Clone)]
118pub(crate) struct DrawQuad {
119  pub img_id: usize,
120  pub corners: QuadCorners,
121  pub transform: M4,
122}
123
124#[derive(Default, Clone, Copy)]
125pub(crate) struct DrawMesh {
126  pub mesh_i: usize,
127  pub transform: M4,
128}
129
130#[derive(Default)]
131pub(crate) struct GlShape {
132  pub pipeline: SgPipeline,
133  pub bindings: SgBindings,
134}
135
136#[derive(Default, Copy, Clone)]
137pub(crate) struct Image {
138  pub(crate) e: SgImage,
139  pub(crate) w: u32,
140  pub(crate) h: u32,
141}
142
143pub(crate) struct ImagesCtx {
144  e: [Image; MAX_IMAGES],
145  count: usize,
146}
147
148impl Default for ImagesCtx {
149  fn default() -> Self {
150    Self {
151      e: [Default::default(); MAX_IMAGES],
152      count: 0,
153    }
154  }
155}
156
157pub(crate) struct QuadsCtx {
158  pub(crate) shape: GlShape,
159  pub(crate) e: [DrawQuad; MAX_QUADS],
160  pub(crate) count: usize,
161}
162
163impl Default for QuadsCtx {
164  fn default() -> Self {
165    Self {
166      shape: Default::default(),
167      e: [Default::default(); MAX_QUADS],
168      count: Default::default(),
169    }
170  }
171}
172
173pub(crate) struct PointsCtx {
174  pub(crate) shape: GlShape,
175  pub(crate) e: [DrawPoint; MAX_POINTS],
176  pub(crate) count: usize,
177}
178
179impl Default for PointsCtx {
180  fn default() -> Self {
181    Self {
182      shape: Default::default(),
183      e: [Default::default(); MAX_POINTS],
184      count: Default::default(),
185    }
186  }
187}
188
189pub(crate) struct LinesCtx {
190  pub(crate) shape: GlShape,
191  pub(crate) e: [DrawLine; MAX_LINES],
192  pub(crate) count: usize,
193}
194
195impl Default for LinesCtx {
196  fn default() -> Self {
197    Self {
198      shape: Default::default(),
199      e: [Default::default(); MAX_LINES],
200      count: Default::default(),
201    }
202  }
203}
204
205pub(crate) struct MeshCtx {
206  pub(crate) shape: GlShape,
207  pub(crate) e: [DrawMesh; MAX_MESHES],
208  pub(crate) count: usize,
209}
210
211impl Default for MeshCtx {
212  fn default() -> Self {
213    Self {
214      shape: Default::default(),
215      e: [Default::default(); MAX_MESHES],
216      count: Default::default(),
217    }
218  }
219}
220
221#[derive(Default)]
222pub struct GraphicsCtx {
223  pub bg: V3,
224  pub proj: M4,
225  pub view: M4,
226  pub(crate) view_proj: M4,
227  //
228  pub(crate) quads: QuadsCtx,
229  pub(crate) points: PointsCtx,
230  pub(crate) lines: LinesCtx,
231  pub(crate) images: ImagesCtx,
232  pub(crate) mesh: MeshCtx,
233  //
234  pub(crate) pass_action: SgPassAction,
235}
236
237#[derive(Default)]
238pub struct InputCtx {
239  pub l_stick: V2,
240  pub r_stick: V2,
241  pub quit: bool,
242  pub dir_u: bool,
243  pub dir_d: bool,
244  pub dir_l: bool,
245  pub dir_r: bool,
246  pub action_pressed: bool,
247  pub action_released: bool,
248  pub mouse_wheel_y: f32,
249  pub mouse_pos: V2,
250  pub mouse_prev_pos: V2,
251}
252
253// TODO should arrays in here be Vec<T> instead? Heap instead of stack?
254
255pub struct Ctx {
256  pub input: InputCtx,
257  pub gl: GraphicsCtx,
258}
259
260impl Default for Ctx {
261  fn default() -> Self {
262    Self {
263      input: Default::default(),
264      gl: Default::default(),
265    }
266  }
267}
268
269// ----------------------------------------------------------------------------
270// Lifecycle
271
272struct App<K: KApp> {
273  ctx: Ctx,
274  app: K,
275}
276
277pub trait KApp: 'static + Sized {
278  fn new() -> Self;
279  fn init(&mut self, ctx: &mut Ctx);
280  fn frame(&mut self, ctx: &mut Ctx);
281}
282
283impl<K: KApp> SApp for App<K> {
284  fn sapp_init(&mut self) {
285    let ctx = &mut self.ctx;
286    graphics::init(ctx);
287    self.app.init(ctx);
288  }
289
290  fn sapp_frame(&mut self) {
291    let ctx = &mut self.ctx;
292    ctx.gl.view_proj = ctx.gl.proj * ctx.gl.view;
293    self.app.frame(ctx);
294    ctx.input.mouse_wheel_y = 0.0;
295    ctx.input.mouse_prev_pos = ctx.input.mouse_pos;
296    graphics::present(ctx);
297  }
298
299  fn sapp_cleanup(&mut self) {
300    std::process::exit(0);
301  }
302
303  fn sapp_event(&mut self, event: SAppEvent) {
304    let ctx = &mut self.ctx;
305    // check for system exit shortcut
306    if event.event_type == SAppEventType::KeyDown
307      && event.modifiers.contains(SAppModifier::SUPER)
308      && (event.key_code == SAppKeycode::KeyW || event.key_code == SAppKeycode::KeyQ)
309    {
310      std::process::exit(0)
311    }
312
313    // TODO... sapp for events vs sdl? how do I handle gamepad input?
314    match event.event_type {
315      SAppEventType::MouseMove => {
316        ctx.input.mouse_pos = v2(event.mouse_x, event.mouse_y);
317      }
318      SAppEventType::MouseScroll => {
319        ctx.input.mouse_wheel_y += event.scroll_y;
320      }
321      SAppEventType::KeyDown => match event.key_code {
322        SAppKeycode::KeyW => ctx.input.dir_u = true,
323        SAppKeycode::KeyA => ctx.input.dir_l = true,
324        SAppKeycode::KeyD => ctx.input.dir_r = true,
325        SAppKeycode::KeyS => ctx.input.dir_d = true,
326        _ => {}
327      },
328      SAppEventType::KeyUp => match event.key_code {
329        SAppKeycode::KeyW => ctx.input.dir_u = false,
330        SAppKeycode::KeyA => ctx.input.dir_l = false,
331        SAppKeycode::KeyD => ctx.input.dir_r = false,
332        SAppKeycode::KeyS => ctx.input.dir_d = false,
333        _ => {}
334      },
335
336      _ => {}
337    }
338  }
339}
340
341pub fn run<K: KApp>(desc: KAppDesc) {
342  let ctx: Ctx = Default::default();
343  let app: K = K::new();
344  sapp_run(App { ctx, app }, desc);
345}
346
347// ----------------------------------------------------------------------------
348// Gamepad input
349
350// TODO test these
351// const AXIS_MIN: i16 = 4;
352// const AXIS_MAX: i32 = 1000;
353
354// fn get_normalized_gamepad_axis_value(controller: GameController, axis: Axis) -> f32 {
355//   let value = controller.axis(axis);
356//   let magnitude = value.abs();
357//   if magnitude < AXIS_MIN {
358//     return 0.0;
359//   }
360//   let normalized_magnitude = ((magnitude - AXIS_MIN) as f32) / (AXIS_MAX as f32);
361//   if value > 0 {
362//     normalized_magnitude
363//   } else {
364//     -normalized_magnitude
365//   }
366// }