bevy 1.0.0

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 (93) hide show
  1. checksums.yaml +7 -0
  2. data/Cargo.lock +4279 -0
  3. data/Cargo.toml +36 -0
  4. data/README.md +226 -0
  5. data/crates/bevy/Cargo.toml +52 -0
  6. data/crates/bevy/src/app.rs +43 -0
  7. data/crates/bevy/src/component.rs +111 -0
  8. data/crates/bevy/src/entity.rs +30 -0
  9. data/crates/bevy/src/error.rs +32 -0
  10. data/crates/bevy/src/event.rs +190 -0
  11. data/crates/bevy/src/input_bridge.rs +300 -0
  12. data/crates/bevy/src/lib.rs +42 -0
  13. data/crates/bevy/src/mesh_renderer.rs +328 -0
  14. data/crates/bevy/src/query.rs +53 -0
  15. data/crates/bevy/src/render_app.rs +689 -0
  16. data/crates/bevy/src/resource.rs +28 -0
  17. data/crates/bevy/src/schedule.rs +186 -0
  18. data/crates/bevy/src/sprite_renderer.rs +355 -0
  19. data/crates/bevy/src/system.rs +44 -0
  20. data/crates/bevy/src/text_renderer.rs +258 -0
  21. data/crates/bevy/src/types/color.rs +114 -0
  22. data/crates/bevy/src/types/dynamic.rs +131 -0
  23. data/crates/bevy/src/types/math.rs +260 -0
  24. data/crates/bevy/src/types/mod.rs +9 -0
  25. data/crates/bevy/src/types/transform.rs +166 -0
  26. data/crates/bevy/src/world.rs +163 -0
  27. data/crates/bevy_ruby_render/Cargo.toml +22 -0
  28. data/crates/bevy_ruby_render/src/asset.rs +360 -0
  29. data/crates/bevy_ruby_render/src/audio.rs +511 -0
  30. data/crates/bevy_ruby_render/src/camera.rs +365 -0
  31. data/crates/bevy_ruby_render/src/gamepad.rs +398 -0
  32. data/crates/bevy_ruby_render/src/lib.rs +26 -0
  33. data/crates/bevy_ruby_render/src/material.rs +310 -0
  34. data/crates/bevy_ruby_render/src/mesh.rs +491 -0
  35. data/crates/bevy_ruby_render/src/sprite.rs +289 -0
  36. data/ext/bevy/Cargo.toml +20 -0
  37. data/ext/bevy/extconf.rb +6 -0
  38. data/ext/bevy/src/conversions.rs +137 -0
  39. data/ext/bevy/src/lib.rs +29 -0
  40. data/ext/bevy/src/ruby_app.rs +65 -0
  41. data/ext/bevy/src/ruby_color.rs +149 -0
  42. data/ext/bevy/src/ruby_component.rs +189 -0
  43. data/ext/bevy/src/ruby_entity.rs +33 -0
  44. data/ext/bevy/src/ruby_math.rs +384 -0
  45. data/ext/bevy/src/ruby_query.rs +64 -0
  46. data/ext/bevy/src/ruby_render_app.rs +779 -0
  47. data/ext/bevy/src/ruby_system.rs +122 -0
  48. data/ext/bevy/src/ruby_world.rs +107 -0
  49. data/lib/bevy/animation.rb +597 -0
  50. data/lib/bevy/app.rb +675 -0
  51. data/lib/bevy/asset.rb +613 -0
  52. data/lib/bevy/audio.rb +545 -0
  53. data/lib/bevy/audio_effects.rb +224 -0
  54. data/lib/bevy/camera.rb +412 -0
  55. data/lib/bevy/component.rb +91 -0
  56. data/lib/bevy/diagnostics.rb +227 -0
  57. data/lib/bevy/ecs_advanced.rb +296 -0
  58. data/lib/bevy/event.rb +199 -0
  59. data/lib/bevy/gizmos.rb +158 -0
  60. data/lib/bevy/gltf.rb +227 -0
  61. data/lib/bevy/hierarchy.rb +444 -0
  62. data/lib/bevy/input.rb +514 -0
  63. data/lib/bevy/lighting.rb +369 -0
  64. data/lib/bevy/material.rb +248 -0
  65. data/lib/bevy/mesh.rb +257 -0
  66. data/lib/bevy/navigation.rb +344 -0
  67. data/lib/bevy/networking.rb +335 -0
  68. data/lib/bevy/particle.rb +337 -0
  69. data/lib/bevy/physics.rb +396 -0
  70. data/lib/bevy/plugins/default_plugins.rb +34 -0
  71. data/lib/bevy/plugins/input_plugin.rb +49 -0
  72. data/lib/bevy/reflect.rb +361 -0
  73. data/lib/bevy/render_graph.rb +210 -0
  74. data/lib/bevy/resource.rb +185 -0
  75. data/lib/bevy/scene.rb +254 -0
  76. data/lib/bevy/shader.rb +319 -0
  77. data/lib/bevy/shape.rb +195 -0
  78. data/lib/bevy/skeletal.rb +248 -0
  79. data/lib/bevy/sprite.rb +152 -0
  80. data/lib/bevy/sprite_sheet.rb +444 -0
  81. data/lib/bevy/state.rb +277 -0
  82. data/lib/bevy/system.rb +206 -0
  83. data/lib/bevy/text.rb +99 -0
  84. data/lib/bevy/text_advanced.rb +455 -0
  85. data/lib/bevy/timer.rb +147 -0
  86. data/lib/bevy/transform.rb +158 -0
  87. data/lib/bevy/ui.rb +454 -0
  88. data/lib/bevy/ui_advanced.rb +568 -0
  89. data/lib/bevy/version.rb +5 -0
  90. data/lib/bevy/visibility.rb +250 -0
  91. data/lib/bevy/window.rb +302 -0
  92. data/lib/bevy.rb +390 -0
  93. metadata +150 -0
