three-rb 0.1.0 → 0.2.1

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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +33 -1
  3. data/README.md +66 -3
  4. data/docs/browser-runtime.md +92 -24
  5. data/docs/loaded-assets-design.md +1 -1
  6. data/docs/next-work.md +9 -5
  7. data/docs/publishing.md +119 -23
  8. data/docs/release-readiness.md +5 -3
  9. data/docs/standalone-browser-app.md +106 -0
  10. data/examples/browser/README.md +8 -0
  11. data/examples/browser/composition/main.rb +44 -64
  12. data/examples/browser/cube/main.rb +4 -34
  13. data/examples/browser/cubemap/assets/checker.svg +11 -0
  14. data/examples/browser/cubemap/main.rb +17 -40
  15. data/examples/browser/gltf/main.rb +30 -50
  16. data/examples/browser/picking/main.rb +27 -53
  17. data/examples/browser/postprocessing/main.rb +23 -42
  18. data/examples/browser/primitives/assets/checker.svg +11 -0
  19. data/examples/browser/primitives/main.rb +19 -42
  20. data/examples/browser/ruby/README.md +24 -0
  21. data/examples/browser/ruby/boot.mjs +6 -0
  22. data/examples/browser/ruby/index.html +142 -0
  23. data/examples/browser/ruby/main.rb +313 -0
  24. data/examples/browser/ruby/smoke_test.mjs +126 -0
  25. data/examples/browser/serialization/assets/checker.svg +11 -0
  26. data/examples/browser/serialization/main.rb +20 -42
  27. data/examples/browser/shared/boot.mjs +37 -5
  28. data/examples/browser/textures/assets/checker.svg +11 -0
  29. data/examples/browser/textures/assets/studio.hdr +5 -0
  30. data/examples/browser/textures/main.rb +23 -41
  31. data/exe/three-rb +56 -0
  32. data/lib/three/backends/threejs/materialization.rb +6 -0
  33. data/lib/three/backends/threejs/parameters.rb +17 -0
  34. data/lib/three/backends/threejs/ruby_wasm_adapter.rb +166 -59
  35. data/lib/three/backends/threejs/synchronization.rb +38 -4
  36. data/lib/three/backends/threejs.rb +24 -0
  37. data/lib/three/browser.rb +389 -0
  38. data/lib/three/constants.rb +6 -0
  39. data/lib/three/core/buffer_attribute.rb +5 -1
  40. data/lib/three/core/buffer_geometry.rb +29 -1
  41. data/lib/three/core/object3d.rb +39 -1
  42. data/lib/three/exporters/three_json_exporter.rb +3 -0
  43. data/lib/three/generators/browser_example.rb +396 -0
  44. data/lib/three/geometries/text_geometry.rb +41 -0
  45. data/lib/three/loaders/font_loader.rb +29 -0
  46. data/lib/three/loaders/three_json_loader.rb +92 -46
  47. data/lib/three/materials/material.rb +2 -1
  48. data/lib/three/math/matrix4.rb +27 -0
  49. data/lib/three/renderers/threejs_renderer.rb +19 -0
  50. data/lib/three/scenes/fog.rb +86 -0
  51. data/lib/three/scenes/scene.rb +19 -1
  52. data/lib/three/textures/texture.rb +2 -1
  53. data/lib/three/version.rb +1 -1
  54. data/lib/three.rb +4 -0
  55. data/package.json +2 -1
  56. metadata +26 -8
  57. /data/examples/browser/{assets → composition/assets}/checker.svg +0 -0
  58. /data/examples/browser/{assets → gltf/assets}/animated_triangle.gltf +0 -0
  59. /data/examples/browser/{assets → gltf/assets}/compressed_triangle.gltf +0 -0
  60. /data/examples/browser/{assets → gltf/assets}/triangle.gltf +0 -0
  61. /data/examples/browser/{assets → ruby/assets}/studio.hdr +0 -0
