three-rb 0.1.0 → 0.2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +21 -1
- data/README.md +64 -3
- data/docs/browser-runtime.md +90 -24
- data/docs/next-work.md +9 -5
- data/docs/publishing.md +116 -23
- data/docs/release-readiness.md +5 -3
- data/docs/standalone-browser-app.md +102 -0
- data/examples/browser/README.md +6 -0
- data/examples/browser/composition/main.rb +41 -61
- data/examples/browser/cube/main.rb +4 -34
- data/examples/browser/cubemap/main.rb +16 -39
- data/examples/browser/gltf/main.rb +28 -48
- data/examples/browser/picking/main.rb +27 -53
- data/examples/browser/postprocessing/main.rb +23 -42
- data/examples/browser/primitives/main.rb +18 -41
- data/examples/browser/ruby/README.md +22 -0
- data/examples/browser/ruby/boot.mjs +6 -0
- data/examples/browser/ruby/index.html +142 -0
- data/examples/browser/ruby/main.rb +313 -0
- data/examples/browser/ruby/smoke_test.mjs +126 -0
- data/examples/browser/serialization/main.rb +19 -41
- data/examples/browser/shared/boot.mjs +37 -5
- data/examples/browser/textures/main.rb +21 -39
- data/exe/three-rb +55 -0
- data/lib/three/backends/threejs/materialization.rb +6 -0
- data/lib/three/backends/threejs/parameters.rb +17 -0
- data/lib/three/backends/threejs/ruby_wasm_adapter.rb +166 -59
- data/lib/three/backends/threejs/synchronization.rb +38 -4
- data/lib/three/backends/threejs.rb +24 -0
- data/lib/three/browser.rb +389 -0
- data/lib/three/constants.rb +6 -0
- data/lib/three/core/buffer_attribute.rb +5 -1
- data/lib/three/core/buffer_geometry.rb +29 -1
- data/lib/three/core/object3d.rb +39 -1
- data/lib/three/exporters/three_json_exporter.rb +3 -0
- data/lib/three/generators/browser_example.rb +315 -0
- data/lib/three/geometries/text_geometry.rb +41 -0
- data/lib/three/loaders/font_loader.rb +29 -0
- data/lib/three/loaders/three_json_loader.rb +92 -46
- data/lib/three/materials/material.rb +2 -1
- data/lib/three/math/matrix4.rb +27 -0
- data/lib/three/renderers/threejs_renderer.rb +19 -0
- data/lib/three/scenes/fog.rb +86 -0
- data/lib/three/scenes/scene.rb +19 -1
- data/lib/three/textures/texture.rb +2 -1
- data/lib/three/version.rb +1 -1
- data/lib/three.rb +4 -0
- data/package.json +2 -1
- metadata +16 -3
|
@@ -0,0 +1,102 @@
|
|
|
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
|
+
The generator creates:
|
|
26
|
+
|
|
27
|
+
- `package.json` and `pnpm-lock.yaml`
|
|
28
|
+
- `lib/`, copied from the installed gem
|
|
29
|
+
- `examples/browser/shared/boot.mjs`
|
|
30
|
+
- `examples/browser/quickstart/index.html`
|
|
31
|
+
- `examples/browser/quickstart/boot.mjs`
|
|
32
|
+
- `examples/browser/quickstart/main.rb`
|
|
33
|
+
- `examples/browser/quickstart/README.md`
|
|
34
|
+
|
|
35
|
+
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.
|
|
36
|
+
|
|
37
|
+
Pass `--force` only when you want to overwrite generated example files:
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
three-rb browser examples/browser/quickstart --force
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Ruby Entrypoint
|
|
44
|
+
|
|
45
|
+
The generated `examples/browser/quickstart/main.rb` is plain Ruby scene code. It does not require `js` or call `JS.global`:
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
# frozen_string_literal: true
|
|
49
|
+
|
|
50
|
+
require_relative "../../../lib/three"
|
|
51
|
+
|
|
52
|
+
Three::Browser.run(starting: "Starting Ruby scene") do |app|
|
|
53
|
+
scene = Three::Scene.new
|
|
54
|
+
camera = Three::PerspectiveCamera.new(70, aspect: 1.0, near: 0.1, far: 100)
|
|
55
|
+
camera.position.z = 3
|
|
56
|
+
|
|
57
|
+
cube = Three::Mesh.new(
|
|
58
|
+
Three::BoxGeometry.new(1, 1, 1),
|
|
59
|
+
Three::MeshBasicMaterial.new(color: 0x4ed08f)
|
|
60
|
+
)
|
|
61
|
+
scene.add(cube)
|
|
62
|
+
|
|
63
|
+
renderer = Three::Renderers::ThreeJSRenderer.new(
|
|
64
|
+
canvas: "#scene",
|
|
65
|
+
antialias: true,
|
|
66
|
+
alpha: false
|
|
67
|
+
)
|
|
68
|
+
renderer.set_clear_color(0x101418, 1)
|
|
69
|
+
|
|
70
|
+
app.resize_renderer(renderer, camera)
|
|
71
|
+
renderer.render(scene, camera)
|
|
72
|
+
|
|
73
|
+
app.animation_loop(renderer) do
|
|
74
|
+
cube.rotation.x += 0.01
|
|
75
|
+
cube.rotation.y += 0.015
|
|
76
|
+
renderer.render(scene, camera)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
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.
|
|
82
|
+
|
|
83
|
+
## Run It
|
|
84
|
+
|
|
85
|
+
Install browser packages, serve the app directory, and open the page:
|
|
86
|
+
|
|
87
|
+
```sh
|
|
88
|
+
pnpm install
|
|
89
|
+
ruby -run -e httpd . -p 8000
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
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.
|
|
93
|
+
|
|
94
|
+
```text
|
|
95
|
+
http://localhost:8000/examples/browser/quickstart/
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Use `http://localhost:8000/...`; do not open the files with `file://`, because the runtime loads ES modules, wasm, Ruby files, and assets over HTTP.
|
|
99
|
+
|
|
100
|
+
## When JavaScript Is Still Involved
|
|
101
|
+
|
|
102
|
+
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.
|
data/examples/browser/README.md
CHANGED
|
@@ -11,9 +11,12 @@ pnpm install
|
|
|
11
11
|
ruby -run -e httpd . -p 8000
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
+
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.
|
|
15
|
+
|
|
14
16
|
Open an example URL:
|
|
15
17
|
|
|
16
18
|
```text
|
|
19
|
+
http://localhost:8000/examples/browser/ruby/
|
|
17
20
|
http://localhost:8000/examples/browser/cube/
|
|
18
21
|
http://localhost:8000/examples/browser/composition/
|
|
19
22
|
http://localhost:8000/examples/browser/textures/
|
|
@@ -39,6 +42,7 @@ Run one smoke test by using the command listed in the table below.
|
|
|
39
42
|
|
|
40
43
|
| Example | Primary APIs covered | Smoke command | Why it exists |
|
|
41
44
|
| --- | --- | --- | --- |
|
|
45
|
+
| `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
46
|
| `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
47
|
| `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
48
|
| `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 +56,5 @@ Run one smoke test by using the command listed in the table below.
|
|
|
52
56
|
## Adding Browser Coverage
|
|
53
57
|
|
|
54
58
|
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.
|
|
59
|
+
|
|
60
|
+
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
|
-
|
|
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
|
|
@@ -131,9 +120,7 @@ begin
|
|
|
131
120
|
)
|
|
132
121
|
controls.target.set(0, 0, 0)
|
|
133
122
|
|
|
134
|
-
|
|
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
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
+
)
|
|
178
166
|
|
|
179
167
|
disposable_texture = Three::Loaders::TextureLoader.new.load("/examples/browser/assets/checker.svg")
|
|
180
168
|
disposable_material = Three::MeshBasicMaterial.new(map: disposable_texture)
|
|
181
|
-
|
|
182
|
-
|
|
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
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
@@ -1,19 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
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)
|
|
@@ -46,39 +35,27 @@ begin
|
|
|
46
35
|
)
|
|
47
36
|
renderer.set_clear_color(0x10151b, 1)
|
|
48
37
|
|
|
49
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
@@ -53,53 +42,44 @@ begin
|
|
|
53
42
|
action = mixer.clip_action(gltf.animations.first)
|
|
54
43
|
action.play
|
|
55
44
|
|
|
56
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
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
|
-
|
|
96
|
-
|
|
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
|
|
@@ -1,19 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
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 picking scene"
|
|
3
|
+
require_relative "../../../lib/three"
|
|
16
4
|
|
|
5
|
+
Three::Browser.run(starting: "Starting picking scene") do |app|
|
|
17
6
|
scene = Three::Scene.new
|
|
18
7
|
camera = Three::PerspectiveCamera.new(60, aspect: 1.0, near: 0.1, far: 100)
|
|
19
8
|
camera.position.z = 4
|
|
@@ -40,25 +29,15 @@ begin
|
|
|
40
29
|
)
|
|
41
30
|
renderer.set_clear_color(0x101418, 1)
|
|
42
31
|
|
|
43
|
-
resize = proc do
|
|
44
|
-
width = [viewport[:clientWidth].to_i, 1].max
|
|
45
|
-
height = [viewport[:clientHeight].to_i, 1].max
|
|
46
|
-
|
|
47
|
-
camera.aspect = width.to_f / height
|
|
48
|
-
camera.update_projection_matrix
|
|
49
|
-
renderer.set_size(width, height)
|
|
50
|
-
end
|
|
51
|
-
|
|
52
32
|
pointer = Three::Vector2.new
|
|
53
33
|
raycaster = Three::Raycaster.new(backend: renderer.backend)
|
|
54
34
|
pickables = [left_cube, right_cube]
|
|
55
35
|
selected = nil
|
|
56
36
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
pointer.set(x, y)
|
|
37
|
+
app.resize_renderer(renderer, camera)
|
|
38
|
+
canvas = app.element(renderer.dom_element)
|
|
39
|
+
canvas.on("click") do |event|
|
|
40
|
+
pointer.set(*canvas.pointer_ndc(event))
|
|
62
41
|
raycaster.set_from_camera(pointer, camera)
|
|
63
42
|
hits = raycaster.intersect_objects(pickables, recursive: false)
|
|
64
43
|
hit = hits.find(&:object)
|
|
@@ -71,43 +50,38 @@ begin
|
|
|
71
50
|
if hit
|
|
72
51
|
selected = hit.object
|
|
73
52
|
selected.material.color.set_hex(picked_color)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
53
|
+
app.set(:picked_name, selected.name)
|
|
54
|
+
app.set(:picked_distance, hit.distance)
|
|
55
|
+
app.set(:picked_point, hit.point.to_a)
|
|
77
56
|
else
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
57
|
+
app.set(:picked_name, nil)
|
|
58
|
+
app.set(:picked_distance, nil)
|
|
59
|
+
app.set(:picked_point, nil)
|
|
81
60
|
end
|
|
82
61
|
|
|
83
|
-
|
|
62
|
+
app.increment(:pick_count)
|
|
84
63
|
renderer.render(scene, camera)
|
|
85
64
|
end
|
|
86
|
-
|
|
87
|
-
resize.call
|
|
88
|
-
window.call(:addEventListener, "resize", resize)
|
|
89
|
-
renderer.dom_element.call(:addEventListener, "click", pick)
|
|
90
65
|
renderer.render(scene, camera)
|
|
91
66
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
67
|
+
app.expose(
|
|
68
|
+
{
|
|
69
|
+
renderer: renderer,
|
|
70
|
+
scene: scene,
|
|
71
|
+
camera: camera,
|
|
72
|
+
left_cube: left_cube,
|
|
73
|
+
right_cube: right_cube,
|
|
74
|
+
raycaster: raycaster,
|
|
75
|
+
pick_count: 0,
|
|
76
|
+
picking_frame: 0
|
|
77
|
+
},
|
|
78
|
+
renderer: renderer
|
|
79
|
+
)
|
|
100
80
|
|
|
101
81
|
renderer.animation_loop do
|
|
102
|
-
|
|
82
|
+
app.increment(:picking_frame)
|
|
103
83
|
left_cube.rotation.y += 0.01
|
|
104
84
|
right_cube.rotation.y -= 0.01
|
|
105
85
|
renderer.render(scene, camera)
|
|
106
86
|
end
|
|
107
|
-
|
|
108
|
-
status[:textContent] = "Running"
|
|
109
|
-
status_dot[:dataset][:state] = "running"
|
|
110
|
-
rescue StandardError => error
|
|
111
|
-
JS.global.call(:__threeRbBootFailed, error.message) if JS.global[:__threeRbBootFailed]
|
|
112
|
-
raise
|
|
113
87
|
end
|