webxr 0.1.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 (54) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/CHANGELOG.md +7 -0
  4. data/README.md +138 -0
  5. data/Rakefile +21 -0
  6. data/examples/ar_demo.html +238 -0
  7. data/examples/ar_hit_test.rb +157 -0
  8. data/examples/basic_vr.rb +110 -0
  9. data/examples/controller_input.rb +91 -0
  10. data/examples/hand_tracking.rb +124 -0
  11. data/examples/hello_webxr.html +288 -0
  12. data/examples/inline_demo.html +261 -0
  13. data/examples/server.rb +34 -0
  14. data/examples/vr_scene_demo.html +330 -0
  15. data/lib/webxr/ar/anchor.rb +83 -0
  16. data/lib/webxr/ar/hit_test_result.rb +54 -0
  17. data/lib/webxr/ar/hit_test_source.rb +34 -0
  18. data/lib/webxr/ar/ray.rb +90 -0
  19. data/lib/webxr/constants.rb +61 -0
  20. data/lib/webxr/core/frame.rb +155 -0
  21. data/lib/webxr/core/render_state.rb +47 -0
  22. data/lib/webxr/core/session.rb +212 -0
  23. data/lib/webxr/core/system.rb +122 -0
  24. data/lib/webxr/errors.rb +18 -0
  25. data/lib/webxr/events/input_source_event.rb +53 -0
  26. data/lib/webxr/events/reference_space_event.rb +44 -0
  27. data/lib/webxr/events/session_event.rb +56 -0
  28. data/lib/webxr/geometry/pose.rb +49 -0
  29. data/lib/webxr/geometry/rigid_transform.rb +73 -0
  30. data/lib/webxr/geometry/view.rb +68 -0
  31. data/lib/webxr/geometry/viewer_pose.rb +40 -0
  32. data/lib/webxr/geometry/viewport.rb +55 -0
  33. data/lib/webxr/hand/hand.rb +197 -0
  34. data/lib/webxr/hand/joint_pose.rb +33 -0
  35. data/lib/webxr/hand/joint_space.rb +74 -0
  36. data/lib/webxr/helpers/input_helper.rb +142 -0
  37. data/lib/webxr/helpers/rendering_helper.rb +94 -0
  38. data/lib/webxr/helpers/session_manager.rb +105 -0
  39. data/lib/webxr/input/gamepad.rb +115 -0
  40. data/lib/webxr/input/gamepad_button.rb +36 -0
  41. data/lib/webxr/input/input_source.rb +101 -0
  42. data/lib/webxr/input/input_source_array.rb +86 -0
  43. data/lib/webxr/js_wrapper.rb +116 -0
  44. data/lib/webxr/layers/layer.rb +28 -0
  45. data/lib/webxr/layers/webgl_binding.rb +69 -0
  46. data/lib/webxr/layers/webgl_layer.rb +102 -0
  47. data/lib/webxr/layers/webgl_sub_image.rb +59 -0
  48. data/lib/webxr/spaces/bounded_reference_space.rb +43 -0
  49. data/lib/webxr/spaces/reference_space.rb +51 -0
  50. data/lib/webxr/spaces/space.rb +18 -0
  51. data/lib/webxr/version.rb +5 -0
  52. data/lib/webxr.rb +73 -0
  53. data/webxr.gemspec +33 -0
  54. metadata +111 -0
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WebXR
4
+ # Gamepad - Represents a gamepad connected to an XR input source
5
+ class Gamepad < JSWrapper
6
+ # @param js_gamepad [JS::Object] The Gamepad JavaScript object
7
+ def initialize(js_gamepad)
8
+ super(js_gamepad)
9
+ end
10
+
11
+ # Get the gamepad identifier
12
+ # @return [String]
13
+ def id
14
+ js_string(:id) || ""
15
+ end
16
+
17
+ # Get the gamepad index
18
+ # @return [Integer]
19
+ def index
20
+ js_int(:index) || -1
21
+ end
22
+
23
+ # Check if the gamepad is connected
24
+ # @return [Boolean]
25
+ def connected?
26
+ js_bool(:connected)
27
+ end
28
+
29
+ # Get the button/axis mapping type
30
+ # @return [String]
31
+ def mapping
32
+ js_string(:mapping) || ""
33
+ end
34
+
35
+ # Get all axis values
36
+ # @return [Array<Float>]
37
+ def axes
38
+ js_axes = js_prop(:axes)
39
+ return [] if js_axes.nil?
40
+
41
+ js_array_to_a(js_axes).map(&:to_f)
42
+ end
43
+
44
+ # Get all buttons
45
+ # @return [Array<GamepadButton>]
46
+ def buttons
47
+ js_buttons = js_prop(:buttons)
48
+ return [] if js_buttons.nil?
49
+
50
+ js_array_to_a(js_buttons).map { |b| GamepadButton.new(b) }
51
+ end
52
+
53
+ # Get a specific axis value
54
+ # @param index [Integer] The axis index
55
+ # @return [Float]
56
+ def axis(index)
57
+ axes[index] || 0.0
58
+ end
59
+
60
+ # Get a specific button
61
+ # @param index [Integer] The button index
62
+ # @return [GamepadButton, nil]
63
+ def button(index)
64
+ buttons[index]
65
+ end
66
+
67
+ # Get the thumbstick X axis (typically axis 2)
68
+ # @return [Float]
69
+ def thumbstick_x
70
+ axis(2)
71
+ end
72
+
73
+ # Get the thumbstick Y axis (typically axis 3)
74
+ # @return [Float]
75
+ def thumbstick_y
76
+ axis(3)
77
+ end
78
+
79
+ # Get the touchpad X axis (typically axis 0)
80
+ # @return [Float]
81
+ def touchpad_x
82
+ axis(0)
83
+ end
84
+
85
+ # Get the touchpad Y axis (typically axis 1)
86
+ # @return [Float]
87
+ def touchpad_y
88
+ axis(1)
89
+ end
90
+
91
+ # Get the trigger button (typically button 0)
92
+ # @return [GamepadButton, nil]
93
+ def trigger
94
+ button(0)
95
+ end
96
+
97
+ # Get the squeeze/grip button (typically button 1)
98
+ # @return [GamepadButton, nil]
99
+ def squeeze
100
+ button(1)
101
+ end
102
+
103
+ # Get the touchpad button (typically button 2)
104
+ # @return [GamepadButton, nil]
105
+ def touchpad
106
+ button(2)
107
+ end
108
+
109
+ # Get the thumbstick button (typically button 3)
110
+ # @return [GamepadButton, nil]
111
+ def thumbstick
112
+ button(3)
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WebXR
4
+ # GamepadButton - Represents a button on a gamepad
5
+ class GamepadButton < JSWrapper
6
+ # @param js_button [JS::Object] The GamepadButton JavaScript object
7
+ def initialize(js_button)
8
+ super(js_button)
9
+ end
10
+
11
+ # Check if the button is currently pressed
12
+ # @return [Boolean]
13
+ def pressed?
14
+ js_bool(:pressed)
15
+ end
16
+
17
+ # Check if the button is being touched (capacitive)
18
+ # @return [Boolean]
19
+ def touched?
20
+ js_bool(:touched)
21
+ end
22
+
23
+ # Get the analog value of the button (0.0 to 1.0)
24
+ # @return [Float]
25
+ def value
26
+ js_float(:value) || 0.0
27
+ end
28
+
29
+ # Check if the button value is above a threshold
30
+ # @param threshold [Float] The threshold (0.0 to 1.0)
31
+ # @return [Boolean]
32
+ def active?(threshold: 0.5)
33
+ value >= threshold
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WebXR
4
+ # XRInputSource - Represents an input device (controller, hand, etc.)
5
+ class InputSource < JSWrapper
6
+ # @param js_input_source [JS::Object] The XRInputSource JavaScript object
7
+ def initialize(js_input_source)
8
+ super(js_input_source)
9
+ end
10
+
11
+ # Get the handedness of the input source
12
+ # @return [String] "none", "left", or "right"
13
+ def handedness
14
+ js_string(:handedness) || Handedness::NONE
15
+ end
16
+
17
+ # Get the target ray mode
18
+ # @return [String] "gaze", "tracked-pointer", "screen", or "transient-pointer"
19
+ def target_ray_mode
20
+ js_string(:targetRayMode) || TargetRayMode::GAZE
21
+ end
22
+
23
+ # Get the target ray space (pointing direction)
24
+ # @return [Space]
25
+ def target_ray_space
26
+ Space.new(js_prop(:targetRaySpace))
27
+ end
28
+
29
+ # Get the grip space (controller position/orientation)
30
+ # @return [Space, nil] nil if no grip space is available
31
+ def grip_space
32
+ js_space = js_prop(:gripSpace)
33
+ return nil if js_space.nil?
34
+
35
+ Space.new(js_space)
36
+ end
37
+
38
+ # Get the input source profiles
39
+ # @return [Array<String>]
40
+ def profiles
41
+ js_profiles = js_prop(:profiles)
42
+ return [] if js_profiles.nil?
43
+
44
+ js_array_to_a(js_profiles).map(&:to_s)
45
+ end
46
+
47
+ # Get the associated gamepad (if available)
48
+ # @return [Gamepad, nil]
49
+ def gamepad
50
+ js_gamepad = js_prop(:gamepad)
51
+ return nil if js_gamepad.nil?
52
+
53
+ Gamepad.new(js_gamepad)
54
+ end
55
+
56
+ # Get the associated hand (if hand tracking is enabled)
57
+ # @return [Hand::Hand, nil]
58
+ def hand
59
+ js_hand = js_prop(:hand)
60
+ return nil if js_hand.nil?
61
+
62
+ Hand::Hand.new(js_hand)
63
+ end
64
+
65
+ # Check if this is the left controller
66
+ # @return [Boolean]
67
+ def left?
68
+ handedness == Handedness::LEFT
69
+ end
70
+
71
+ # Check if this is the right controller
72
+ # @return [Boolean]
73
+ def right?
74
+ handedness == Handedness::RIGHT
75
+ end
76
+
77
+ # Check if this is a tracked pointer (controller)
78
+ # @return [Boolean]
79
+ def tracked_pointer?
80
+ target_ray_mode == TargetRayMode::TRACKED_POINTER
81
+ end
82
+
83
+ # Check if this is a gaze input
84
+ # @return [Boolean]
85
+ def gaze?
86
+ target_ray_mode == TargetRayMode::GAZE
87
+ end
88
+
89
+ # Check if this is a screen-based input
90
+ # @return [Boolean]
91
+ def screen?
92
+ target_ray_mode == TargetRayMode::SCREEN
93
+ end
94
+
95
+ # Check if this input has hand tracking
96
+ # @return [Boolean]
97
+ def hand_tracking?
98
+ !hand.nil?
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WebXR
4
+ # XRInputSourceArray - Collection of input sources
5
+ class InputSourceArray < JSWrapper
6
+ include Enumerable
7
+
8
+ # @param js_array [JS::Object] The XRInputSourceArray JavaScript object
9
+ def initialize(js_array)
10
+ super(js_array)
11
+ end
12
+
13
+ # Iterate over each input source
14
+ # @yield [InputSource] Each input source
15
+ # @return [Enumerator, void]
16
+ def each(&block)
17
+ return enum_for(:each) unless block_given?
18
+
19
+ length.times do |i|
20
+ yield self[i]
21
+ end
22
+ end
23
+
24
+ # Get an input source by index
25
+ # @param index [Integer] The index
26
+ # @return [InputSource, nil]
27
+ def [](index)
28
+ js_source = @js[index]
29
+ return nil if js_source.nil?
30
+
31
+ InputSource.new(js_source)
32
+ end
33
+
34
+ # Get the number of input sources
35
+ # @return [Integer]
36
+ def length
37
+ js_int(:length) || 0
38
+ end
39
+ alias size length
40
+
41
+ # Check if the array is empty
42
+ # @return [Boolean]
43
+ def empty?
44
+ length.zero?
45
+ end
46
+
47
+ # Get the first input source
48
+ # @return [InputSource, nil]
49
+ def first
50
+ self[0]
51
+ end
52
+
53
+ # Get the last input source
54
+ # @return [InputSource, nil]
55
+ def last
56
+ return nil if empty?
57
+
58
+ self[length - 1]
59
+ end
60
+
61
+ # Find input sources by handedness
62
+ # @param handedness [String] "left", "right", or "none"
63
+ # @return [Array<InputSource>]
64
+ def by_handedness(handedness)
65
+ select { |source| source.handedness == handedness }
66
+ end
67
+
68
+ # Get the left controller
69
+ # @return [InputSource, nil]
70
+ def left
71
+ find(&:left?)
72
+ end
73
+
74
+ # Get the right controller
75
+ # @return [InputSource, nil]
76
+ def right
77
+ find(&:right?)
78
+ end
79
+
80
+ # Get all tracked pointer inputs
81
+ # @return [Array<InputSource>]
82
+ def tracked_pointers
83
+ select(&:tracked_pointer?)
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WebXR
4
+ # Base class for wrapping JavaScript objects
5
+ # Provides common utilities for JS interop via ruby.wasm
6
+ class JSWrapper
7
+ # @return [JS::Object] The wrapped JavaScript object
8
+ attr_reader :js
9
+
10
+ # @param js_object [JS::Object] The JavaScript object to wrap
11
+ def initialize(js_object)
12
+ @js = js_object
13
+ end
14
+
15
+ # Check if the wrapped JS object is nil/undefined
16
+ # @return [Boolean]
17
+ def nil?
18
+ @js.nil?
19
+ end
20
+
21
+ protected
22
+
23
+ # Safely get a JS property
24
+ # @param name [String, Symbol] Property name
25
+ # @return [JS::Object, nil]
26
+ def js_prop(name)
27
+ @js[name]
28
+ end
29
+
30
+ # Get a JS property as a Ruby String
31
+ # @param name [String, Symbol] Property name
32
+ # @return [String, nil]
33
+ def js_string(name)
34
+ val = @js[name]
35
+ val.nil? ? nil : val.to_s
36
+ end
37
+
38
+ # Get a JS property as a Ruby Float
39
+ # @param name [String, Symbol] Property name
40
+ # @return [Float, nil]
41
+ def js_float(name)
42
+ val = @js[name]
43
+ val.nil? ? nil : val.to_f
44
+ end
45
+
46
+ # Get a JS property as a Ruby Integer
47
+ # @param name [String, Symbol] Property name
48
+ # @return [Integer, nil]
49
+ def js_int(name)
50
+ val = @js[name]
51
+ val.nil? ? nil : val.to_i
52
+ end
53
+
54
+ # Get a JS property as a Ruby Boolean
55
+ # @param name [String, Symbol] Property name
56
+ # @return [Boolean]
57
+ def js_bool(name)
58
+ !!@js[name]
59
+ end
60
+
61
+ # Call a JS method on the wrapped object
62
+ # @param method [String, Symbol] Method name
63
+ # @param args [Array] Arguments to pass
64
+ # @return [JS::Object]
65
+ def js_call(method, *args)
66
+ @js.call(method, *args)
67
+ end
68
+
69
+ # Wait for a JS Promise and return the result
70
+ # @param promise [JS::Object] A JavaScript Promise
71
+ # @return [JS::Object] The resolved value
72
+ def js_await(promise)
73
+ JS.await(promise)
74
+ end
75
+
76
+ # Convert a Ruby Hash to a JS object
77
+ # @param hash [Hash] Ruby hash to convert
78
+ # @return [JS::Object]
79
+ def to_js_object(hash)
80
+ obj = JS.eval("({})")
81
+ hash.each { |k, v| obj[k.to_s] = v }
82
+ obj
83
+ end
84
+
85
+ # Convert a Ruby Array to a JS array
86
+ # @param array [Array] Ruby array to convert
87
+ # @return [JS::Object]
88
+ def to_js_array(array)
89
+ arr = JS.eval("[]")
90
+ array.each { |item| arr.call(:push, item) }
91
+ arr
92
+ end
93
+
94
+ # Convert a JS array-like object to a Ruby Array
95
+ # @param js_array [JS::Object] JavaScript array
96
+ # @return [Array<JS::Object>]
97
+ def js_array_to_a(js_array)
98
+ return [] if js_array.nil?
99
+
100
+ length = js_array[:length].to_i
101
+ Array.new(length) { |i| js_array[i] }
102
+ end
103
+
104
+ # Create a DOMPointReadOnly from a hash
105
+ # @param hash [Hash] Hash with :x, :y, :z, :w keys
106
+ # @return [JS::Object]
107
+ def create_dom_point(hash)
108
+ JS.global[:DOMPointReadOnly].new(
109
+ hash[:x] || 0,
110
+ hash[:y] || 0,
111
+ hash[:z] || 0,
112
+ hash[:w] || 1
113
+ )
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WebXR
4
+ # XRLayer - Base class for XR rendering layers
5
+ class Layer < JSWrapper
6
+ # @param js_layer [JS::Object] The XRLayer JavaScript object
7
+ def initialize(js_layer)
8
+ super(js_layer)
9
+ end
10
+
11
+ # Wrap a JS layer object in the appropriate Ruby class
12
+ # @param js_layer [JS::Object] The JavaScript layer object
13
+ # @return [Layer, WebGLLayer]
14
+ def self.wrap(js_layer)
15
+ return nil if js_layer.nil?
16
+
17
+ # Check the constructor name to determine the type
18
+ constructor_name = js_layer[:constructor][:name].to_s
19
+
20
+ case constructor_name
21
+ when "XRWebGLLayer"
22
+ WebGLLayer.wrap(js_layer)
23
+ else
24
+ new(js_layer)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WebXR
4
+ # XRWebGLBinding - Provides advanced WebGL layer functionality
5
+ class WebGLBinding < JSWrapper
6
+ # @param session [Session] The XR session
7
+ # @param context [JS::Object] The WebGL2 rendering context
8
+ def initialize(session, context)
9
+ js_binding = JS.global[:XRWebGLBinding].new(session.js, context)
10
+ super(js_binding)
11
+ @context = context
12
+ end
13
+
14
+ # Get the native projection scale factor
15
+ # @return [Float]
16
+ def native_projection_scale_factor
17
+ js_float(:nativeProjectionScaleFactor) || 1.0
18
+ end
19
+
20
+ # Check if HDR is supported
21
+ # @return [Boolean]
22
+ def hdr_supported?
23
+ js_bool(:usesDepthValues)
24
+ end
25
+
26
+ # Get a sub-image for a layer
27
+ # @param layer [Layer] The XR layer
28
+ # @param frame [Frame] The current XR frame
29
+ # @param eye [String] The eye ("left", "right", "none")
30
+ # @return [WebGLSubImage]
31
+ def sub_image(layer, frame, eye = Eye::NONE)
32
+ js_subimage = js_call(:getSubImage, layer.js, frame.js, eye)
33
+ WebGLSubImage.new(js_subimage)
34
+ end
35
+
36
+ # Get a sub-image for a specific view
37
+ # @param layer [Layer] The XR layer
38
+ # @param view [View] The XR view
39
+ # @return [WebGLSubImage]
40
+ def view_sub_image(layer, view)
41
+ js_subimage = js_call(:getViewSubImage, layer.js, view.js)
42
+ WebGLSubImage.new(js_subimage)
43
+ end
44
+
45
+ # Get the reflection cube map
46
+ # @param light_probe [Lighting::Probe] The light probe
47
+ # @return [JS::Object, nil] WebGL texture or nil
48
+ def reflection_cube_map(light_probe)
49
+ js_call(:getReflectionCubeMap, light_probe.js)
50
+ end
51
+
52
+ # Get the camera image for AR
53
+ # @param view [View] The XR view
54
+ # @return [JS::Object, nil] WebGL texture or nil
55
+ def camera_image(view)
56
+ js_call(:getCameraImage, view.js)
57
+ end
58
+
59
+ # Get depth information for a view
60
+ # @param view [View] The XR view
61
+ # @return [Depth::WebGLInformation, nil]
62
+ def depth_information(view)
63
+ js_info = js_call(:getDepthInformation, view.js)
64
+ return nil if js_info.nil?
65
+
66
+ Depth::WebGLInformation.new(js_info)
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WebXR
4
+ # XRWebGLLayer - WebGL rendering layer for XR
5
+ class WebGLLayer < Layer
6
+ # Create a new WebGL layer
7
+ # @param session [Session] The XR session
8
+ # @param context [JS::Object] The WebGL rendering context
9
+ # @param antialias [Boolean] Enable antialiasing
10
+ # @param depth [Boolean] Enable depth buffer
11
+ # @param stencil [Boolean] Enable stencil buffer
12
+ # @param alpha [Boolean] Enable alpha channel
13
+ # @param ignore_depth_values [Boolean] Ignore compositor depth values
14
+ # @param framebuffer_scale_factor [Float] Framebuffer scale factor
15
+ def initialize(session, context, antialias: true, depth: true, stencil: false,
16
+ alpha: true, ignore_depth_values: false, framebuffer_scale_factor: 1.0)
17
+ js_options = JS.eval("({})")
18
+ js_options[:antialias] = antialias
19
+ js_options[:depth] = depth
20
+ js_options[:stencil] = stencil
21
+ js_options[:alpha] = alpha
22
+ js_options[:ignoreDepthValues] = ignore_depth_values
23
+ js_options[:framebufferScaleFactor] = framebuffer_scale_factor
24
+
25
+ js_layer = JS.global[:XRWebGLLayer].new(session.js, context, js_options)
26
+ super(js_layer)
27
+ @context = context
28
+ end
29
+
30
+ # Wrap an existing JS XRWebGLLayer
31
+ # @param js_layer [JS::Object] The JavaScript XRWebGLLayer
32
+ # @return [WebGLLayer]
33
+ def self.wrap(js_layer)
34
+ layer = allocate
35
+ layer.instance_variable_set(:@js, js_layer)
36
+ layer
37
+ end
38
+
39
+ # Get the native framebuffer scale factor for a session
40
+ # @param session [Session] The XR session
41
+ # @return [Float]
42
+ def self.native_framebuffer_scale_factor(session)
43
+ JS.global[:XRWebGLLayer].call(:getNativeFramebufferScaleFactor, session.js).to_f
44
+ end
45
+
46
+ # Check if antialiasing is enabled
47
+ # @return [Boolean]
48
+ def antialias?
49
+ js_bool(:antialias)
50
+ end
51
+
52
+ # Check if depth values are ignored
53
+ # @return [Boolean]
54
+ def ignore_depth_values?
55
+ js_bool(:ignoreDepthValues)
56
+ end
57
+
58
+ # Get the fixed foveation level
59
+ # @return [Float, nil] 0.0 to 1.0, or nil if not supported
60
+ def fixed_foveation
61
+ js_float(:fixedFoveation)
62
+ end
63
+
64
+ # Set the fixed foveation level
65
+ # @param value [Float] 0.0 (no foveation) to 1.0 (maximum foveation)
66
+ def fixed_foveation=(value)
67
+ @js[:fixedFoveation] = value
68
+ end
69
+
70
+ # Get the WebGL framebuffer
71
+ # @return [JS::Object] The WebGL framebuffer
72
+ def framebuffer
73
+ js_prop(:framebuffer)
74
+ end
75
+
76
+ # Get the framebuffer width in pixels
77
+ # @return [Integer]
78
+ def framebuffer_width
79
+ js_int(:framebufferWidth) || 0
80
+ end
81
+
82
+ # Get the framebuffer height in pixels
83
+ # @return [Integer]
84
+ def framebuffer_height
85
+ js_int(:framebufferHeight) || 0
86
+ end
87
+
88
+ # Get the viewport for a specific view
89
+ # @param view [View] The XR view
90
+ # @return [Viewport]
91
+ def viewport(view)
92
+ js_viewport = js_call(:getViewport, view.js)
93
+ Viewport.new(js_viewport)
94
+ end
95
+
96
+ # Get the framebuffer dimensions as a hash
97
+ # @return [Hash] Hash with :width, :height keys
98
+ def framebuffer_size
99
+ { width: framebuffer_width, height: framebuffer_height }
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WebXR
4
+ # XRWebGLSubImage - Represents a sub-image of a WebGL layer
5
+ class WebGLSubImage < JSWrapper
6
+ # @param js_subimage [JS::Object] The XRWebGLSubImage JavaScript object
7
+ def initialize(js_subimage)
8
+ super(js_subimage)
9
+ end
10
+
11
+ # Get the color texture
12
+ # @return [JS::Object] WebGL texture
13
+ def color_texture
14
+ js_prop(:colorTexture)
15
+ end
16
+
17
+ # Get the depth/stencil texture
18
+ # @return [JS::Object, nil] WebGL texture or nil
19
+ def depth_stencil_texture
20
+ js_prop(:depthStencilTexture)
21
+ end
22
+
23
+ # Get the motion vector texture
24
+ # @return [JS::Object, nil] WebGL texture or nil
25
+ def motion_vector_texture
26
+ js_prop(:motionVectorTexture)
27
+ end
28
+
29
+ # Get the image index (for texture arrays)
30
+ # @return [Integer, nil]
31
+ def image_index
32
+ js_int(:imageIndex)
33
+ end
34
+
35
+ # Get the texture width
36
+ # @return [Integer]
37
+ def texture_width
38
+ js_int(:textureWidth) || 0
39
+ end
40
+
41
+ # Get the texture height
42
+ # @return [Integer]
43
+ def texture_height
44
+ js_int(:textureHeight) || 0
45
+ end
46
+
47
+ # Get the viewport for this sub-image
48
+ # @return [Viewport]
49
+ def viewport
50
+ Viewport.new(js_prop(:viewport))
51
+ end
52
+
53
+ # Get the texture size as a hash
54
+ # @return [Hash] Hash with :width, :height keys
55
+ def texture_size
56
+ { width: texture_width, height: texture_height }
57
+ end
58
+ end
59
+ end