@@ -0,0 +1,106 @@
1
+ # Standalone Browser App
2
+
3
+ This guide shows how to make a small browser app outside the three-rb repository with your own Ruby entrypoint.
4
+
5
+ The current browser runtime is still alpha. A standalone app needs a small JavaScript boot file because the browser must start ruby.wasm, expose three.js constructors, and then load your Ruby file over HTTP.
6
+
7
+ ## Generate The App
8
+
9
+ Install the gem, create a project directory, and generate a Ruby-only browser example:
10
+
11
+ ```sh
12
+ mkdir hello-three-rb
13
+ cd hello-three-rb
14
+
15
+ gem install three-rb
16
+ three-rb browser examples/browser/quickstart
17
+ ```
18
+
19
+ If you installed through Bundler, run:
20
+
21
+ ```sh
22
+ bundle exec three-rb browser examples/browser/quickstart
23
+ ```
24
+
25
+ Use `three-rb browser examples/browser/ruby` instead when you want the richer Ruby gemstone sample. It generates the same browser runtime shape and keeps its HDR file under `examples/browser/ruby/assets/`.
26
+
27
+ The generator creates:
28
+
29
+ - `package.json` and `pnpm-lock.yaml`
30
+ - `lib/`, copied from the installed gem
31
+ - `examples/browser/shared/boot.mjs`
32
+ - `examples/browser/quickstart/index.html`
33
+ - `examples/browser/quickstart/boot.mjs`
34
+ - `examples/browser/quickstart/main.rb`
35
+ - `examples/browser/quickstart/README.md`
36
+
37
+ For templates that need assets, the generator also creates an `assets/` directory inside the example.
38
+
39
+ Copying `lib/` puts the installed gem's Ruby source in the served app directory. The browser Ruby VM loads Ruby files over HTTP, so this is the current standalone workflow.
40
+
41
+ Pass `--force` only when you want to overwrite generated example files:
42
+
43
+ ```sh
44
+ three-rb browser examples/browser/quickstart --force
45
+ ```
46
+
47
+ ## Ruby Entrypoint
48
+
49
+ The generated `examples/browser/quickstart/main.rb` is plain Ruby scene code. It does not require `js` or call `JS.global`:
50
+
51
+ ```ruby
52
+ # frozen_string_literal: true
53
+
54
+ require_relative "../../../lib/three"
55
+
56
+ Three::Browser.run(starting: "Starting Ruby scene") do |app|
57
+ scene = Three::Scene.new
58
+ camera = Three::PerspectiveCamera.new(70, aspect: 1.0, near: 0.1, far: 100)
59
+ camera.position.z = 3
60
+
61
+ cube = Three::Mesh.new(
62
+ Three::BoxGeometry.new(1, 1, 1),
63
+ Three::MeshBasicMaterial.new(color: 0x4ed08f)
64
+ )
65
+ scene.add(cube)
66
+
67
+ renderer = Three::Renderers::ThreeJSRenderer.new(
68
+ canvas: "#scene",
69
+ antialias: true,
70
+ alpha: false
71
+ )
72
+ renderer.set_clear_color(0x101418, 1)
73
+
74
+ app.resize_renderer(renderer, camera)
75
+ renderer.render(scene, camera)
76
+
77
+ app.animation_loop(renderer) do
78
+ cube.rotation.x += 0.01
79
+ cube.rotation.y += 0.015
80
+ renderer.render(scene, camera)
81
+ end
82
+ end
83
+ ```
84
+
85
+ Keep ordinary scene code inside `Three::Browser.run`. Use `app.resize_renderer(renderer, camera)` for responsive canvas sizing, `app.animation_loop(renderer)` for animation, and `app.on_key`, `app.on_pointer`, `app.pointer_ndc`, or `app.storage` before reaching for direct JavaScript access.
86
+
87
+ ## Run It
88
+
89
+ Install browser packages, serve the app directory, and open the page:
90
+
91
+ ```sh
92
+ pnpm install
93
+ ruby -run -e httpd . -p 8000
94
+ ```
95
+
96
+ If Ruby reports that `webrick` is not found, install it once with `gem install webrick`. Ruby 3.0 and later no longer include WEBrick as a standard library.
97
+
98
+ ```text
99
+ http://localhost:8000/examples/browser/quickstart/
100
+ ```
101
+
102
+ Use `http://localhost:8000/...`; do not open the files with `file://`, because the runtime loads ES modules, wasm, Ruby files, and assets over HTTP.
103
+
104
+ ## When JavaScript Is Still Involved
105
+
106
+ The generated app still includes a JavaScript boot file. That file imports three.js, registers addon constructors, starts ruby.wasm, and loads the Ruby entrypoint. Application scene code should not need `require "js"` unless it reaches outside the current three-rb browser API into custom browser APIs, unwrapped three.js addons, or application-specific JavaScript integrations. Use `Three::Browser.js` for that explicit escape hatch and keep it isolated.
@@ -2,6 +2,8 @@
2
2
 
