1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
//! A foundational layer for gamedev designed to be a starting point for jams.
//! Draws inspiration from ggez and Love2d, but can do 3D rendering.

#![allow(dead_code)]
#![allow(unused)]

pub mod geometry;
pub mod graphics;
mod math;

// re-exporting for convenient importing by consumers
pub use math::*;

// re-exporting for convenient obfuscation - I may replace sokol_app with winit
pub use sokol::app::SAppDesc as KAppDesc;
pub use sokol::app::SAppKeycode as Keycode;

use sokol::app::*;
use sokol::gfx::*;

// ----------------------------------------------------------------------------
// drawing structures and utils

// pub(crate) const BYTES_MODEL_BUFF_V (size_of::<MeshVert>() * MAX_MODEL_VERTS)
// pub(crate) const BYTES_MODEL_BUFF_I (size_of::<u32>() * MAX_MODEL_VERTS)
pub(crate) const MAX_QUADS: usize = 4000;
pub(crate) const MAX_POINTS: usize = 15000;
pub(crate) const MAX_LINES: usize = 1000;
pub(crate) const MAX_IMAGES: usize = 100;
pub(crate) const MAX_MESHES: usize = 200;

#[derive(Default, Copy, Clone)]
pub struct Texture {
  pub id: usize,
  pub w: u32,
  pub h: u32,
}

// TODO move to game layer?
#[derive(Default, Copy, Clone)]
pub struct TextureFrameDesc {
  pub x: u32,
  pub y: u32,
  pub w: u32,
  pub h: u32,
  pub offset: V2,
}

/// Defines a subset of a texture that can be drawn. Can be used to define the
/// placement of a single sprite within a spritesheet.
///
/// Internally, stores uv coordinates related to the texture, so access to the
/// loaded texture must exist so that the full width and height of the texture
/// can be known. Note that uv coordinates are relative to the lower left corner
/// of the image.
///
/// TODO (wesh) consider whether it would be better to remove direct tie to the
/// texture_id in order to allow the same sprite dimensions to be reused with
/// different (equally sized) images.
#[derive(Default, Clone, Copy)]
pub struct Sprite {
  pub(crate) img_id: usize,
  pub(crate) corners: QuadCorners,
}

/// Primarily used for images, this expresses a point within the
/// image that will be aligned to the image's position coordinates
/// and which will be the center of any scaling or rotation applied
/// to the image.
///
/// Pivot point coordinates, like Sprites and Quad uvs, are relative
/// to the lower-left corner of the Sprite in question.
#[derive(Copy, Clone)]
pub enum Pivot {
  Center,
  Px(V2),
  Percent(V2),
}

#[derive(Default, Copy, Clone)]
pub(crate) struct DrawPoint {
  pub pos: V3,
  pub color: V4,
}

impl DrawPoint {
  pub fn new(x: f32, y: f32, z: f32, color: V4) -> DrawPoint {
    let pos = V3 { x, y, z };
    DrawPoint { pos, color }
  }
}

#[derive(Default, Copy, Clone)]
pub(crate) struct DrawLine {
  pub point_a: V3,
  pub color_a: V4,
  pub point_b: V3,
  pub color_b: V4, // TODO do I *really* need gradient lines?
}

#[derive(Default, Copy, Clone)]
pub(crate) struct QuadVert {
  pub pos: V3,
  pub uv: V2,
}

impl QuadVert {
  pub fn new(x: f32, y: f32, z: f32, uvx: f32, uvy: f32) -> QuadVert {
    let pos = V3 { x, y, z };
    let uv = V2 { x: uvx, y: uvy };
    QuadVert { pos, uv }
  }
}

pub(crate) type QuadCorners = [QuadVert; 4];

#[derive(Default, Copy, Clone)]
pub(crate) struct DrawQuad {
  pub img_id: usize,
  pub corners: QuadCorners,
  pub transform: M4,
}

#[derive(Default, Clone, Copy)]
pub(crate) struct DrawMesh {
  pub mesh_i: usize,
  pub transform: M4,
}

#[derive(Default)]
pub(crate) struct GlShape {
  pub pipeline: SgPipeline,
  pub bindings: SgBindings,
}

#[derive(Default, Copy, Clone)]
pub(crate) struct Image {
  pub(crate) e: SgImage,
  pub(crate) w: u32,
  pub(crate) h: u32,
}

pub(crate) struct ImagesCtx {
  e: [Image; MAX_IMAGES],
  count: usize,
}

impl Default for ImagesCtx {
  fn default() -> Self {
    Self {
      e: [Default::default(); MAX_IMAGES],
      count: 0,
    }
  }
}

pub(crate) struct QuadsCtx {
  pub(crate) shape: GlShape,
  pub(crate) e: [DrawQuad; MAX_QUADS],
  pub(crate) count: usize,
}

impl Default for QuadsCtx {
  fn default() -> Self {
    Self {
      shape: Default::default(),
      e: [Default::default(); MAX_QUADS],
      count: Default::default(),
    }
  }
}

pub(crate) struct PointsCtx {
  pub(crate) shape: GlShape,
  pub(crate) e: [DrawPoint; MAX_POINTS],
  pub(crate) count: usize,
}

impl Default for PointsCtx {
  fn default() -> Self {
    Self {
      shape: Default::default(),
      e: [Default::default(); MAX_POINTS],
      count: Default::default(),
    }
  }
}