data/lib/bevy/app.rb ADDED
@@ -0,0 +1,675 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bevy
4
+ module Schedule
5
+ STARTUP = :startup
6
+ FIRST = :first
7
+ PRE_UPDATE = :pre_update
8
+ UPDATE = :update
9
+ POST_UPDATE = :post_update
10
+ LAST = :last
11
+ FIXED_UPDATE = :fixed_update
12
+ end
13
+
14
+ class System
15
+ attr_reader :name, :schedule, :proc
16
+
17
+ def initialize(name: nil, schedule: Schedule::UPDATE, &block)
18
+ @name = name || "anonymous_system_#{object_id}"
19
+ @schedule = schedule
20
+ @proc = block
21
+ end
22
+
23
+ def run(context)
24
+ @proc.call(context)
25
+ end
26
+ end
27
+
28
+ class SystemSet
29
+ attr_reader :name, :systems
30
+
31
+ def initialize(name)
32
+ @name = name
33
+ @systems = []
34
+ end
35
+
36
+ def add(system)
37
+ @systems << system
38
+ end
39
+
40
+ def run_all(context)
41
+ @systems.each { |s| s.run(context) }
42
+ end
43
+ end
44
+
45
+ class SystemContext
46
+ attr_reader :world, :resources, :events, :time, :keyboard, :mouse, :gamepads, :app
47
+
48
+ def initialize(world:, resources:, events:, time:, keyboard:, mouse:, gamepads:, app:, render_app: nil)
49
+ @world = world
50
+ @resources = resources
51
+ @events = events
52
+ @time = time
53
+ @keyboard = keyboard
54
+ @mouse = mouse
55
+ @gamepads = gamepads
56
+ @app = app
57
+ @render_app = render_app
58
+ end
59
+
60
+ def delta
61
+ @time.delta_seconds
62
+ end
63
+
64
+ def delta_seconds
65
+ @time.delta_seconds
66
+ end
67
+
68
+ def elapsed
69
+ @time.elapsed_seconds
70
+ end
71
+
72
+ def resource(resource_class)
73
+ @resources.get(resource_class)
74
+ end
75
+
76
+ def insert_resource(resource)
77
+ @resources.insert(resource)
78
+ end
79
+
80
+ def event_reader(event_class)
81
+ @events.reader(event_class)
82
+ end
83
+
84
+ def event_writer(event_class)
85
+ @events.writer(event_class)
86
+ end
87
+
88
+ def spawn(*components)
89
+ @world.spawn_entity(*components)
90
+ end
91
+
92
+ def despawn(entity)
93
+ @world.despawn(entity)
94
+ end
95
+
96
+ def query(*component_classes, &block)
97
+ @world.each(*component_classes, &block)
98
+ end
99
+
100
+ def key_pressed?(key)
101
+ if @render_app
102
+ @render_app.key_pressed?(key)
103
+ else
104
+ @keyboard.pressed?(key)
105
+ end
106
+ end
107
+
108
+ def key_just_pressed?(key)
109
+ if @render_app
110
+ @render_app.key_just_pressed?(key)
111
+ else
112
+ @keyboard.just_pressed?(key)
113
+ end
114
+ end
115
+
116
+ def mouse_pressed?(button)
117
+ button_str = button.to_s.upcase
118
+ if @render_app
119
+ @render_app.mouse_button_pressed?(button_str)
120
+ else
121
+ @mouse.pressed?(button_str)
122
+ end
123
+ end
124
+
125
+ def mouse_just_pressed?(button)
126
+ button_str = button.to_s.upcase
127
+ if @render_app
128
+ @render_app.mouse_button_just_pressed?(button_str)
129
+ else
130
+ @mouse.just_pressed?(button_str)
131
+ end
132
+ end
133
+
134
+ def mouse_position
135
+ if @render_app
136
+ pos = @render_app.mouse_position
137
+ Vec2.new(pos[0], pos[1])
138
+ else
139
+ @mouse.position
140
+ end
141
+ end
142
+
143
+ def gamepad(gamepad_id = nil)
144
+ if gamepad_id
145
+ @gamepads.get(gamepad_id)
146
+ else
147
+ @gamepads.first
148
+ end
149
+ end
150
+
151
+ def gamepad_connected?(gamepad_id)
152
+ @gamepads.connected?(gamepad_id)
153
+ end
154
+
155
+ def any_gamepad_connected?
156
+ @gamepads.any?
157
+ end
158
+
159
+ def connected_gamepad_ids
160
+ @gamepads.connected_ids
161
+ end
162
+
163
+ def gamepad_pressed?(button, gamepad_id = nil)
164
+ button_name = button.to_s
165
+ if gamepad_id
166
+ pad = gamepad(gamepad_id)
167
+ pad ? pad.pressed?(button_name) : false
168
+ else
169
+ @gamepads.any_pressed?(button_name)
170
+ end
171
+ end
172
+
173
+ def gamepad_just_pressed?(button, gamepad_id = nil)
174
+ button_name = button.to_s
175
+ if gamepad_id
176
+ pad = gamepad(gamepad_id)
177
+ pad ? pad.just_pressed?(button_name) : false
178
+ else
179
+ @gamepads.any_just_pressed?(button_name)
180
+ end
181
+ end
182
+
183
+ def gamepad_just_released?(button, gamepad_id = nil)
184
+ button_name = button.to_s
185
+ if gamepad_id
186
+ pad = gamepad(gamepad_id)
187
+ pad ? pad.just_released?(button_name) : false
188
+ else
189
+ @gamepads.any_just_released?(button_name)
190
+ end
191
+ end
192
+
193
+ def gamepad_axis(axis, gamepad_id = nil)
194
+ pad = gamepad(gamepad_id)
195
+ return 0.0 unless pad
196
+
197
+ pad.axis(axis.to_s)
198
+ end
199
+
200
+ def gamepad_axis_raw(axis, gamepad_id = nil)
201
+ pad = gamepad(gamepad_id)
202
+ return 0.0 unless pad
203
+
204
+ pad.axis_raw(axis.to_s)
205
+ end
206
+
207
+ def gamepad_left_stick(gamepad_id = nil)
208
+ pad = gamepad(gamepad_id)
209
+ pad ? pad.left_stick : Vec2.zero
210
+ end
211
+
212
+ def gamepad_right_stick(gamepad_id = nil)
213
+ pad = gamepad(gamepad_id)
214
+ pad ? pad.right_stick : Vec2.zero
215
+ end
216
+
217
+ def gamepad_left_trigger(gamepad_id = nil)
218
+ pad = gamepad(gamepad_id)
219
+ pad ? pad.left_trigger : 0.0
220
+ end
221
+
222
+ def gamepad_right_trigger(gamepad_id = nil)
223
+ pad = gamepad(gamepad_id)
224
+ pad ? pad.right_trigger : 0.0
225
+ end
226
+
227
+ def picking_events(kind = nil)
228
+ event_list = @events.get_events(PickingEvent)
229
+ return [] unless event_list
230
+
231
+ events = event_list.read
232
+ return events if kind.nil?
233
+
234
+ kind_name = kind.to_s
235
+ events.select { |event| event.kind == kind_name }
236
+ end
237
+
238
+ def picked?(entity_or_id, kind: nil)
239
+ target_id = entity_or_id.respond_to?(:id) ? entity_or_id.id : entity_or_id
240
+ picking_events(kind).any? { |event| event.target_id == target_id.to_i }
241
+ end
242
+
243
+ def camera_position
244
+ if @render_app
245
+ pos = @render_app.camera_position
246
+ Vec3.new(pos[0], pos[1], pos[2])
247
+ else
248
+ Vec3.new(0.0, 0.0, 0.0)
249
+ end
250
+ end
251
+
252
+ def set_camera_position(position)
253
+ return unless @render_app
254
+
255
+ if position.is_a?(Vec3)
256
+ @render_app.set_camera_position(position.x, position.y, position.z)
257
+ elsif position.is_a?(Vec2)
258
+ @render_app.set_camera_position(position.x, position.y, 0.0)
259
+ elsif position.is_a?(Array)
260
+ @render_app.set_camera_position(position[0] || 0.0, position[1] || 0.0, position[2] || 0.0)
261
+ end
262
+ end
263
+
264
+ def camera_scale
265
+ @render_app ? @render_app.camera_scale : 1.0
266
+ end
267
+
268
+ def set_camera_scale(scale)
269
+ @render_app&.set_camera_scale(scale)
270
+ end
271
+
272
+ alias camera_zoom camera_scale
273
+ alias set_camera_zoom set_camera_scale
274
+ end
275
+
276
+ class App
277
+ attr_reader :world, :resources, :events, :render_app, :time, :fixed_time, :keyboard, :mouse, :gamepads
278
+
279
+ GAMEPAD_BUTTONS = [
280
+ GamepadButton::SOUTH,
281
+ GamepadButton::EAST,
282
+ GamepadButton::NORTH,
283
+ GamepadButton::WEST,
284
+ GamepadButton::LEFT_TRIGGER,
285
+ GamepadButton::LEFT_TRIGGER2,
286
+ GamepadButton::RIGHT_TRIGGER,
287
+ GamepadButton::RIGHT_TRIGGER2,
288
+ GamepadButton::SELECT,
289
+ GamepadButton::START,
290
+ GamepadButton::MODE,
291
+ GamepadButton::LEFT_THUMB,
292
+ GamepadButton::RIGHT_THUMB,
293
+ GamepadButton::DPAD_UP,
294
+ GamepadButton::DPAD_DOWN,
295
+ GamepadButton::DPAD_LEFT,
296
+ GamepadButton::DPAD_RIGHT
297
+ ].freeze
298
+
299
+ GAMEPAD_AXES = [
300
+ GamepadAxis::LEFT_STICK_X,
301
+ GamepadAxis::LEFT_STICK_Y,
302
+ GamepadAxis::RIGHT_STICK_X,
303
+ GamepadAxis::RIGHT_STICK_Y,
304
+ GamepadAxis::LEFT_TRIGGER,
305
+ GamepadAxis::RIGHT_TRIGGER
306
+ ].freeze
307
+
308
+ def initialize(render: false, window: {})
309
+ @world = World.new
310
+ @resources = Resources.new
311
+ @events = EventRegistry.new
312
+ @events.register(PickingEvent)
313
+ @systems = Hash.new { |h, k| h[k] = [] }
314
+ @plugins = []
315
+ @running = false
316
+ @time = Time.new
317
+ @fixed_time = FixedTime.new
318
+ @keyboard = KeyboardInput.new
319
+ @mouse = MouseInput.new
320
+ @gamepads = Gamepads.new
321
+ @render_enabled = render
322
+ @window_config = window
323
+ @render_app = nil
324
+
325
+ yield self if block_given?
326
+ end
327
+
328
+ def render_enabled?
329
+ @render_enabled
330
+ end
331
+
332
+ def add_plugins(*plugins)
333
+ plugins.each do |plugin|
334
+ @plugins << plugin
335
+ plugin.build(self) if plugin.respond_to?(:build)
336
+ end
337
+ self
338
+ end
339
+
340
+ def add_systems(schedule, *systems, &block)
341
+ if block_given?
342
+ system = System.new(schedule: schedule, &block)
343
+ @systems[schedule] << system
344
+ else
345
+ systems.each do |system|
346
+ @systems[schedule] << system
347
+ end
348
+ end
349
+ self
350
+ end
351
+
352
+ def add_startup_system(&block)
353
+ add_systems(Schedule::STARTUP, &block)
354
+ end
355
+
356
+ def add_update_system(&block)
357
+ add_systems(Schedule::UPDATE, &block)
358
+ end
359
+
360
+ def add_event(event_class)
361
+ @events.register(event_class)
362
+ self
363
+ end
364
+
365
+ def insert_resource(resource)
366
+ @resources.insert(resource)
367
+ self
368
+ end
369
+
370
+ def run
371
+ @running = true
372
+ run_startup_systems
373
+ @render_enabled ? run_render_loop : run_main_loop
374
+ end
375
+
376
+ def run_once
377
+ @running = true
378
+ run_startup_systems
379
+ update
380
+ @running = false
381
+ end
382
+
383
+ def update
384
+ @time.update
385
+ accumulate_fixed_time
386
+
387
+ run_schedule(Schedule::FIRST)
388
+ run_schedule(Schedule::PRE_UPDATE)
389
+ run_fixed_update
390
+ run_schedule(Schedule::UPDATE)
391
+ run_schedule(Schedule::POST_UPDATE)
392
+ run_schedule(Schedule::LAST)
393
+
394
+ clear_input_state
395
+ @events.update_all
396
+ end
397
+
398
+ def stop
399
+ @running = false
400
+ @render_app&.stop!
401
+ end
402
+
403
+ def running?
404
+ @running
405
+ end
406
+
407
+ private
408
+
409
+ def run_startup_systems
410
+ context = build_context
411
+ @systems[Schedule::STARTUP].each { |s| s.run(context) }
412
+ end
413
+
414
+ def run_main_loop
415
+ while @running
416
+ update
417
+ sleep(0.001)
418
+ end
419
+ end
420
+
421
+ def run_render_loop
422
+ @render_app = RenderApp.new(@window_config)
423
+ @render_app.initialize!
424
+
425
+ @render_app.run do
426
+ sync_input_from_bevy
427
+ update
428
+ sync_sprites_to_bevy
429
+ end
430
+
431
+ @running = false
432
+ end
433
+
434
+ def sync_input_from_bevy
435
+ return unless @render_app
436
+
437
+ clear_input_state
438
+
439
+ @keyboard.reset
440
+ @render_app.pressed_keys.each do |key|
441
+ @keyboard.press(key)
442
+ end
443
+
444
+ @render_app.pressed_keys.each do |key|
445
+ @keyboard.set_just_pressed(key) unless @keyboard.was_pressed_last_frame?(key)
446
+ end
447
+
448
+ mouse_pos = @render_app.mouse_position
449
+ @mouse.set_position(mouse_pos[0], mouse_pos[1]) if mouse_pos
450
+
451
+ @mouse.reset
452
+ %w[LEFT RIGHT MIDDLE].each do |button|
453
+ @mouse.press(button) if @render_app.mouse_button_pressed?(button)
454
+ end
455
+
456
+ sync_gamepads_from_bevy
457
+ sync_picking_events_from_bevy
458
+ end
459
+
460
+ def sync_sprites_to_bevy
461
+ return unless @render_app
462
+
463
+ @world.despawned_entity_ids.each do |entity_id|
464
+ @render_app.remove_sprite(entity_id)
465
+ @render_app.remove_text(entity_id)
466
+ @render_app.remove_mesh(entity_id)
467
+ end
468
+ @world.clear_despawned_entity_ids
469
+
470
+ @world.each(Sprite, Transform) do |entity, sprite, transform|
471
+ @render_app.sync_sprite(
472
+ entity.id,
473
+ sprite.to_sync_hash,
474
+ transform.to_sync_hash
475
+ )
476
+ end
477
+
478
+ @world.each(Text2d, Transform) do |entity, text, transform|
479
+ @render_app.sync_text(
480
+ entity.id,
481
+ text.to_sync_hash,
482
+ transform.to_sync_hash
483
+ )
484
+ end
485
+
486
+ sync_mesh_shapes
487
+ sync_gamepad_rumble_to_bevy
488
+ end
489
+
490
+ def sync_mesh_shapes
491
+ return unless @render_app
492
+
493
+ @world.mesh_components.each do |entity_id, meshes|
494
+ entity = @world.mesh_entities[entity_id]
495
+ next unless entity
496
+
497
+ begin
498
+ next unless @world.has?(entity, Transform)
499
+
500
+ transform = @world.get_component(entity, Transform)
501
+
502
+ meshes.each_value do |mesh|
503
+ @render_app.sync_mesh(
504
+ entity_id,
505
+ mesh.to_mesh_data,
506
+ transform.to_sync_hash
507
+ )
508
+ end
509
+ rescue StandardError
510
+ next
511
+ end
512
+ end
513
+ end
514
+
515
+ def sync_gamepads_from_bevy
516
+ return unless @render_app.respond_to?(:gamepads_state)
517
+
518
+ gamepad_states = @render_app.gamepads_state
519
+ connected_ids = []
520
+
521
+ gamepad_states.each do |state|
522
+ id = state[:id] || state['id']
523
+ next if id.nil?
524
+
525
+ name = state[:name] || state['name'] || "Gamepad #{id}"
526
+ connected_ids << id
527
+
528
+ gamepad = @gamepads.get(id)
529
+ gamepad ||= @gamepads.connect(id, name: name)
530
+
531
+ pressed_buttons = Array(state[:buttons_pressed] || state['buttons_pressed']).map(&:to_s)
532
+ axes = state[:axes] || state['axes'] || {}
533
+
534
+ buttons_to_sync = (GAMEPAD_BUTTONS + gamepad.known_buttons + pressed_buttons).uniq
535
+ buttons_to_sync.each do |button|
536
+ value = pressed_buttons.include?(button) ? 1.0 : 0.0
537
+ gamepad.set_button_value(button, value)
538
+ end
539
+
540
+ axes_to_sync = (GAMEPAD_AXES + gamepad.known_axes + axes.keys.map(&:to_s)).uniq
541
+ axes_to_sync.each do |axis|
542
+ value = axes[axis] || axes[axis.to_sym] || 0.0
543
+ gamepad.set_axis(axis, value.to_f)
544
+ end
545
+ end
546
+
547
+ (@gamepads.connected_ids - connected_ids).each do |id|
548
+ @gamepads.disconnect(id)
549
+ end
550
+ end
551
+
552
+ def sync_gamepad_rumble_to_bevy
553
+ return unless @render_app.respond_to?(:queue_gamepad_rumble)
554
+
555
+ @gamepads.each do |gamepad|
556
+ rumble = gamepad.pending_rumble
557
+ next unless rumble
558
+
559
+ @render_app.queue_gamepad_rumble(
560
+ gamepad.id,
561
+ rumble.strong_magnitude,
562
+ rumble.weak_magnitude,
563
+ rumble.duration
564
+ )
565
+ gamepad.clear_pending_rumble
566
+ end
567
+ end
568
+
569
+ def sync_picking_events_from_bevy
570
+ return unless @render_app.respond_to?(:drain_picking_events)
571
+
572
+ writer = @events.writer(PickingEvent)
573
+ unless writer
574
+ @events.register(PickingEvent)
575
+ writer = @events.writer(PickingEvent)
576
+ end
577
+ return unless writer
578
+
579
+ Array(@render_app.drain_picking_events).each do |event_data|
580
+ kind = event_data[:kind] || event_data['kind']
581
+ target_id = event_data[:target_id] || event_data['target_id']
582
+ pointer_id = event_data[:pointer_id] || event_data['pointer_id']
583
+ button = event_data[:button] || event_data['button']
584
+ position = event_data[:position] || event_data['position']
585
+ camera_id = event_data[:camera_id] || event_data['camera_id']
586
+ depth = event_data[:depth] || event_data['depth']
587
+ hit_position = event_data[:hit_position] || event_data['hit_position']
588
+ hit_normal = event_data[:hit_normal] || event_data['hit_normal']
589
+
590
+ writer.send(
591
+ PickingEvent.new(
592
+ kind: kind.to_s,
593
+ target_id: target_id.to_i,
594
+ pointer_id: pointer_id.to_s,
595
+ button: button&.to_s,
596
+ position: to_vec2(position),
597
+ camera_id: camera_id.nil? ? nil : camera_id.to_i,
598
+ depth: depth.nil? ? nil : depth.to_f,
599
+ hit_position: to_vec3_or_nil(hit_position),
600
+ hit_normal: to_vec3_or_nil(hit_normal)
601
+ )
602
+ )
603
+ end
604
+ end
605
+
606
+ def run_schedule(schedule)
607
+ context = build_context
608
+ @systems[schedule].each { |s| s.run(context) }
609
+ end
610
+
611
+ def run_fixed_update
612
+ context = build_context
613
+ @systems[Schedule::FIXED_UPDATE].each { |s| s.run(context) } while @fixed_time.expend
614
+ end
615
+
616
+ def accumulate_fixed_time
617
+ @fixed_time.accumulate(@time.delta_seconds)
618
+ end
619
+
620
+ def build_context
621
+ SystemContext.new(
622
+ world: @world,
623
+ resources: @resources,
624
+ events: @events,
625
+ time: @time,
626
+ keyboard: @keyboard,
627
+ mouse: @mouse,
628
+ gamepads: @gamepads,
629
+ app: self,
630
+ render_app: @render_app
631
+ )
632
+ end
633
+
634
+ def clear_input_state
635
+ @keyboard.clear_just_pressed
636
+ @mouse.clear_just_pressed
637
+ @gamepads.clear_just_pressed
638
+ end
639
+
640
+ def to_vec2(value)
641
+ return value if value.is_a?(Vec2)
642
+ return Vec2.new(value[0].to_f, value[1].to_f) if value.is_a?(Array)
643
+
644
+ Vec2.zero
645
+ end
646
+
647
+ def to_vec3_or_nil(value)
648
+ return value if value.is_a?(Vec3)
649
+ return Vec3.new(value[0].to_f, value[1].to_f, value[2].to_f) if value.is_a?(Array)
650
+
651
+ nil
652
+ end
653
+ end
654
+
655
+ class Plugin
656
+ def build(app)
657
+ raise NotImplementedError, 'Subclasses must implement #build'
658
+ end
659
+ end
660
+
661
+ class DefaultPlugins < Plugin
662
+ def build(app)
663
+ app.insert_resource(Time.new)
664
+ app.insert_resource(FixedTime.new)
665
+ end
666
+ end
667
+
668
+ class InputPlugin < Plugin
669
+ def build(app)
670
+ app.insert_resource(KeyboardInput.new)
671
+ app.insert_resource(MouseInput.new)
672
+ app.insert_resource(Gamepads.new)
673
+ end
674
+ end
675
+ end