3
3
  These examples are the browser-facing coverage map for three-rb's browser-first alpha. They run Ruby through ruby.wasm, load pnpm-managed three.js packages, and render through `Three::Renderers::ThreeJSRenderer`.
4
4
 
5
+ Examples that need fixtures keep those files under their own `assets/` directory so each sample remains self-contained.
6
+
5
7
  ## Run Examples
6
8
 
7
9
  Install browser dependencies and serve the repository root:
@@ -11,9 +13,12 @@ pnpm install
11
13
  ruby -run -e httpd . -p 8000
12
14
  ```
13
15
 
16
+ If Ruby reports that `webrick` is not found, install it once with `gem install webrick`. Ruby 3.0 and later no longer include WEBrick as a standard library.
17
+
14
18
  Open an example URL:
15
19
 
16
20
  ```text
21
+ http://localhost:8000/examples/browser/ruby/
17
22
  http://localhost:8000/examples/browser/cube/
18
23
  http://localhost:8000/examples/browser/composition/
19
24
  http://localhost:8000/examples/browser/textures/
@@ -39,6 +44,7 @@ Run one smoke test by using the command listed in the table below.
39
44
 
40
45
  | Example | Primary APIs covered | Smoke command | Why it exists |
41
46
  | --- | --- | --- | --- |
47
+ | `examples/browser/ruby/` | `BufferGeometry`, `Float32BufferAttribute`, `MeshPhysicalMaterial`, `RGBELoader`, `FontLoader`, `TextGeometry`, `OrbitControls`, shadows | `pnpm test:browser:ruby` | Provides the first visual sample: a Ruby-authored faceted red gemstone with a three-dimensional `three-rb` title. |
42
48
  | `examples/browser/cube/` | `Scene`, `PerspectiveCamera`, `BoxGeometry`, `Mesh`, `MeshBasicMaterial`, `ThreeJSRenderer`, animation loop | `pnpm test:browser:cube` | Verifies the smallest Ruby-authored scene can boot through ruby.wasm and draw nonblank WebGL pixels through the three.js renderer path. |
43
49
  | `examples/browser/composition/` | `OrthographicCamera`, ambient/directional/point/hemisphere lights, shadows, `ShadowMaterial`, `Group`, `InstancedMesh`, `TextureLoader`, `OrbitControls`, material/texture disposal | `pnpm test:browser:composition` | Exercises the broad scene-composition path used by richer browser scenes, including dynamic material updates and camera controls. |
44
50
  | `examples/browser/textures/` | `TextureLoader`, `RGBELoader`, repeat/wrap/filter/UV-transform settings, `MeshPhysicalMaterial`, `MeshMatcapMaterial`, `MeshToonMaterial`, physical, matcap, and toon texture slots, scene environment | `pnpm test:browser:textures` | Verifies browser texture loading, HDR environment synchronization, and the current material texture bridge. |
@@ -52,3 +58,5 @@ Run one smoke test by using the command listed in the table below.
52
58
  ## Adding Browser Coverage
53
59
 
54
60
  New browser-facing features should add or extend one of these examples and include deterministic smoke coverage. Prefer extending an existing example when the feature strengthens the same workflow; add a new example when it introduces a distinct API surface such as a new loader family, render target workflow, or postprocessing pipeline.