pub(crate) struct LinesCtx {
  pub(crate) shape: GlShape,
  pub(crate) e: [DrawLine; MAX_LINES],
  pub(crate) count: usize,
}

impl Default for LinesCtx {
  fn default() -> Self {
    Self {
      shape: Default::default(),
      e: [Default::default(); MAX_LINES],
      count: Default::default(),
    }
  }
}

pub(crate) struct MeshCtx {
  pub(crate) shape: GlShape,
  pub(crate) e: [DrawMesh; MAX_MESHES],
  pub(crate) count: usize,
}

impl Default for MeshCtx {
  fn default() -> Self {
    Self {
      shape: Default::default(),
      e: [Default::default(); MAX_MESHES],
      count: Default::default(),
    }
  }
}

#[derive(Default)]
pub struct GraphicsCtx {
  pub bg: V3,
  pub proj: M4,
  pub view: M4,
  pub(crate) view_proj: M4,
  //
  pub(crate) quads: QuadsCtx,
  pub(crate) points: PointsCtx,
  pub(crate) lines: LinesCtx,
  pub(crate) images: ImagesCtx,
  pub(crate) mesh: MeshCtx,
  //
  pub(crate) pass_action: SgPassAction,
}

#[derive(Default)]
pub struct InputCtx {
  pub l_stick: V2,
  pub r_stick: V2,
  pub quit: bool,
  pub dir_u: bool,
  pub dir_d: bool,
  pub dir_l: bool,
  pub dir_r: bool,
  pub action_pressed: bool,
  pub action_released: bool,
  pub mouse_wheel_y: f32,
  pub mouse_pos: V2,
  pub mouse_prev_pos: V2,
}

// TODO should arrays in here be Vec<T> instead? Heap instead of stack?

pub struct Ctx {
  pub input: InputCtx,
  pub gl: GraphicsCtx,
}

impl Default for Ctx {
  fn default() -> Self {
    Self {
      input: Default::default(),
      gl: Default::default(),
    }
  }
}

// ----------------------------------------------------------------------------
// Lifecycle

struct App<K: KApp> {
  ctx: Ctx,
  app: K,
}

pub trait KApp: 'static + Sized {
  fn new() -> Self;
  fn init(&mut self, ctx: &mut Ctx);
  fn frame(&mut self, ctx: &mut Ctx);
}

impl<K: KApp> SApp for App<K> {
  fn sapp_init(&mut self) {
    let ctx = &mut self.ctx;
    graphics::init(ctx);
    self.app.init(ctx);
  }

  fn sapp_frame(&mut self) {
    let ctx = &mut self.ctx;
    ctx.gl.view_proj = ctx.gl.proj * ctx.gl.view;
    self.app.frame(ctx);
    ctx.input.mouse_wheel_y = 0.0;
    ctx.input.mouse_prev_pos = ctx.input.mouse_pos;
    graphics::present(ctx);
  }

  fn sapp_cleanup(&mut self) {
    std::process::exit(0);
  }

  fn sapp_event(&mut self, event: SAppEvent) {
    let ctx = &mut self.ctx;
    // check for system exit shortcut
    if event.event_type == SAppEventType::KeyDown
      && event.modifiers.contains(SAppModifier::SUPER)
      && (event.key_code == SAppKeycode::KeyW || event.key_code == SAppKeycode::KeyQ)
    {
      std::process::exit(0)
    }

    // TODO... sapp for events vs sdl? how do I handle gamepad input?
    match event.event_type {
      SAppEventType::MouseMove => {
        ctx.input.mouse_pos = v2(event.mouse_x, event.mouse_y);
      }
      SAppEventType::MouseScroll => {
        ctx.input.mouse_wheel_y += event.scroll_y;
      }
      SAppEventType::KeyDown => match event.key_code {
        SAppKeycode::KeyW => ctx.input.dir_u = true,
        SAppKeycode::KeyA => ctx.input.dir_l = true,
        SAppKeycode::KeyD => ctx.input.dir_r = true,
        SAppKeycode::KeyS => ctx.input.dir_d = true,
        _ => {}
      },
      SAppEventType::KeyUp => match event.key_code {
        SAppKeycode::KeyW => ctx.input.dir_u = false,
        SAppKeycode::KeyA => ctx.input.dir_l = false,
        SAppKeycode::KeyD => ctx.input.dir_r = false,
        SAppKeycode::KeyS => ctx.input.dir_d = false,
        _ => {}
      },

      _ => {}
    }
  }
}

pub fn run<K: KApp>(desc: KAppDesc) {
  let ctx: Ctx = Default::default();
  let app: K = K::new();
  sapp_run(App { ctx, app }, desc);
}

// ----------------------------------------------------------------------------
// Gamepad input

// TODO test these
// const AXIS_MIN: i16 = 4;
// const AXIS_MAX: i32 = 1000;

// fn get_normalized_gamepad_axis_value(controller: GameController, axis: Axis) -> f32 {
//   let value = controller.axis(axis);
//   let magnitude = value.abs();
//   if magnitude < AXIS_MIN {
//     return 0.0;
//   }
//   let normalized_magnitude = ((magnitude - AXIS_MIN) as f32) / (AXIS_MAX as f32);
//   if value > 0 {
//     normalized_magnitude
//   } else {
//     -normalized_magnitude
//   }
// }