@bornengine/engine 0.4.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (213) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +231 -0
  3. package/native/android/Cargo.lock +1848 -0
  4. package/native/android/Cargo.toml +24 -0
  5. package/native/android/src/lib.rs +702 -0
  6. package/native/ios/Cargo.lock +1690 -0
  7. package/native/ios/Cargo.toml +32 -0
  8. package/native/ios/src/lib.rs +1267 -0
  9. package/native/linux/Cargo.lock +3279 -0
  10. package/native/linux/Cargo.toml +29 -0
  11. package/native/linux/src/lib.rs +1331 -0
  12. package/native/macos/Cargo.lock +3310 -0
  13. package/native/macos/Cargo.toml +46 -0
  14. package/native/macos/src/lib.rs +1302 -0
  15. package/native/shared/Cargo.lock +1899 -0
  16. package/native/shared/Cargo.toml +62 -0
  17. package/native/shared/assets/default_font.ttf +0 -0
  18. package/native/shared/build.rs +270 -0
  19. package/native/shared/shaders/common/clouds.wgsl +122 -0
  20. package/native/shared/shaders/common/fog.wgsl +16 -0
  21. package/native/shared/shaders/common/foliage_wind.wgsl +98 -0
  22. package/native/shared/shaders/common/imposter.wgsl +112 -0
  23. package/native/shared/shaders/common/pbr.wgsl +186 -0
  24. package/native/shared/shaders/common/shadows.wgsl +186 -0
  25. package/native/shared/shaders/common/sky.wgsl +8 -0
  26. package/native/shared/shaders/common/tonemap.wgsl +25 -0
  27. package/native/shared/shaders/impulse_field.wgsl +57 -0
  28. package/native/shared/shaders/material_abi.wgsl +383 -0
  29. package/native/shared/shaders/materials/test_minimal.wgsl +42 -0
  30. package/native/shared/src/anim_mixer.rs +61 -0
  31. package/native/shared/src/attach.rs +263 -0
  32. package/native/shared/src/audio/decode.rs +123 -0
  33. package/native/shared/src/audio/mod.rs +863 -0
  34. package/native/shared/src/audio/render.rs +892 -0
  35. package/native/shared/src/audio/spsc.rs +156 -0
  36. package/native/shared/src/audio/stream.rs +226 -0
  37. package/native/shared/src/custom_shaders.rs +104 -0
  38. package/native/shared/src/decals.rs +245 -0
  39. package/native/shared/src/drs.rs +211 -0
  40. package/native/shared/src/engine.rs +261 -0
  41. package/native/shared/src/ffi.rs +116 -0
  42. package/native/shared/src/ffi_core/assets.rs +388 -0
  43. package/native/shared/src/ffi_core/audio_ffi.rs +184 -0
  44. package/native/shared/src/ffi_core/draw.rs +334 -0
  45. package/native/shared/src/ffi_core/game_loop.rs +577 -0
  46. package/native/shared/src/ffi_core/input.rs +234 -0
  47. package/native/shared/src/ffi_core/mod.rs +127 -0
  48. package/native/shared/src/ffi_core/models.rs +1154 -0
  49. package/native/shared/src/ffi_core/ragdoll_ffi.rs +261 -0
  50. package/native/shared/src/ffi_core/scene.rs +626 -0
  51. package/native/shared/src/ffi_core/vfx.rs +212 -0
  52. package/native/shared/src/ffi_core/visual.rs +691 -0
  53. package/native/shared/src/frame_callbacks.rs +122 -0
  54. package/native/shared/src/geometry.rs +236 -0
  55. package/native/shared/src/handles.rs +182 -0
  56. package/native/shared/src/input.rs +448 -0
  57. package/native/shared/src/jolt_sys.rs +822 -0
  58. package/native/shared/src/lib.rs +55 -0
  59. package/native/shared/src/models.rs +1093 -0
  60. package/native/shared/src/models_gltf.rs +1280 -0
  61. package/native/shared/src/particles.rs +391 -0
  62. package/native/shared/src/physics_jolt.rs +1908 -0
  63. package/native/shared/src/picking.rs +298 -0
  64. package/native/shared/src/postfx.rs +345 -0
  65. package/native/shared/src/profiler.rs +492 -0
  66. package/native/shared/src/ragdoll.rs +474 -0
  67. package/native/shared/src/renderer/atmosphere_lut.rs +573 -0
  68. package/native/shared/src/renderer/brdf_lut.rs +154 -0
  69. package/native/shared/src/renderer/draw2d.rs +143 -0
  70. package/native/shared/src/renderer/formats.rs +822 -0
  71. package/native/shared/src/renderer/froxel.rs +421 -0
  72. package/native/shared/src/renderer/gi_bake.rs +653 -0
  73. package/native/shared/src/renderer/graph.rs +462 -0
  74. package/native/shared/src/renderer/hiz.rs +269 -0
  75. package/native/shared/src/renderer/hot_reload.rs +390 -0
  76. package/native/shared/src/renderer/impulse_field.rs +456 -0
  77. package/native/shared/src/renderer/lighting.rs +154 -0
  78. package/native/shared/src/renderer/material_instancing.rs +171 -0
  79. package/native/shared/src/renderer/material_pipeline.rs +700 -0
  80. package/native/shared/src/renderer/material_system.rs +1996 -0
  81. package/native/shared/src/renderer/material_system_tests.rs +601 -0
  82. package/native/shared/src/renderer/material_system_wasm.rs +41 -0
  83. package/native/shared/src/renderer/mod.rs +12556 -0
  84. package/native/shared/src/renderer/model_draw.rs +641 -0
  85. package/native/shared/src/renderer/occlusion.rs +429 -0
  86. package/native/shared/src/renderer/planar_pass.rs +593 -0
  87. package/native/shared/src/renderer/planar_reflection.rs +499 -0
  88. package/native/shared/src/renderer/post_pass.rs +249 -0
  89. package/native/shared/src/renderer/postfx_chain.rs +728 -0
  90. package/native/shared/src/renderer/pt_pass.rs +577 -0
  91. package/native/shared/src/renderer/scene_pass.rs +607 -0
  92. package/native/shared/src/renderer/shader_include.rs +205 -0
  93. package/native/shared/src/renderer/shader_library.rs +135 -0
  94. package/native/shared/src/renderer/shaders/ao.rs +570 -0
  95. package/native/shared/src/renderer/shaders/core.rs +1243 -0
  96. package/native/shared/src/renderer/shaders/env.rs +907 -0
  97. package/native/shared/src/renderer/shaders/gi.rs +810 -0
  98. package/native/shared/src/renderer/shaders/mod.rs +19 -0
  99. package/native/shared/src/renderer/shaders/post.rs +1558 -0
  100. package/native/shared/src/renderer/shaders/pt.rs +1859 -0
  101. package/native/shared/src/renderer/shaders/ssgi.rs +1586 -0
  102. package/native/shared/src/renderer/shadow_pass.rs +731 -0
  103. package/native/shared/src/renderer/ssgi_pass.rs +392 -0
  104. package/native/shared/src/renderer/ssr_pass.rs +188 -0
  105. package/native/shared/src/renderer/texture_store.rs +473 -0
  106. package/native/shared/src/renderer/transient.rs +591 -0
  107. package/native/shared/src/renderer/types.rs +941 -0
  108. package/native/shared/src/renderer/util.rs +152 -0
  109. package/native/shared/src/scene.rs +1362 -0
  110. package/native/shared/src/sdf_cache.rs +274 -0
  111. package/native/shared/src/shadows.rs +1036 -0
  112. package/native/shared/src/staging.rs +102 -0
  113. package/native/shared/src/string_header.rs +266 -0
  114. package/native/shared/src/text_renderer.rs +502 -0
  115. package/native/shared/src/textures.rs +197 -0
  116. package/native/tvos/Cargo.lock +1693 -0
  117. package/native/tvos/Cargo.toml +36 -0
  118. package/native/tvos/metal-patched/Cargo.toml +178 -0
  119. package/native/tvos/metal-patched/LICENSE-APACHE +201 -0
  120. package/native/tvos/metal-patched/LICENSE-MIT +25 -0
  121. package/native/tvos/metal-patched/src/acceleration_structure.rs +667 -0
  122. package/native/tvos/metal-patched/src/acceleration_structure_pass.rs +108 -0
  123. package/native/tvos/metal-patched/src/argument.rs +366 -0
  124. package/native/tvos/metal-patched/src/blitpass.rs +102 -0
  125. package/native/tvos/metal-patched/src/buffer.rs +71 -0
  126. package/native/tvos/metal-patched/src/capturedescriptor.rs +76 -0
  127. package/native/tvos/metal-patched/src/capturemanager.rs +113 -0
  128. package/native/tvos/metal-patched/src/commandbuffer.rs +192 -0
  129. package/native/tvos/metal-patched/src/commandqueue.rs +44 -0
  130. package/native/tvos/metal-patched/src/computepass.rs +107 -0
  131. package/native/tvos/metal-patched/src/constants.rs +152 -0
  132. package/native/tvos/metal-patched/src/counters.rs +119 -0
  133. package/native/tvos/metal-patched/src/depthstencil.rs +190 -0
  134. package/native/tvos/metal-patched/src/device.rs +2134 -0
  135. package/native/tvos/metal-patched/src/drawable.rs +39 -0
  136. package/native/tvos/metal-patched/src/encoder.rs +2041 -0
  137. package/native/tvos/metal-patched/src/heap.rs +281 -0
  138. package/native/tvos/metal-patched/src/indirect_encoder.rs +344 -0
  139. package/native/tvos/metal-patched/src/lib.rs +657 -0
  140. package/native/tvos/metal-patched/src/library.rs +902 -0
  141. package/native/tvos/metal-patched/src/mps.rs +575 -0
  142. package/native/tvos/metal-patched/src/pipeline/compute.rs +475 -0
  143. package/native/tvos/metal-patched/src/pipeline/mod.rs +71 -0
  144. package/native/tvos/metal-patched/src/pipeline/render.rs +762 -0
  145. package/native/tvos/metal-patched/src/renderpass.rs +443 -0
  146. package/native/tvos/metal-patched/src/resource.rs +182 -0
  147. package/native/tvos/metal-patched/src/sampler.rs +165 -0
  148. package/native/tvos/metal-patched/src/sync.rs +178 -0
  149. package/native/tvos/metal-patched/src/texture.rs +352 -0
  150. package/native/tvos/metal-patched/src/types.rs +90 -0
  151. package/native/tvos/metal-patched/src/vertexdescriptor.rs +250 -0
  152. package/native/tvos/src/audio_backend.rs +197 -0
  153. package/native/tvos/src/lib.rs +1891 -0
  154. package/native/visionos/Cargo.lock +1693 -0
  155. package/native/visionos/Cargo.toml +40 -0
  156. package/native/visionos/src/audio_backend.rs +197 -0
  157. package/native/visionos/src/lib.rs +1887 -0
  158. package/native/watchos/Cargo.lock +16 -0
  159. package/native/watchos/Cargo.toml +19 -0
  160. package/native/watchos/shaders/bloom_postfx.metal +99 -0
  161. package/native/watchos/src/BloomWatchApp.swift +1267 -0
  162. package/native/watchos/src/BloomWatchAudio.swift +179 -0
  163. package/native/watchos/src/audio.rs +55 -0
  164. package/native/watchos/src/draw_list.rs +229 -0
  165. package/native/watchos/src/ffi_stubs.rs +915 -0
  166. package/native/watchos/src/ffi_stubs_manual.rs +35 -0
  167. package/native/watchos/src/lib.rs +1124 -0
  168. package/native/watchos/src/models.rs +746 -0
  169. package/native/watchos/src/postfx.rs +95 -0
  170. package/native/watchos/src/scene.rs +534 -0
  171. package/native/watchos/src/textures.rs +184 -0
  172. package/native/web/Cargo.lock +1657 -0
  173. package/native/web/Cargo.toml +43 -0
  174. package/native/web/bloom_glue.js +695 -0
  175. package/native/web/build.sh +131 -0
  176. package/native/web/index.html +35 -0
  177. package/native/web/jolt_bridge.js +1519 -0
  178. package/native/web/src/input_ffi.rs +286 -0
  179. package/native/web/src/lib.rs +1796 -0
  180. package/native/web/src/material_ffi.rs +710 -0
  181. package/native/web/src/parity_ffi.rs +343 -0
  182. package/native/web/src/physics_ffi.rs +643 -0
  183. package/native/web/src/ragdoll_ffi.rs +250 -0
  184. package/native/web/src/render_settings.rs +98 -0
  185. package/native/windows/Cargo.lock +1815 -0
  186. package/native/windows/Cargo.toml +68 -0
  187. package/native/windows/src/lib.rs +1486 -0
  188. package/package.json +4279 -0
  189. package/src/audio/index.ts +315 -0
  190. package/src/core/colors.ts +63 -0
  191. package/src/core/index.ts +1206 -0
  192. package/src/core/keys.ts +63 -0
  193. package/src/core/types.ts +104 -0
  194. package/src/index.ts +171 -0
  195. package/src/math/index.ts +516 -0
  196. package/src/mobile/index.ts +294 -0
  197. package/src/models/index.ts +1258 -0
  198. package/src/physics/index.ts +1134 -0
  199. package/src/scene/index.ts +698 -0
  200. package/src/shapes/index.ts +120 -0
  201. package/src/text/index.ts +48 -0
  202. package/src/textures/index.ts +187 -0
  203. package/src/vfx/index.ts +191 -0
  204. package/src/world/index.ts +24 -0
  205. package/src/world/loader.ts +423 -0
  206. package/src/world/prefab.ts +217 -0
  207. package/src/world/render.ts +172 -0
  208. package/src/world/saver.ts +108 -0
  209. package/src/world/serialize.ts +301 -0
  210. package/src/world/terrain.ts +355 -0
  211. package/src/world/types.ts +160 -0
  212. package/src/world/validate.ts +319 -0
  213. package/src/world/version.ts +114 -0