61
+
62
+ Keep new Ruby entrypoints Ruby-only: use `Three::Browser.run`, `app.resize_renderer`, `app.on_resize`, `app.animation_loop`, `app.element`, `app.on_key`, `app.on_pointer`, `app.pointer_ndc`, `app.storage`, and `app.expose` instead of `require "js"` or direct `JS.global` calls. If a feature needs browser or three.js APIs that are not wrapped yet, add a small Ruby helper or backend method first. Use `Three::Browser.js` only as an explicit escape hatch and keep it isolated from scene construction code.
@@ -1,19 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "js"
4
-
5
- begin
6
- JS.global[:__threeReady].await
7
-
8
- require_relative "../../../lib/three"
9
-
10
- document = JS.global[:document]
11
- window = JS.global[:window]
12
- viewport = document.call(:querySelector, "#viewport")
13
- status = document.call(:querySelector, "#status")
14
- status_dot = document.call(:querySelector, "#status-dot")
15
- status[:textContent] = "Starting Ruby scene"
3
+ require_relative "../../../lib/three"
16
4
 
5
+ Three::Browser.run(starting: "Starting Ruby scene") do |app|
17
6
  scene = Three::Scene.new
18
7
  camera = Three::OrthographicCamera.new(-3, 3, 1.8, -1.8, near: 0.1, far: 100)
19
8
  camera.position.z = 5
@@ -52,7 +41,7 @@ begin
52
41
  rig.name = "composition-rig"
53
42
  scene.add(rig)
54
43
 
55
- primary_texture = Three::Loaders::TextureLoader.new.load("/examples/browser/assets/checker.svg")
44
+ primary_texture = Three::Loaders::TextureLoader.new.load("/examples/browser/composition/assets/checker.svg")
56
45
  primary_texture.wrap_s = Three::RepeatWrapping
57
46
  primary_texture.wrap_t = Three::RepeatWrapping
58
47
  primary_texture.mag_filter = Three::NearestFilter
@@ -131,9 +120,7 @@ begin
131
120
  )
132
121
  controls.target.set(0, 0, 0)
133
122
 
134
- resize = proc do
135
- width = [viewport[:clientWidth].to_i, 1].max
136
- height = [viewport[:clientHeight].to_i, 1].max
123
+ app.resize_renderer(renderer, camera) do |width, height, _aspect|
137
124
  view_height = 3.8
138
125
  view_width = view_height * width.to_f / height
139
126
 
@@ -142,51 +129,50 @@ begin
142
129
  camera.top = view_height / 2
143
130
  camera.bottom = -view_height / 2
144
131
  camera.update_projection_matrix
145
- renderer.set_size(width, height)
146
132
  end
147
-
148
- resize.call
149
- window.call(:addEventListener, "resize", resize)
150
133
  renderer.render(scene, camera)
151
134
 
