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
@@ -0,0 +1,227 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bevy
4
+ class DiagnosticsStore
5
+ attr_reader :diagnostics
6
+
7
+ def initialize
8
+ @diagnostics = {}
9
+ end
10
+
11
+ def add(diagnostic)
12
+ @diagnostics[diagnostic.id] = diagnostic
13
+ self
14
+ end
15
+
16
+ def get(id)
17
+ @diagnostics[id]
18
+ end
19
+
20
+ def remove(id)
21
+ @diagnostics.delete(id)
22
+ end
23
+
24
+ def get_measurement(id)
25
+ diagnostic = @diagnostics[id]
26
+ diagnostic&.average
27
+ end
28
+
29
+ def type_name
30
+ 'DiagnosticsStore'
31
+ end
32
+ end
33
+
34
+ class Diagnostic
35
+ attr_reader :id, :name, :suffix, :history
36
+
37
+ def initialize(id:, name:, max_history: 120, suffix: nil)
38
+ @id = id
39
+ @name = name
40
+ @suffix = suffix || ''
41
+ @max_history = max_history
42
+ @history = []
43
+ end
44
+
45
+ def add_measurement(value)
46
+ @history << { value: value.to_f, time: ::Time.now }
47
+ @history.shift while @history.size > @max_history
48
+ end
49
+
50
+ def value
51
+ @history.last&.fetch(:value)
52
+ end
53
+
54
+ def average
55
+ return nil if @history.empty?
56
+
57
+ @history.sum { |m| m[:value] } / @history.size
58
+ end
59
+
60
+ def smoothed
61
+ return nil if @history.size < 2
62
+
63
+ @history.last(10).sum { |m| m[:value] } / [10, @history.size].min
64
+ end
65
+
66
+ def clear_history
67
+ @history = []
68
+ end
69
+
70
+ def type_name
71
+ 'Diagnostic'
72
+ end
73
+ end
74
+
75
+ class FrameTimeDiagnostics
76
+ FPS_ID = 'fps'
77
+ FRAME_TIME_ID = 'frame_time'
78
+ FRAME_COUNT_ID = 'frame_count'
79
+
80
+ attr_reader :fps, :frame_time, :frame_count
81
+
82
+ def initialize
83
+ @fps = Diagnostic.new(id: FPS_ID, name: 'FPS', suffix: ' fps')
84
+ @frame_time = Diagnostic.new(id: FRAME_TIME_ID, name: 'Frame Time', suffix: ' ms')
85
+ @frame_count = 0
86
+ @last_time = ::Time.now
87
+ end
88
+
89
+ def update
90
+ current_time = ::Time.now
91
+ delta = current_time - @last_time
92
+ @last_time = current_time
93
+ @frame_count += 1
94
+
95
+ @frame_time.add_measurement(delta * 1000.0)
96
+ @fps.add_measurement(1.0 / delta) if delta > 0
97
+ end
98
+
99
+ def diagnostics
100
+ [@fps, @frame_time]
101
+ end
102
+
103
+ def type_name
104
+ 'FrameTimeDiagnostics'
105
+ end
106
+ end
107
+
108
+ class EntityCountDiagnostics
109
+ ENTITY_COUNT_ID = 'entity_count'
110
+
111
+ attr_reader :entity_count
112
+
113
+ def initialize
114
+ @entity_count = Diagnostic.new(id: ENTITY_COUNT_ID, name: 'Entity Count')
115
+ end
116
+
117
+ def update(world)
118
+ count = world.respond_to?(:entity_count) ? world.entity_count : 0
119
+ @entity_count.add_measurement(count)
120
+ end
121
+
122
+ def diagnostics
123
+ [@entity_count]
124
+ end
125
+
126
+ def type_name
127
+ 'EntityCountDiagnostics'
128
+ end
129
+ end
130
+
131
+ class SystemDiagnostics
132
+ attr_reader :system_timings
133
+
134
+ def initialize
135
+ @system_timings = {}
136
+ end
137
+
138
+ def start_system(name)
139
+ @system_timings[name] ||= Diagnostic.new(id: "system_#{name}", name: name, suffix: ' ms')
140
+ @system_timings[name].instance_variable_set(:@start_time, ::Time.now)
141
+ end
142
+
143
+ def end_system(name)
144
+ diagnostic = @system_timings[name]
145
+ return unless diagnostic
146
+
147
+ start_time = diagnostic.instance_variable_get(:@start_time)
148
+ return unless start_time
149
+
150
+ elapsed = (::Time.now - start_time) * 1000.0
151
+ diagnostic.add_measurement(elapsed)
152
+ end
153
+
154
+ def diagnostics
155
+ @system_timings.values
156
+ end
157
+
158
+ def type_name
159
+ 'SystemDiagnostics'
160
+ end
161
+ end
162
+
163
+ class LogDiagnostics
164
+ attr_accessor :enabled, :wait_duration
165
+
166
+ def initialize(wait_duration: 1.0)
167
+ @enabled = true
168
+ @wait_duration = wait_duration.to_f
169
+ @last_log_time = ::Time.now
170
+ end
171
+
172
+ def log(store)
173
+ return unless @enabled
174
+
175
+ current_time = ::Time.now
176
+ return unless current_time - @last_log_time >= @wait_duration
177
+
178
+ @last_log_time = current_time
179
+ store.diagnostics.each_value do |diagnostic|
180
+ puts format_diagnostic(diagnostic) if diagnostic.value
181
+ end
182
+ end
183
+
184
+ def type_name
185
+ 'LogDiagnostics'
186
+ end
187
+
188
+ private
189
+
190
+ def format_diagnostic(diagnostic)
191
+ value = diagnostic.smoothed || diagnostic.value
192
+ "#{diagnostic.name}: #{value.round(2)}#{diagnostic.suffix}"
193
+ end
194
+ end
195
+
196
+ class PerformanceMetrics
197
+ attr_reader :draw_calls, :triangles, :vertices, :textures_bound
198
+
199
+ def initialize
200
+ @draw_calls = 0
201
+ @triangles = 0
202
+ @vertices = 0
203
+ @textures_bound = 0
204
+ end
205
+
206
+ def reset
207
+ @draw_calls = 0
208
+ @triangles = 0
209
+ @vertices = 0
210
+ @textures_bound = 0
211
+ end
212
+
213
+ def add_draw_call(triangles: 0, vertices: 0)
214
+ @draw_calls += 1
215
+ @triangles += triangles
216
+ @vertices += vertices
217
+ end
218
+
219
+ def bind_texture
220
+ @textures_bound += 1
221
+ end
222
+
223
+ def type_name
224
+ 'PerformanceMetrics'
225
+ end
226
+ end
227
+ end
@@ -0,0 +1,296 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bevy
4
+ class Changed
5
+ attr_reader :component_type
6
+
7
+ def initialize(component_type)
8
+ @component_type = component_type
9
+ end
10
+
11
+ def type_name
12
+ 'Changed'
13
+ end
14
+ end
15
+
16
+ class Added
17
+ attr_reader :component_type
18
+
19
+ def initialize(component_type)
20
+ @component_type = component_type
21
+ end
22
+
23
+ def type_name
24
+ 'Added'
25
+ end
26
+ end
27
+
28
+ class With
29
+ attr_reader :component_types
30
+
31
+ def initialize(*component_types)
32
+ @component_types = component_types
33
+ end
34
+
35
+ def type_name
36
+ 'With'
37
+ end
38
+ end
39
+
40
+ class Without
41
+ attr_reader :component_types
42
+
43
+ def initialize(*component_types)
44
+ @component_types = component_types
45
+ end
46
+
47
+ def type_name
48
+ 'Without'
49
+ end
50
+ end
51
+
52
+ class Or
53
+ attr_reader :filters
54
+
55
+ def initialize(*filters)
56
+ @filters = filters
57
+ end
58
+
59
+ def type_name
60
+ 'Or'
61
+ end
62
+ end
63
+
64
+ class ChangeTrackers
65
+ attr_reader :entity, :component_type
66
+
67
+ def initialize(entity:, component_type:)
68
+ @entity = entity
69
+ @component_type = component_type
70
+ @added_tick = 0
71
+ @changed_tick = 0
72
+ @last_run = 0
73
+ end
74
+
75
+ def is_added?(current_tick)
76
+ @added_tick > @last_run && @added_tick <= current_tick
77
+ end
78
+
79
+ def is_changed?(current_tick)
80
+ @changed_tick > @last_run && @changed_tick <= current_tick
81
+ end
82
+
83
+ def set_added(tick)
84
+ @added_tick = tick
85
+ end
86
+
87
+ def set_changed(tick)
88
+ @changed_tick = tick
89
+ end
90
+
91
+ def update_last_run(tick)
92
+ @last_run = tick
93
+ end
94
+
95
+ def type_name
96
+ 'ChangeTrackers'
97
+ end
98
+ end
99
+
100
+ class ComponentTracker
101
+ def initialize
102
+ @trackers = {}
103
+ @current_tick = 0
104
+ end
105
+
106
+ def tick
107
+ @current_tick += 1
108
+ end
109
+
110
+ def current_tick
111
+ @current_tick
112
+ end
113
+
114
+ def track_add(entity, component_type)
115
+ key = [entity, component_type]
116
+ @trackers[key] ||= ChangeTrackers.new(entity: entity, component_type: component_type)
117
+ @trackers[key].set_added(@current_tick)
118
+ end
119
+
120
+ def track_change(entity, component_type)
121
+ key = [entity, component_type]
122
+ @trackers[key] ||= ChangeTrackers.new(entity: entity, component_type: component_type)
123
+ @trackers[key].set_changed(@current_tick)
124
+ end
125
+
126
+ def added?(entity, component_type)
127
+ key = [entity, component_type]
128
+ tracker = @trackers[key]
129
+ tracker&.is_added?(@current_tick) || false
130
+ end
131
+
132
+ def changed?(entity, component_type)
133
+ key = [entity, component_type]
134
+ tracker = @trackers[key]
135
+ tracker&.is_changed?(@current_tick) || false
136
+ end
137
+
138
+ def type_name
139
+ 'ComponentTracker'
140
+ end
141
+ end
142
+
143
+ class SystemSet
144
+ attr_reader :name, :systems
145
+
146
+ def initialize(name)
147
+ @name = name
148
+ @systems = []
149
+ @run_condition = nil
150
+ end
151
+
152
+ def add_system(system)
153
+ @systems << system
154
+ self
155
+ end
156
+
157
+ def run_if(&condition)
158
+ @run_condition = condition
159
+ self
160
+ end
161
+
162
+ def should_run?(context)
163
+ return true unless @run_condition
164
+
165
+ @run_condition.call(context)
166
+ end
167
+
168
+ def type_name
169
+ 'SystemSet'
170
+ end
171
+ end
172
+
173
+ class RunCondition
174
+ def self.resource_exists(resource_type)
175
+ ->(ctx) { ctx.has_resource?(resource_type) }
176
+ end
177
+
178
+ def self.resource_equals(resource_type, value)
179
+ ->(ctx) { ctx.get_resource(resource_type) == value }
180
+ end
181
+
182
+ def self.state_equals(state_type, value)
183
+ ->(ctx) { ctx.get_state(state_type) == value }
184
+ end
185
+
186
+ def self.in_state(state)
187
+ ->(ctx) { ctx.current_state == state }
188
+ end
189
+
190
+ def self.run_once
191
+ ran = false
192
+ lambda do |_ctx|
193
+ return false if ran
194
+
195
+ ran = true
196
+ true
197
+ end
198
+ end
199
+
200
+ def self.not(condition)
201
+ ->(ctx) { !condition.call(ctx) }
202
+ end
203
+
204
+ def self.and(cond1, cond2)
205
+ ->(ctx) { cond1.call(ctx) && cond2.call(ctx) }
206
+ end
207
+
208
+ def self.or(cond1, cond2)
209
+ ->(ctx) { cond1.call(ctx) || cond2.call(ctx) }
210
+ end
211
+ end
212
+
213
+ class Commands
214
+ def initialize(world)
215
+ @world = world
216
+ @command_queue = []
217
+ end
218
+
219
+ def spawn(*components)
220
+ entity = @world.spawn_entity(*components)
221
+ EntityCommands.new(entity, @world)
222
+ end
223
+
224
+ def entity(entity)
225
+ EntityCommands.new(entity, @world)
226
+ end
227
+
228
+ def despawn(entity)
229
+ @command_queue << [:despawn, entity]
230
+ self
231
+ end
232
+
233
+ def insert_resource(resource)
234
+ @command_queue << [:insert_resource, resource]
235
+ self
236
+ end
237
+
238
+ def remove_resource(resource_type)
239
+ @command_queue << [:remove_resource, resource_type]
240
+ self
241
+ end
242
+
243
+ def apply
244
+ @command_queue.each do |cmd, *args|
245
+ case cmd
246
+ when :despawn
247
+ @world.despawn(args[0])
248
+ when :insert_resource
249
+ @world.insert_resource(args[0])
250
+ when :remove_resource
251
+ @world.remove_resource(args[0])
252
+ end
253
+ end
254
+ @command_queue = []
255
+ end
256
+
257
+ def type_name
258
+ 'Commands'
259
+ end
260
+ end
261
+
262
+ class EntityCommands
263
+ def initialize(entity, world)
264
+ @entity = entity
265
+ @world = world
266
+ end
267
+
268
+ def insert(*components)
269
+ components.each do |component|
270
+ @world.insert_component(@entity, component)
271
+ end
272
+ self
273
+ end
274
+
275
+ def remove(component_type)
276
+ @world.remove_component(@entity, component_type)
277
+ self
278
+ end
279
+
280
+ def despawn
281
+ @world.despawn(@entity)
282
+ end
283
+
284
+ def despawn_recursive
285
+ DespawnRecursive.despawn(@world, @entity)
286
+ end
287
+
288
+ def id
289
+ @entity
290
+ end
291
+
292
+ def type_name
293
+ 'EntityCommands'
294
+ end
295
+ end
296
+ end
data/lib/bevy/event.rb ADDED
@@ -0,0 +1,199 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bevy
4
+ class EventDSL
5
+ class << self
6
+ def attribute(name, type, default: nil)
7
+ @attributes ||= {}
8
+ @attributes[name] = { type: type, default: default }
9
+
10
+ define_method(name) { @data[name] }
11
+ define_method(:"#{name}=") { |value| @data[name] = value }
12
+ end
13
+
14
+ def attributes
15
+ @attributes ||= {}
16
+ end
17
+
18
+ def event_name
19
+ name || 'AnonymousEvent'
20
+ end
21
+
22
+ def inherited(subclass)
23
+ super
24
+ subclass.instance_variable_set(:@attributes, attributes.dup)
25
+ end
26
+ end
27
+
28
+ def initialize(**attrs)
29
+ @data = {}
30
+ self.class.attributes.each do |attr_name, config|
31
+ default = config[:default]
32
+ default_value = default.respond_to?(:call) ? default.call : default
33
+ @data[attr_name] = attrs.fetch(attr_name, default_value)
34
+ end
35
+ end
36
+
37
+ def to_h
38
+ @data.dup
39
+ end
40
+ end
41
+
42
+ class Events
43
+ def initialize(event_class)
44
+ @event_class = event_class
45
+ @events = []
46
+ @events_last_frame = []
47
+ end
48
+
49
+ def send(event)
50
+ validate_event!(event)
51
+ @events << event
52
+ end
53
+
54
+ def read
55
+ @events_last_frame + @events
56
+ end
57
+
58
+ def drain
59
+ result = @events.dup
60
+ @events.clear
61
+ result
62
+ end
63
+
64
+ def clear
65
+ @events.clear
66
+ end
67
+
68
+ def update
69
+ @events_last_frame = @events.dup
70
+ @events.clear
71
+ end
72
+
73
+ def empty?
74
+ @events.empty? && @events_last_frame.empty?
75
+ end
76
+
77
+ def len
78
+ @events.length + @events_last_frame.length
79
+ end
80
+
81
+ private
82
+
83
+ def validate_event!(event)
84
+ return if event.is_a?(@event_class)
85
+
86
+ raise ArgumentError, "Expected #{@event_class}, got #{event.class}"
87
+ end
88
+ end
89
+
90
+ class EventReader
91
+ def initialize(events)
92
+ @events = events
93
+ @cursor = 0
94
+ end
95
+
96
+ def read
97
+ all_events = @events.read
98
+ new_events = all_events[@cursor..]
99
+ @cursor = all_events.length
100
+ new_events || []
101
+ end
102
+
103
+ def is_empty?
104
+ @events.read.length <= @cursor
105
+ end
106
+
107
+ def len
108
+ [@events.read.length - @cursor, 0].max
109
+ end
110
+
111
+ def clear
112
+ @cursor = @events.read.length
113
+ end
114
+ end
115
+
116
+ class EventWriter
117
+ def initialize(events)
118
+ @events = events
119
+ end
120
+
121
+ def send(event)
122
+ @events.send(event)
123
+ end
124
+
125
+ def send_batch(events)
126
+ events.each { |e| @events.send(e) }
127
+ end
128
+
129
+ def send_default
130
+ @events.send(@events.instance_variable_get(:@event_class).new)
131
+ end
132
+ end
133
+
134
+ class EventRegistry
135
+ def initialize
136
+ @events = {}
137
+ @readers = {}
138
+ end
139
+
140
+ def register(event_class)
141
+ type_name = event_class_name(event_class)
142
+ @events[type_name] ||= Events.new(event_class)
143
+ end
144
+
145
+ def get_events(event_class)
146
+ type_name = event_class_name(event_class)
147
+ @events[type_name]
148
+ end
149
+
150
+ def reader(event_class)
151
+ type_name = event_class_name(event_class)
152
+ events = @events[type_name]
153
+ return nil unless events
154
+
155
+ @readers[type_name] ||= EventReader.new(events)
156
+ end
157
+
158
+ def writer(event_class)
159
+ type_name = event_class_name(event_class)
160
+ events = @events[type_name]
161
+ return nil unless events
162
+
163
+ EventWriter.new(events)
164
+ end
165
+
166
+ def update_all
167
+ @events.each_value(&:update)
168
+ end
169
+
170
+ def clear_all
171
+ @events.each_value(&:clear)
172
+ end
173
+
174
+ private
175
+
176
+ def event_class_name(event_class)
177
+ case event_class
178
+ when Class
179
+ event_class.respond_to?(:event_name) ? event_class.event_name : event_class.name
180
+ when String
181
+ event_class
182
+ else
183
+ raise ArgumentError, 'Expected Class or String'
184
+ end
185
+ end
186
+ end
187
+
188
+ class PickingEvent < EventDSL
189
+ attribute :kind, :string, default: ''
190
+ attribute :target_id, :integer, default: 0
191
+ attribute :pointer_id, :string, default: ''
192
+ attribute :button, :string, default: nil
193
+ attribute :position, :vec2, default: -> { Vec2.zero }
194
+ attribute :camera_id, :integer, default: nil
195
+ attribute :depth, :float, default: nil
196
+ attribute :hit_position, :vec3, default: nil
197
+ attribute :hit_normal, :vec3, default: nil
198
+ end
199
+ end