@@ -0,0 +1,448 @@
1
+ /// Platform-agnostic input state.
2
+
3
+ const MAX_KEYS: usize = 512;
4
+ const MAX_MOUSE_BUTTONS: usize = 8;
5
+ const MAX_GAMEPAD_AXES: usize = 8;
6
+ const MAX_GAMEPAD_BUTTONS: usize = 16;
7
+ const MAX_TOUCH_POINTS: usize = 10;
8
+
9
+ pub struct TouchPoint {
10
+ pub x: f64,
11
+ pub y: f64,
12
+ pub active: bool,
13
+ }
14
+
15
+ pub struct InputState {
16
+ keys_pressed: [bool; MAX_KEYS],
17
+ keys_down: [bool; MAX_KEYS],
18
+ keys_released: [bool; MAX_KEYS],
19
+ prev_keys_down: [bool; MAX_KEYS],
20
+
21
+ // Injected keys (bloom_inject_key_down/up) are staged here and applied at the
22
+ // top of begin_frame, rather than written straight into keys_down.
23
+ //
24
+ // They HAVE to be, and the reason is the frame order: a real key arrives in
25
+ // poll_events(), which runs BEFORE begin_frame(), so the edge
26
+ // (keys_down && !prev_keys_down) is computed for it. An injected key is set
27
+ // from the game loop — AFTER begin_frame — so end_frame() copied it into
28
+ // prev_keys_down before begin_frame ever got to look at it, and the edge was
29
+ // never computed at all. is_key_down() saw the key; is_key_pressed() never
30
+ // did. That silently made the injection API useless for anything
31
+ // edge-triggered, which is to say every menu and every UI in every game
32
+ // built on this engine.
33
+ pending_key_down: [bool; MAX_KEYS],
34
+ pending_key_up: [bool; MAX_KEYS],
35
+
36
+ pub mouse_x: f64,
37
+ pub mouse_y: f64,
38
+ pub mouse_delta_x: f64,
39
+ pub mouse_delta_y: f64,
40
+ prev_mouse_x: f64,
41
+ prev_mouse_y: f64,
42
+ raw_delta_x: f64,
43
+ raw_delta_y: f64,
44
+ /// Scroll wheel accumulator. Platform event loops call `accumulate_mouse_wheel`
45
+ /// when a scroll event arrives; games/editors call `consume_mouse_wheel` once
46
+ /// per frame to read the total delta (and reset it to 0).
47
+ mouse_wheel_y: f64,
48
+ /// Digital Crown rotation accumulator (watchOS). Platforms push rotation
49
+ /// deltas via `accumulate_crown_rotation`; games call `consume_crown_rotation`
50
+ /// once per frame. Units are radians, positive = clockwise (away from user).
51
+ crown_rotation: f64,
52
+ /// Character input ring buffer for text-entry support (E3b). Platform
53
+ /// event loops push Unicode codepoints here on key-down; editors pop them
54
+ /// one at a time via `pop_char`.
55
+ char_queue: [u32; 32],
56
+ char_queue_head: usize,
57
+ char_queue_tail: usize,
58
+ /// Cursor shape requested by the editor. The platform event loop polls
59
+ /// this and applies the appropriate native cursor.
60
+ /// 0=default, 1=hand, 2=move, 3=text, 4=resize_h, 5=resize_v, 6=crosshair
61
+ pub cursor_shape: u32,
62
+ pub cursor_disabled: bool,
63
+ mouse_pressed: [bool; MAX_MOUSE_BUTTONS],
64
+ mouse_down: [bool; MAX_MOUSE_BUTTONS],
65
+ mouse_released: [bool; MAX_MOUSE_BUTTONS],
66
+ prev_mouse_down: [bool; MAX_MOUSE_BUTTONS],
67
+ // Platform event loops can drain both halves of a click before the next
68
+ // frame begins. Keep the transitions independently so neither edge is
69
+ // lost when the final button state is released again.
70
+ pending_mouse_pressed: [bool; MAX_MOUSE_BUTTONS],
71
+ pending_mouse_released: [bool; MAX_MOUSE_BUTTONS],
72
+
73
+ // Gamepad
74
+ pub gamepad_available: bool,
75
+ gamepad_axes: [f32; MAX_GAMEPAD_AXES],
76
+ gamepad_buttons_down: [bool; MAX_GAMEPAD_BUTTONS],
77
+ gamepad_buttons_pressed: [bool; MAX_GAMEPAD_BUTTONS],
78
+ gamepad_buttons_released: [bool; MAX_GAMEPAD_BUTTONS],
79
+ prev_gamepad_buttons: [bool; MAX_GAMEPAD_BUTTONS],
80
+ pub gamepad_axis_count: usize,
81
+ /// EN-031 rumble: (low-frequency motor, high-frequency motor, seconds
82
+ /// left), all 0 = motors off. The FFI writes this; each platform's input
83
+ /// poll drives its own vibration API from it and decrements the timer.
84
+ /// Keeping the *state* shared and only the driving per-platform is what
85
+ /// stops the six platform crates from drifting again.
86
+ pub rumble: [f32; 3],
87
+
88
+ // Touch
89
+ pub touch_points: [TouchPoint; MAX_TOUCH_POINTS],
90
+ pub touch_count: usize,
91
+ // Release-deferral: an ACTION_UP that arrives in the same frame as its
92
+ // ACTION_DOWN would otherwise zero out `touch_count` before the game has a
93
+ // chance to poll. Mark the slot here instead and clear at end_frame, so a
94
+ // fast tap is still visible for one full frame.
95
+ touch_pending_release: [bool; MAX_TOUCH_POINTS],
96
+
97
+ // OS key auto-repeat, surfaced as its OWN edge (isKeyRepeated) so that
98
+ // isKeyPressed semantics never change for games — a held jump key must
99
+ // not machine-gun. Text/caret navigation (arrows, Home/End, Delete) is
100
+ // what consumes these. The platform layer queues repeats as they arrive;
101
+ // begin_frame publishes them for exactly one frame.
102
+ keys_repeated: [bool; MAX_KEYS],
103
+ repeat_pending: [bool; MAX_KEYS],
104
+ }
105
+
106
+ impl InputState {
107
+ pub fn new() -> Self {
108
+ const EMPTY_TOUCH: TouchPoint = TouchPoint { x: 0.0, y: 0.0, active: false };
109
+ Self {
110
+ keys_pressed: [false; MAX_KEYS],
111
+ keys_down: [false; MAX_KEYS],
112
+ keys_released: [false; MAX_KEYS],
113
+ prev_keys_down: [false; MAX_KEYS],
114
+ pending_key_down: [false; MAX_KEYS],
115
+ pending_key_up: [false; MAX_KEYS],
116
+ mouse_x: 0.0,
117
+ mouse_y: 0.0,
118
+ mouse_delta_x: 0.0,
119
+ mouse_delta_y: 0.0,
120
+ prev_mouse_x: 0.0,
121
+ prev_mouse_y: 0.0,
122
+ raw_delta_x: 0.0,
123
+ raw_delta_y: 0.0,
124
+ mouse_wheel_y: 0.0,
125
+ crown_rotation: 0.0,
126
+ char_queue: [0; 32],
127
+ char_queue_head: 0,
128
+ char_queue_tail: 0,
129
+ cursor_shape: 0,
130
+ cursor_disabled: false,
131
+ mouse_pressed: [false; MAX_MOUSE_BUTTONS],
132
+ mouse_down: [false; MAX_MOUSE_BUTTONS],
133
+ mouse_released: [false; MAX_MOUSE_BUTTONS],
134
+ prev_mouse_down: [false; MAX_MOUSE_BUTTONS],
135
+ pending_mouse_pressed: [false; MAX_MOUSE_BUTTONS],
136
+ pending_mouse_released: [false; MAX_MOUSE_BUTTONS],
137
+ gamepad_available: false,
138
+ gamepad_axes: [0.0; MAX_GAMEPAD_AXES],
139
+ gamepad_buttons_down: [false; MAX_GAMEPAD_BUTTONS],
140
+ gamepad_buttons_pressed: [false; MAX_GAMEPAD_BUTTONS],
141
+ gamepad_buttons_released: [false; MAX_GAMEPAD_BUTTONS],
142
+ prev_gamepad_buttons: [false; MAX_GAMEPAD_BUTTONS],
143
+ gamepad_axis_count: 0,
144
+ rumble: [0.0; 3],
145
+ touch_points: [EMPTY_TOUCH; MAX_TOUCH_POINTS],
146
+ touch_count: 0,
147
+ touch_pending_release: [false; MAX_TOUCH_POINTS],
148
+ keys_repeated: [false; MAX_KEYS],
149
+ repeat_pending: [false; MAX_KEYS],
150
+ }
151
+ }
152
+
153
+ /// Platform layer: an OS auto-repeat arrived for `key`. Published as a
154
+ /// one-frame isKeyRepeated edge by the next begin_frame.
155
+ pub fn queue_key_repeat(&mut self, key: usize) {
156
+ if key < MAX_KEYS {
157
+ self.repeat_pending[key] = true;
158
+ }
159
+ }
160
+
161
+ pub fn is_key_repeated(&self, key: usize) -> bool {
162
+ key < MAX_KEYS && self.keys_repeated[key]
163
+ }
164
+
165
+ pub fn begin_frame(&mut self) {
166
+ // Apply injected keys BEFORE the edge computation below, so an injection
167
+ // made during the previous frame reads exactly like a real key press:
168
+ // down this frame, not-down last frame => keys_pressed. See the note on
169
+ // pending_key_down.
170
+ for i in 0..MAX_KEYS {
171
+ if self.pending_key_down[i] {
172
+ self.keys_down[i] = true;
173
+ self.pending_key_down[i] = false;
174
+ }
175
+ if self.pending_key_up[i] {
176
+ self.keys_down[i] = false;
177
+ self.pending_key_up[i] = false;
178
+ }
179
+ }
180
+ if self.cursor_disabled {
181
+ self.mouse_delta_x = self.raw_delta_x;
182
+ self.mouse_delta_y = self.raw_delta_y;
183
+ self.raw_delta_x = 0.0;
184
+ self.raw_delta_y = 0.0;
185
+ } else {
186
+ self.mouse_delta_x = self.mouse_x - self.prev_mouse_x;
187
+ self.mouse_delta_y = self.mouse_y - self.prev_mouse_y;
188
+ }
189
+ for i in 0..MAX_KEYS {
190
+ self.keys_pressed[i] = self.keys_down[i] && !self.prev_keys_down[i];
191
+ self.keys_released[i] = !self.keys_down[i] && self.prev_keys_down[i];
192
+ self.keys_repeated[i] = self.repeat_pending[i];
193
+ self.repeat_pending[i] = false;
194
+ }
195
+ for i in 0..MAX_MOUSE_BUTTONS {
196
+ self.mouse_pressed[i] =
197
+ self.pending_mouse_pressed[i] || (self.mouse_down[i] && !self.prev_mouse_down[i]);
198
+ self.mouse_released[i] =
199
+ self.pending_mouse_released[i] || (!self.mouse_down[i] && self.prev_mouse_down[i]);
200
+ self.pending_mouse_pressed[i] = false;
201
+ self.pending_mouse_released[i] = false;
202
+ }
203
+ for i in 0..MAX_GAMEPAD_BUTTONS {
204
+ self.gamepad_buttons_pressed[i] = self.gamepad_buttons_down[i] && !self.prev_gamepad_buttons[i];
205
+ self.gamepad_buttons_released[i] = !self.gamepad_buttons_down[i] && self.prev_gamepad_buttons[i];
206
+ }
207
+ }
208
+
209
+ pub fn end_frame(&mut self) {
210
+ self.prev_keys_down = self.keys_down;
211
+ self.prev_mouse_down = self.mouse_down;
212
+ self.prev_gamepad_buttons = self.gamepad_buttons_down;
213
+ self.prev_mouse_x = self.mouse_x;
214
+ self.prev_mouse_y = self.mouse_y;
215
+ let mut flushed = false;
216
+ for i in 0..MAX_TOUCH_POINTS {
217
+ if self.touch_pending_release[i] {
218
+ self.touch_points[i].active = false;
219
+ self.touch_pending_release[i] = false;
220
+ flushed = true;
221
+ }
222
+ }
223
+ if flushed {
224
+ self.touch_count = self.touch_points.iter().filter(|t| t.active).count();
225
+ }
226
+ }
227
+
228
+ // Keyboard
229
+ /// Real key events, delivered from the platform's message pump. These run
230
+ /// before begin_frame(), so writing keys_down directly is correct here.
231
+ pub fn set_key_down(&mut self, key: usize) { if key < MAX_KEYS { self.keys_down[key] = true; } }
232
+ pub fn set_key_up(&mut self, key: usize) { if key < MAX_KEYS { self.keys_down[key] = false; } }
233
+
234
+ /// Synthetic key events (bloom_inject_key_down/up), which callers raise from
235
+ /// inside the game loop. Staged, not written — see pending_key_down.
236
+ pub fn inject_key_down(&mut self, key: usize) {
237
+ if key < MAX_KEYS { self.pending_key_down[key] = true; self.pending_key_up[key] = false; }
238
+ }
239
+ pub fn inject_key_up(&mut self, key: usize) {
240
+ if key < MAX_KEYS { self.pending_key_up[key] = true; self.pending_key_down[key] = false; }
241
+ }
242
+
243
+ pub fn is_key_pressed(&self, key: usize) -> bool { key < MAX_KEYS && self.keys_pressed[key] }
244
+ pub fn is_key_down(&self, key: usize) -> bool { key < MAX_KEYS && self.keys_down[key] }
245
+ pub fn is_key_released(&self, key: usize) -> bool { key < MAX_KEYS && self.keys_released[key] }
246
+
247
+ // Mouse
248
+ pub fn set_mouse_position(&mut self, x: f64, y: f64) { self.mouse_x = x; self.mouse_y = y; }
249
+ pub fn accumulate_mouse_delta(&mut self, dx: f64, dy: f64) { self.raw_delta_x += dx; self.raw_delta_y += dy; }
250
+ pub fn clear_mouse_delta(&mut self) { self.raw_delta_x = 0.0; self.raw_delta_y = 0.0; self.mouse_delta_x = 0.0; self.mouse_delta_y = 0.0; }
251
+ pub fn accumulate_mouse_wheel(&mut self, dy: f64) { self.mouse_wheel_y += dy; }
252
+ /// Returns the accumulated scroll delta since the last call, and resets it.
253
+ /// Games/editors call this once per frame.
254
+ pub fn consume_mouse_wheel(&mut self) -> f64 {
255
+ let v = self.mouse_wheel_y;
256
+ self.mouse_wheel_y = 0.0;
257
+ v
258
+ }
259
+ /// Push a Unicode codepoint into the character input queue. Called by
260
+ /// platform event loops when a key-down event produces a printable char.
261
+ pub fn push_char(&mut self, c: u32) {
262
+ let next = (self.char_queue_head + 1) % self.char_queue.len();
263
+ if next != self.char_queue_tail {
264
+ self.char_queue[self.char_queue_head] = c;
265
+ self.char_queue_head = next;
266
+ }
267
+ // If the queue is full, silently drop the character.
268
+ }
269
+
270
+ /// Pop the next queued character. Returns 0 when the queue is empty.
271
+ /// Called once per frame by the editor's text-input widget.
272
+ pub fn pop_char(&mut self) -> u32 {
273
+ if self.char_queue_tail == self.char_queue_head {
274
+ return 0;
275
+ }
276
+ let c = self.char_queue[self.char_queue_tail];
277
+ self.char_queue_tail = (self.char_queue_tail + 1) % self.char_queue.len();
278
+ c
279
+ }
280
+
281
+ pub fn set_mouse_button_down(&mut self, button: usize) {
282
+ if button < MAX_MOUSE_BUTTONS {
283
+ if !self.mouse_down[button] {
284
+ self.pending_mouse_pressed[button] = true;
285
+ }
286
+ self.mouse_down[button] = true;
287
+ }
288
+ }
289
+ pub fn set_mouse_button_up(&mut self, button: usize) {
290
+ if button < MAX_MOUSE_BUTTONS {
291
+ if self.mouse_down[button] {
292
+ self.pending_mouse_released[button] = true;
293
+ }
294
+ self.mouse_down[button] = false;
295
+ }
296
+ }
297
+
298
+ pub fn is_mouse_button_pressed(&self, button: usize) -> bool { button < MAX_MOUSE_BUTTONS && self.mouse_pressed[button] }
299
+ pub fn is_mouse_button_down(&self, button: usize) -> bool { button < MAX_MOUSE_BUTTONS && self.mouse_down[button] }
300
+ pub fn is_mouse_button_released(&self, button: usize) -> bool { button < MAX_MOUSE_BUTTONS && self.mouse_released[button] }
301
+
302
+ // Gamepad
303
+ pub fn set_gamepad_axis(&mut self, axis: usize, value: f32) {
304
+ if axis < MAX_GAMEPAD_AXES { self.gamepad_axes[axis] = value; }
305
+ }
306
+ pub fn set_gamepad_button_down(&mut self, button: usize) {
307
+ if button < MAX_GAMEPAD_BUTTONS { self.gamepad_buttons_down[button] = true; }
308
+ }
309
+ pub fn set_gamepad_button_up(&mut self, button: usize) {
310
+ if button < MAX_GAMEPAD_BUTTONS { self.gamepad_buttons_down[button] = false; }
311
+ }
312
+
313
+ /// Clear polled gamepad axes + buttons at the top of a hardware poll.
314
+ /// A poller only ever SETS the currently-pressed buttons/axes, so without
315
+ /// this a released button would stay `down` forever (begin_frame does not
316
+ /// reset gamepad state — the edge detector reads down-vs-prev). Call this
317
+ /// once per poll, only when a real controller is present, so it never
318
+ /// clobbers the inject path on frames with no pad connected.
319
+ pub fn reset_gamepad(&mut self) {
320
+ self.gamepad_axes = [0.0; MAX_GAMEPAD_AXES];
321
+ self.gamepad_buttons_down = [false; MAX_GAMEPAD_BUTTONS];
322
+ }
323
+
324
+ pub fn is_gamepad_available(&self) -> bool { self.gamepad_available }
325
+ pub fn get_gamepad_axis(&self, axis: usize) -> f32 {
326
+ if axis < MAX_GAMEPAD_AXES { self.gamepad_axes[axis] } else { 0.0 }
327
+ }
328
+ pub fn is_gamepad_button_pressed(&self, button: usize) -> bool {
329
+ button < MAX_GAMEPAD_BUTTONS && self.gamepad_buttons_pressed[button]
330
+ }
331
+ pub fn is_gamepad_button_down(&self, button: usize) -> bool {
332
+ button < MAX_GAMEPAD_BUTTONS && self.gamepad_buttons_down[button]
333
+ }
334
+ pub fn is_gamepad_button_released(&self, button: usize) -> bool {
335
+ button < MAX_GAMEPAD_BUTTONS && self.gamepad_buttons_released[button]
336
+ }
337
+ pub fn get_gamepad_axis_count(&self) -> usize { self.gamepad_axis_count }
338
+
339
+ // Touch
340
+ pub fn set_touch(&mut self, index: usize, x: f64, y: f64, active: bool) {
341
+ if index < MAX_TOUCH_POINTS {
342
+ self.touch_points[index] = TouchPoint { x, y, active };
343
+ if active {
344
+ self.touch_pending_release[index] = false;
345
+ }
346
+ self.touch_count = self.touch_points.iter().filter(|t| t.active).count();
347
+ }
348
+ }
349
+ /// Deferred release: keeps the slot active for the current frame and
350
+ /// clears it at `end_frame`. Call from platform event loops on ACTION_UP
351
+ /// so a DOWN+UP that falls inside one frame remains visible to the game.
352
+ pub fn release_touch(&mut self, index: usize, x: f64, y: f64) {
353
+ if index < MAX_TOUCH_POINTS {
354
+ self.touch_points[index].x = x;
355
+ self.touch_points[index].y = y;
356
+ self.touch_pending_release[index] = true;
357
+ }
358
+ }
359
+ pub fn get_touch_x(&self, index: usize) -> f64 {
360
+ if index < MAX_TOUCH_POINTS { self.touch_points[index].x } else { 0.0 }
361
+ }
362
+ pub fn get_touch_y(&self, index: usize) -> f64 {
363
+ if index < MAX_TOUCH_POINTS { self.touch_points[index].y } else { 0.0 }
364
+ }
365
+ pub fn get_touch_count(&self) -> usize { self.touch_count }
366
+ /// Whether touch *slot* `index` currently holds a live finger.
367
+ ///
368
+ /// `get_touch_count` counts active fingers, but `touch_points` is indexed
369
+ /// by slot, and slots go sparse as soon as a finger lifts out of order
370
+ /// (lift slot 0 while slot 1 is still held → count == 1, live finger at
371
+ /// slot 1). Scanning `0..get_touch_count()` therefore reads a released
372
+ /// slot's stale coordinates as if they were a live touch. Games that track
373
+ /// per-finger state must iterate `0..MAX_TOUCH_POINTS` and skip slots this
374
+ /// returns false for.
375
+ pub fn is_touch_active(&self, index: usize) -> bool {
376
+ index < MAX_TOUCH_POINTS && self.touch_points[index].active
377
+ }
378
+ pub fn max_touch_points(&self) -> usize { MAX_TOUCH_POINTS }
379
+
380
+ // Digital Crown (watchOS)
381
+ pub fn accumulate_crown_rotation(&mut self, delta: f64) { self.crown_rotation += delta; }
382
+ pub fn consume_crown_rotation(&mut self) -> f64 {
383
+ let v = self.crown_rotation;
384
+ self.crown_rotation = 0.0;
385
+ v
386
+ }
387
+
388
+ // Any input
389
+ pub fn is_any_input_pressed(&self) -> bool {
390
+ self.keys_pressed.iter().any(|&k| k)
391
+ || self.mouse_pressed.iter().any(|&m| m)
392
+ || self.gamepad_buttons_pressed.iter().any(|&g| g)
393
+ || self.touch_count > 0
394
+ }
395
+ }
396
+
397
+ #[cfg(test)]
398
+ mod tests {
399
+ use super::InputState;
400
+
401
+ #[test]
402
+ fn mouse_edges_are_published_for_one_frame() {
403
+ let mut input = InputState::new();
404
+
405
+ input.set_mouse_button_down(0);
406
+ input.begin_frame();
407
+ assert!(input.is_mouse_button_pressed(0));
408
+ assert!(input.is_mouse_button_down(0));
409
+ assert!(!input.is_mouse_button_released(0));
410
+
411
+ input.end_frame();
412
+ input.begin_frame();
413
+ assert!(!input.is_mouse_button_pressed(0));
414
+ assert!(input.is_mouse_button_down(0));
415
+ assert!(!input.is_mouse_button_released(0));
416
+
417
+ input.set_mouse_button_up(0);
418
+ input.begin_frame();
419
+ assert!(!input.is_mouse_button_pressed(0));
420
+ assert!(!input.is_mouse_button_down(0));
421
+ assert!(input.is_mouse_button_released(0));
422
+
423
+ input.end_frame();
424
+ input.begin_frame();
425
+ assert!(!input.is_mouse_button_pressed(0));
426
+ assert!(!input.is_mouse_button_down(0));
427
+ assert!(!input.is_mouse_button_released(0));
428
+ }
429
+
430
+ #[test]
431
+ fn fast_mouse_click_publishes_both_edges() {
432
+ let mut input = InputState::new();
433
+
434
+ input.set_mouse_button_down(0);
435
+ input.set_mouse_button_up(0);
436
+ input.begin_frame();
437
+
438
+ assert!(input.is_mouse_button_pressed(0));
439
+ assert!(!input.is_mouse_button_down(0));
440
+ assert!(input.is_mouse_button_released(0));
441
+
442
+ input.end_frame();
443
+ input.begin_frame();
444
+ assert!(!input.is_mouse_button_pressed(0));
445
+ assert!(!input.is_mouse_button_down(0));
446
+ assert!(!input.is_mouse_button_released(0));
447
+ }
448
+ }