152
- JS.global[:__threeRbRenderer] = renderer.handle
153
- JS.global[:__threeRbControls] = controls.handle
154
- JS.global[:__threeRbScene] = renderer.backend.materialize(scene)
155
- JS.global[:__threeRbCamera] = renderer.backend.materialize(camera)
156
- JS.global[:__threeRbAmbientLight] = renderer.backend.materialize(ambient_light)
157
- JS.global[:__threeRbDirectionalLight] = renderer.backend.materialize(key_light)
158
- JS.global[:__threeRbPointLight] = renderer.backend.materialize(point_light)
159
- JS.global[:__threeRbHemisphereLight] = renderer.backend.materialize(hemisphere_light)
160
- JS.global[:__threeRbPlane] = renderer.backend.materialize(backdrop)
161
- JS.global[:__threeRbShadowCatcher] = renderer.backend.materialize(shadow_catcher)
162
- JS.global[:__threeRbShadowMaterial] = renderer.backend.materialize(shadow_material)
163
- JS.global[:__threeRbRig] = renderer.backend.materialize(rig)
164
- JS.global[:__threeRbPrimaryMesh] = renderer.backend.materialize(primary)
165
- JS.global[:__threeRbSatelliteMesh] = renderer.backend.materialize(satellite)
166
- JS.global[:__threeRbSphereMesh] = renderer.backend.materialize(orb)
167
- JS.global[:__threeRbPhongMesh] = renderer.backend.materialize(highlight)
168
- JS.global[:__threeRbInstancedMesh] = renderer.backend.materialize(instanced_field)
169
- JS.global[:__threeRbInstancedMaterial] = renderer.backend.materialize(instanced_material)
170
- JS.global[:__threeRbTexture] = renderer.backend.materialize(primary_texture)
171
- JS.global[:__threeRbChangingMaterial] = renderer.backend.materialize(primary_material)
172
- JS.global[:__threeRbLambertMaterial] = renderer.backend.materialize(primary_material)
173
- JS.global[:__threeRbNormalMaterial] = renderer.backend.materialize(satellite_material)
174
- JS.global[:__threeRbStandardMaterial] = renderer.backend.materialize(orb_material)
175
- JS.global[:__threeRbPhongMaterial] = renderer.backend.materialize(highlight_material)
176
- JS.global[:__threeRbMaterialDisposeEvent] = false
177
- JS.global[:__threeRbTextureDisposeEvent] = false
178
-
179
- disposable_texture = Three::Loaders::TextureLoader.new.load("/examples/browser/assets/checker.svg")
135
+ app.expose(
136
+ {
137
+ renderer: renderer,
138
+ controls: controls,
139
+ scene: scene,
140
+ camera: camera,
141
+ ambient_light: ambient_light,
142
+ directional_light: key_light,
143
+ point_light: point_light,
144
+ hemisphere_light: hemisphere_light,
145
+ plane: backdrop,
146
+ shadow_catcher: shadow_catcher,
147
+ shadow_material: shadow_material,
148
+ rig: rig,
149
+ primary_mesh: primary,
150
+ satellite_mesh: satellite,
151
+ sphere_mesh: orb,
152
+ phong_mesh: highlight,
153
+ instanced_mesh: instanced_field,
154
+ instanced_material: instanced_material,
155
+ texture: primary_texture,
156
+ changing_material: primary_material,
157
+ lambert_material: primary_material,
158
+ normal_material: satellite_material,
159
+ standard_material: orb_material,
160
+ phong_material: highlight_material,
161
+ material_dispose_event: false,
162
+ texture_dispose_event: false
163
+ },
164
+ renderer: renderer
165
+ )
166
+
167
+ disposable_texture = Three::Loaders::TextureLoader.new.load("/examples/browser/composition/assets/checker.svg")
180
168
  disposable_material = Three::MeshBasicMaterial.new(map: disposable_texture)
181
- disposable_texture_handle = renderer.backend.materialize(disposable_texture)
182
- disposable_material_handle = renderer.backend.materialize(disposable_material)
183
- disposable_texture_handle.call(:addEventListener, "dispose", proc { JS.global[:__threeRbTextureDisposeEvent] = true })
184
- disposable_material_handle.call(:addEventListener, "dispose", proc { JS.global[:__threeRbMaterialDisposeEvent] = true })
169
+ renderer.on_dispose(disposable_texture) { app.set(:texture_dispose_event, true) }
170
+ renderer.on_dispose(disposable_material) { app.set(:material_dispose_event, true) }
185
171
  renderer.dispose(disposable_material, dispose_textures: true)
186
- JS.global[:__threeRbMaterialHandleCachedAfterDispose] = renderer.backend.handles.key?(disposable_material.uuid)
187
- JS.global[:__threeRbTextureHandleCachedAfterDispose] = renderer.backend.handles.key?(disposable_texture.uuid)
188
- JS.global[:__threeRbInitialMaterialColor] = primary_material.color.hex
189
- JS.global[:__threeRbCompositionFrame] = 0
172
+ app.set(:material_handle_cached_after_dispose, renderer.cached?(disposable_material))
173
+ app.set(:texture_handle_cached_after_dispose, renderer.cached?(disposable_texture))
174
+ app.set(:initial_material_color, primary_material.color.hex)
175
+ app.set(:composition_frame, 0)
190
176
 
191
177
  frame = 0
192
178
  renderer.animation_loop do
@@ -203,14 +189,8 @@ begin
203
189
  pulse = (Math.sin(frame * 0.045) + 1) / 2.0
204
190
  primary_material.color.set_rgb(0.25 + (0.35 * pulse), 0.55 + (0.25 * pulse), 0.42)
205
191
 
206
- JS.global[:__threeRbCompositionFrame] = frame
192
+ app.set(:composition_frame, frame)
207
193
  controls.update
208
194
  renderer.render(scene, camera)
209
195
  end
210
-
211
- status[:textContent] = "Running"
212
- status_dot[:dataset][:state] = "running"
213
- rescue StandardError => error
214
- JS.global.call(:__threeRbBootFailed, error.message) if JS.global[:__threeRbBootFailed]
215
- raise
216
196
  end
@@ -1,19 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "js"
4
-
5
- begin
6
- JS.global[:__threeReady].await
7
-
8
- require_relative "../../../lib/three"
9
-
10
- document = JS.global[:document]
11
- window = JS.global[:window]
12
- viewport = document.call(:querySelector, "#viewport")
13
- status = document.call(:querySelector, "#status")
14
- status_dot = document.call(:querySelector, "#status-dot")
15
- status[:textContent] = "Starting Ruby scene"
3
+ require_relative "../../../lib/three"
16
4
 
5
+ Three::Browser.run(starting: "Starting Ruby scene") do |app|
17
6
  scene = Three::Scene.new
18
7
  camera = Three::PerspectiveCamera.new(70, aspect: 1.0, near: 0.1, far: 100)
19
8
  camera.position.z = 3
@@ -31,32 +20,13 @@ begin
31
20
  )
32
21
  renderer.set_clear_color(0x101418, 1)
33
22
 
34
- resize = proc do
35
- width = [viewport[:clientWidth].to_i, 1].max
36
- height = [viewport[:clientHeight].to_i, 1].max
37
-
38
- camera.aspect = width.to_f / height
39
- camera.update_projection_matrix
40
- renderer.set_size(width, height)
41
- end
42
-
43
- resize.call
44
- window.call(:addEventListener, "resize", resize)
23
+ app.resize_renderer(renderer, camera)
45
24
  renderer.render(scene, camera)
46
- JS.global[:__threeRbRenderer] = renderer.handle
47
- JS.global[:__threeRbScene] = renderer.backend.materialize(scene)
48
- JS.global[:__threeRbCamera] = renderer.backend.materialize(camera)
49
- JS.global[:__threeRbCube] = renderer.backend.materialize(cube)
25
+ app.expose({ renderer: renderer, scene: scene, camera: camera, cube: cube }, renderer: renderer)
50
26
 
51
27
  renderer.animation_loop do
52
28
  cube.rotation.x += 0.01
53
29
  cube.rotation.y += 0.015
54
30
  renderer.render(scene, camera)
55
31
  end
56
-
57
- status[:textContent] = "Running"
58
- status_dot[:dataset][:state] = "running"
59
- rescue StandardError => error
60
- JS.global.call(:__threeRbBootFailed, error.message) if JS.global[:__threeRbBootFailed]
61
- raise
62
32
  end
@@ -0,0 +1,11 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128">
2
+ <rect width="128" height="128" fill="#eef3f7"/>
3
+ <rect width="32" height="32" fill="#22313f"/>
4
+ <rect x="64" width="32" height="32" fill="#22313f"/>
5
+ <rect x="32" y="32" width="32" height="32" fill="#6ed69a"/>
6
+ <rect x="96" y="32" width="32" height="32" fill="#6ed69a"/>
7
+ <rect y="64" width="32" height="32" fill="#22313f"/>
8
+ <rect x="64" y="64" width="32" height="32" fill="#22313f"/>
9
+ <rect x="32" y="96" width="32" height="32" fill="#6ed69a"/>
10
+ <rect x="96" y="96" width="32" height="32" fill="#6ed69a"/>
11
+ </svg>
@@ -1,24 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "js"
4
-
5
- begin
6
- JS.global[:__threeReady].await
7
-
8
- require_relative "../../../lib/three"
9
-
10
- document = JS.global[:document]
11
- window = JS.global[:window]
12
- viewport = document.call(:querySelector, "#viewport")
13
- status = document.call(:querySelector, "#status")
14
- status_dot = document.call(:querySelector, "#status-dot")
15
- status[:textContent] = "Starting Ruby scene"
3
+ require_relative "../../../lib/three"
16
4
 
5
+ Three::Browser.run(starting: "Starting Ruby scene") do |app|
17
6
  scene = Three::Scene.new
18
7
  camera = Three::PerspectiveCamera.new(45, aspect: 1.0, near: 0.1, far: 100)
19
8
  camera.position.set(0, 0.7, 4.0)
20
9
 
21
- cube_sources = Array.new(6, "/examples/browser/assets/checker.svg")
10
+ cube_sources = Array.new(6, "/examples/browser/cubemap/assets/checker.svg")
22
11
  cube_texture = Three::Loaders::CubeTextureLoader.new.load(cube_sources)
23
12
  scene.background = cube_texture
24
13
  scene.environment = cube_texture
@@ -46,39 +35,27 @@ begin
46
35
  )
47
36
  renderer.set_clear_color(0x10151b, 1)
48
37
 
49
- resize = proc do
50
- width = [viewport[:clientWidth].to_i, 1].max
51
- height = [viewport[:clientHeight].to_i, 1].max
52
-
53
- camera.aspect = width.to_f / height
54
- camera.update_projection_matrix
55
- renderer.set_size(width, height)
56
- end
57
-
58
- resize.call
59
- window.call(:addEventListener, "resize", resize)
38
+ app.resize_renderer(renderer, camera)
60
39
  renderer.render(scene, camera)
61
-
62
- JS.global[:__threeRbRenderer] = renderer.handle
63
- JS.global[:__threeRbCubemapScene] = renderer.backend.materialize(scene)
64
- JS.global[:__threeRbCamera] = renderer.backend.materialize(camera)
65
- JS.global[:__threeRbCubemapMesh] = renderer.backend.materialize(mesh)
66
- JS.global[:__threeRbCubemapMaterial] = renderer.backend.materialize(material)
67
- JS.global[:__threeRbCubeTexture] = renderer.backend.materialize(cube_texture)
68
- JS.global[:__threeRbCubemapFrame] = 0
40
+ app.expose(
41
+ {
42
+ renderer: renderer,
43
+ cubemap_scene: scene,
44
+ camera: camera,
45
+ cubemap_mesh: mesh,
46
+ cubemap_material: material,
47
+ cube_texture: cube_texture,
48
+ cubemap_frame: 0
49
+ },
50
+ renderer: renderer
51
+ )
69
52
 
70
53
  frame = 0
71
54
  renderer.animation_loop do
72
55
  frame += 1
73
56
  mesh.rotation.y += 0.01
74
57
  mesh.rotation.x = Math.sin(frame * 0.02) * 0.08
75
- JS.global[:__threeRbCubemapFrame] = frame
58
+ app.set(:cubemap_frame, frame)
76
59
  renderer.render(scene, camera)
77
60
  end
78
-
79
- status[:textContent] = "Running"
80
- status_dot[:dataset][:state] = "running"
81
- rescue StandardError => error
82
- JS.global.call(:__threeRbBootFailed, error.message) if JS.global[:__threeRbBootFailed]
83
- raise
84
61
  end
@@ -1,19 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "js"
4
-
5
- begin
6
- JS.global[:__threeReady].await
7
-
8
- require_relative "../../../lib/three"
9
-
10
- document = JS.global[:document]
11
- window = JS.global[:window]
12
- viewport = document.call(:querySelector, "#viewport")
13
- status = document.call(:querySelector, "#status")
14
- status_dot = document.call(:querySelector, "#status-dot")
15
- status[:textContent] = "Starting Ruby scene"
3
+ require_relative "../../../lib/three"
16
4
 
5
+ Three::Browser.run(starting: "Starting Ruby scene") do |app|
17
6
  scene = Three::Scene.new
18
7
  camera = Three::PerspectiveCamera.new(45, aspect: 1.0, near: 0.1, far: 100)
19
8
  camera.position.set(0, 0.15, 4.0)
@@ -32,7 +21,7 @@ begin
32
21
  )
33
22
  renderer.set_clear_color(0x11151a, 1)
34
23
 
35
- gltf = Three::Loaders::GLTFLoader.new(backend: renderer.backend).load("/examples/browser/assets/animated_triangle.gltf")
24
+ gltf = Three::Loaders::GLTFLoader.new(backend: renderer.backend).load("/examples/browser/gltf/assets/animated_triangle.gltf")
36
25
  model = gltf.scene
37
26
  model.position.x = -0.75
38
27
  model.scale.set(1.2, 1.2, 1.2)
@@ -42,7 +31,7 @@ begin
42
31
  compressed_gltf = Three::Loaders::GLTFLoader.new(
43
32
  backend: renderer.backend,
44
33
  draco_decoder_path: draco_decoder_path
45
- ).load("/examples/browser/assets/compressed_triangle.gltf")
34
+ ).load("/examples/browser/gltf/assets/compressed_triangle.gltf")
46
35
  compressed_model = compressed_gltf.scene
47
36
  compressed_model.position.x = 1.05
48
37
  compressed_model.scale.set(0.82, 0.82, 0.82)
@@ -53,53 +42,44 @@ begin
53
42
  action = mixer.clip_action(gltf.animations.first)
54
43
  action.play
55
44
 
56
- resize = proc do
57
- width = [viewport[:clientWidth].to_i, 1].max
58
- height = [viewport[:clientHeight].to_i, 1].max
59
-
60
- camera.aspect = width.to_f / height
61
- camera.update_projection_matrix
62
- renderer.set_size(width, height)
63
- end
64
-
65
- resize.call
66
- window.call(:addEventListener, "resize", resize)
45
+ app.resize_renderer(renderer, camera)
67
46
  renderer.render(scene, camera)
68
47
 
69
- JS.global[:__threeRbRenderer] = renderer.handle
70
- JS.global[:__threeRbGltfRootScene] = renderer.backend.materialize(scene)
71
- JS.global[:__threeRbCamera] = renderer.backend.materialize(camera)
72
- JS.global[:__threeRbGltfScene] = renderer.backend.materialize(model)
73
- JS.global[:__threeRbCompressedGltfScene] = renderer.backend.materialize(compressed_model)
74
- JS.global[:__threeRbCompressedGltfDecoderPath] = draco_decoder_path
75
- JS.global[:__threeRbGltfAnimations] = gltf.animations.length
76
- JS.global[:__threeRbGltfAnimationName] = gltf.animations.first&.name
77
- JS.global[:__threeRbGltfAnimationDuration] = gltf.animations.first&.duration
78
- JS.global[:__threeRbGltfMixer] = mixer.handle
79
- JS.global[:__threeRbGltfAction] = action.handle
80
- JS.global[:__threeRbGltfFrame] = 0
81
- JS.global[:__threeRbGltfAnimationTime] = 0
82
- JS.global[:__threeRbDisposeGltf] = proc do
48
+ app.expose(
49
+ {
50
+ renderer: renderer,
51
+ gltf_root_scene: scene,
52
+ camera: camera,
53
+ gltf_scene: model,
54
+ compressed_gltf_scene: compressed_model,
55
+ compressed_gltf_decoder_path: draco_decoder_path,
56
+ gltf_animations: gltf.animations.length,
57
+ gltf_animation_name: gltf.animations.first&.name,
58
+ gltf_animation_duration: gltf.animations.first&.duration,
59
+ gltf_mixer: mixer,
60
+ gltf_action: action,
61
+ gltf_frame: 0,
62
+ gltf_animation_time: 0
63
+ },
64
+ renderer: renderer
65
+ )
66
+ app.set(:dispose_gltf, proc do
83
67
  mixer.stop_all_action
84
68
  mixer.uncache_root
85
69
  renderer.dispose_subtree(model, remove: true, dispose_textures: true)
86
70
  renderer.dispose_subtree(compressed_model, remove: true, dispose_textures: true)
87
- JS.global[:__threeRbGltfDisposed] = true
88
- end
71
+ app.set(:gltf_disposed, true)
72
+ end)
89
73
 
90
74
  frame = 0
75
+ animation_time = 0
91
76
  renderer.animation_loop do
92
77
  frame += 1
93
78
  delta = clock.get_delta
94
79
  mixer.update(delta)
95
- JS.global[:__threeRbGltfAnimationTime] = JS.global[:__threeRbGltfAnimationTime].to_f + delta
96
- JS.global[:__threeRbGltfFrame] = frame
80
+ animation_time += delta
81
+ app.set(:gltf_animation_time, animation_time)
82
+ app.set(:gltf_frame, frame)
97
83
  renderer.render(scene, camera)
98
84
  end
99
-
100
- status[:textContent] = "Running"
101
- status_dot[:dataset][:state] = "running"
102
- rescue StandardError => error
103
- JS.global.call(:__threeRbBootFailed, error.message) if JS.global[:__threeRbBootFailed]
104
- raise
105
85
  end