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,396 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "cgi"
5
+ require "json"
6
+ require "pathname"
7
+
8
+ module Three
9
+ module Generators
10
+ class BrowserExample
11
+ attr_reader :created, :skipped
12
+
13
+ def initialize(target:, root: Dir.pwd, force: false, stream: $stdout)
14
+ @root = File.expand_path(root)
15
+ @target = File.expand_path(target, @root)
16
+ @force = force
17
+ @stream = stream
18
+ @created = []
19
+ @skipped = []
20
+ end
21
+
22
+ def call
23
+ validate_target!
24
+ validate_example_files!
25
+ copy_runtime
26
+ write_example_files
27
+ report
28
+ self
29
+ end
30
+
31
+ private
32
+
33
+ def validate_target!
34
+ target_path = Pathname.new(@target)
35
+ root_path = Pathname.new(@root)
36
+ relative = begin
37
+ target_path.relative_path_from(root_path).to_s
38
+ rescue ArgumentError
39
+ raise ArgumentError, "target must be inside #{root_path}"
40
+ end
41
+
42
+ raise ArgumentError, "target must be a directory inside #{root_path}, not the project root" if relative == "."
43
+ return unless relative == ".." || relative.start_with?("../")
44
+
45
+ raise ArgumentError, "target must be inside #{root_path}"
46
+ end
47
+
48
+ def validate_example_files!
49
+ return if @force
50
+
51
+ example_files.each do |path|
52
+ next unless File.exist?(path)
53
+
54
+ raise ArgumentError, "#{relative_path(path)} already exists; pass --force to overwrite generated example files"
55
+ end
56
+ end
57
+
58
+ def copy_runtime
59
+ copy_file(runtime_path("package.json"), File.join(@root, "package.json"), overwrite: false)
60
+ copy_file(runtime_path("pnpm-lock.yaml"), File.join(@root, "pnpm-lock.yaml"), overwrite: false)
61
+ copy_tree(runtime_path("lib"), File.join(@root, "lib"), overwrite: false)
62
+ copy_file(
63
+ runtime_path("examples/browser/shared/boot.mjs"),
64
+ File.join(File.dirname(@target), "shared", "boot.mjs"),
65
+ overwrite: @force
66
+ )
67
+ end
68
+
69
+ def write_example_files
70
+ if ruby_example?
71
+ copy_ruby_example_files
72
+ else
73
+ write_file(example_path("index.html"), index_html, overwrite: @force)
74
+ write_file(example_path("boot.mjs"), boot_js, overwrite: @force)
75
+ write_file(example_path("main.rb"), main_rb, overwrite: @force)
76
+ write_file(example_path("README.md"), readme, overwrite: @force)
77
+ end
78
+ end
79
+
80
+ def copy_ruby_example_files
81
+ {
82
+ "index.html" => "index.html",
83
+ "boot.mjs" => "boot.mjs",
84
+ "main.rb" => "main.rb",
85
+ "assets/studio.hdr" => "assets/studio.hdr"
86
+ }.each do |source_name, destination_name|
87
+ copy_generated_file(
88
+ runtime_path(File.join("examples/browser/ruby", source_name)),
89
+ example_path(destination_name),
90
+ overwrite: @force
91
+ )
92
+ end
93
+
94
+ write_ruby_readme
95
+ end
96
+
97
+ def write_ruby_readme
98
+ source = runtime_path("examples/browser/ruby/README.md")
99
+ destination = example_path("README.md")
100
+ if File.exist?(destination) && File.identical?(source, destination)
101
+ @skipped << relative_path(destination)
102
+ return
103
+ end
104
+
105
+ write_file(destination, ruby_readme, overwrite: @force)
106
+ end
107
+
108
+ def copy_tree(source, destination, overwrite:)
109
+ Dir.glob(File.join(source, "**/*"), File::FNM_DOTMATCH).each do |path|
110
+ next if File.directory?(path)
111
+
112
+ relative = Pathname.new(path).relative_path_from(Pathname.new(source)).to_s
113
+ copy_file(path, File.join(destination, relative), overwrite: overwrite)
114
+ end
115
+ end
116
+
117
+ def copy_file(source, destination, overwrite:)
118
+ if File.exist?(destination) && File.identical?(source, destination)
119
+ @skipped << relative_path(destination)
120
+ return
121
+ end
122
+
123
+ if File.exist?(destination) && !overwrite
124
+ @skipped << relative_path(destination)
125
+ return
126
+ end
127
+
128
+ FileUtils.mkdir_p(File.dirname(destination))
129
+ FileUtils.cp(source, destination)
130
+ @created << relative_path(destination)
131
+ end
132
+
133
+ def copy_generated_file(source, destination, overwrite:)
134
+ if File.exist?(destination) && File.identical?(source, destination)
135
+ @skipped << relative_path(destination)
136
+ return
137
+ end
138
+
139
+ if File.exist?(destination) && !overwrite
140
+ raise ArgumentError, "#{relative_path(destination)} already exists; pass --force to overwrite generated example files"
141
+ end
142
+
143
+ FileUtils.mkdir_p(File.dirname(destination))
144
+ FileUtils.cp(source, destination)
145
+ @created << relative_path(destination)
146
+ end
147
+
148
+ def write_file(path, content, overwrite:)
149
+ if File.exist?(path) && !overwrite
150
+ raise ArgumentError, "#{relative_path(path)} already exists; pass --force to overwrite generated example files"
151
+ end
152
+
153
+ FileUtils.mkdir_p(File.dirname(path))
154
+ File.write(path, content)
155
+ @created << relative_path(path)
156
+ end
157
+
158
+ def report
159
+ @stream.puts "Created browser example at #{relative_path(@target)}"
160
+ @stream.puts "Created #{created.length} files" unless created.empty?
161
+ @stream.puts "Skipped #{skipped.length} existing runtime files" unless skipped.empty?
162
+ end
163
+
164
+ def runtime_path(relative)
165
+ File.expand_path(File.join("../../../", relative), __dir__)
166
+ end
167
+
168
+ def example_files
169
+ names = %w[index.html boot.mjs main.rb README.md]
170
+ names << "assets/studio.hdr" if ruby_example?
171
+ names.map { |name| example_path(name) }
172
+ end
173
+
174
+ def example_path(name)
175
+ File.join(@target, name)
176
+ end
177
+
178
+ def relative_path(path)
179
+ Pathname.new(path).relative_path_from(Pathname.new(@root)).to_s
180
+ end
181
+
182
+ def main_module_path
183
+ File.join(relative_path(@target), "main").delete_prefix("./")
184
+ end
185
+
186
+ def require_three_path
187
+ Pathname
188
+ .new(File.join(@root, "lib", "three"))
189
+ .relative_path_from(Pathname.new(@target))
190
+ .to_s
191
+ end
192
+
193
+ def shared_boot_path
194
+ Pathname
195
+ .new(File.join(File.dirname(@target), "shared", "boot.mjs"))
196
+ .relative_path_from(Pathname.new(@target))
197
+ .to_s
198
+ end
199
+
200
+ def title
201
+ "three-rb #{File.basename(@target)}"
202
+ end
203
+
204
+ def html_text(text)
205
+ CGI.escapeHTML(text)
206
+ end
207
+
208
+ def js_string(text)
209
+ JSON.generate(text)
210
+ end
211
+
212
+ def ruby_example?
213
+ relative_path(@target) == "examples/browser/ruby"
214
+ end
215
+
216
+ def index_html
217
+ <<~HTML
218
+ <!doctype html>
219
+ <html lang="en">
220
+ <head>
221
+ <meta charset="utf-8">
222
+ <meta name="viewport" content="width=device-width, initial-scale=1">
223
+ <title>#{html_text(title)}</title>
224
+ <style>
225
+ :root {
226
+ color-scheme: dark;
227
+ font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
228
+ background: #101418;
229
+ color: #eef3f7;
230
+ }
231
+
232
+ * { box-sizing: border-box; }
233
+ html, body, #viewport { width: 100%; height: 100%; margin: 0; }
234
+ body { overflow: hidden; }
235
+ #viewport { position: relative; min-width: 320px; min-height: 320px; }
236
+ canvas { display: block; width: 100%; height: 100%; }
237
+ .hud {
238
+ position: absolute;
239
+ top: 16px;
240
+ left: 16px;
241
+ display: flex;
242
+ align-items: center;
243
+ gap: 8px;
244
+ padding: 8px 10px;
245
+ border: 1px solid rgba(255, 255, 255, 0.14);
246
+ border-radius: 6px;
247
+ background: rgba(10, 14, 18, 0.72);
248
+ color: #dce7ef;
249
+ font-size: 13px;
250
+ backdrop-filter: blur(10px);
251
+ }
252
+ .status-dot {
253
+ width: 8px;
254
+ height: 8px;
255
+ border-radius: 999px;
256
+ background: #f2b84b;
257
+ }
258
+ .status-dot[data-state="running"] { background: #4ed08f; }
259
+ .status-dot[data-state="error"] { background: #f15b5b; }
260
+ </style>
261
+ <script type="importmap">
262
+ {
263
+ "imports": {
264
+ "@bjorn3/browser_wasi_shim": "/node_modules/@bjorn3/browser_wasi_shim/dist/index.js",
265
+ "@ruby/wasm-wasi/browser": "/node_modules/@ruby/wasm-wasi/dist/esm/browser.js",
266
+ "three": "/node_modules/three/build/three.module.js",
267
+ "three/addons/": "/node_modules/three/examples/jsm/"
268
+ }
269
+ }
270
+ </script>
271
+ <script type="module">
272
+ const status = document.querySelector("#status");
273
+ const statusDot = document.querySelector("#status-dot");
274
+
275
+ globalThis.__threeRbSetStatus = (message, state) => {
276
+ if (status) status.textContent = message;
277
+ if (statusDot) statusDot.dataset.state = state;
278
+ };
279
+ globalThis.__threeRbBootFailed = (message) => globalThis.__threeRbSetStatus(message, "error");
280
+ globalThis.addEventListener("error", (event) => globalThis.__threeRbBootFailed(event.message || "Browser error"));
281
+ globalThis.addEventListener("unhandledrejection", (event) => {
282
+ const reason = event.reason;
283
+ globalThis.__threeRbBootFailed(reason && reason.message ? reason.message : "Ruby boot failed");
284
+ });
285
+ globalThis.__threeRbSetStatus("Loading ruby.wasm", "loading");
286
+ </script>
287
+ <script type="module" src="./boot.mjs"></script>
288
+ </head>
289
+ <body>
290
+ <main id="viewport">
291
+ <canvas id="scene" data-testid="scene-canvas"></canvas>
292
+ <div class="hud" aria-live="polite">
293
+ <span class="status-dot" id="status-dot"></span>
294
+ <span id="status" data-testid="status">Loading ruby.wasm</span>
295
+ </div>
296
+ </main>
297
+ </body>
298
+ </html>
299
+ HTML
300
+ end
301
+
302
+ def boot_js
303
+ <<~JS
304
+ import { bootRubyExample } from #{js_string(shared_boot_path)};
305
+
306
+ await bootRubyExample({
307
+ main: #{js_string(main_module_path)},
308
+ clearColor: 0x101418
309
+ });
310
+ JS
311
+ end
312
+
313
+ def main_rb
314
+ <<~RUBY
315
+ # frozen_string_literal: true
316
+
317
+ require_relative "#{require_three_path}"
318
+
319
+ Three::Browser.run(starting: "Starting Ruby scene") do |app|
320
+ scene = Three::Scene.new
321
+ camera = Three::PerspectiveCamera.new(70, aspect: 1.0, near: 0.1, far: 100)
322
+ camera.position.z = 3
323
+
324
+ cube = Three::Mesh.new(
325
+ Three::BoxGeometry.new(1, 1, 1),
326
+ Three::MeshBasicMaterial.new(color: 0x4ed08f)
327
+ )
328
+ scene.add(cube)
329
+
330
+ renderer = Three::Renderers::ThreeJSRenderer.new(
331
+ canvas: "#scene",
332
+ antialias: true,
333
+ alpha: false
334
+ )
335
+ renderer.set_clear_color(0x101418, 1)
336
+
337
+ app.resize_renderer(renderer, camera)
338
+ renderer.render(scene, camera)
339
+
340
+ app.animation_loop(renderer) do
341
+ cube.rotation.x += 0.01
342
+ cube.rotation.y += 0.015
343
+ renderer.render(scene, camera)
344
+ end
345
+ end
346
+ RUBY
347
+ end
348
+
349
+ def readme
350
+ <<~MARKDOWN
351
+ # #{title}
352
+
353
+ This generated browser example runs Ruby through ruby.wasm and renders with three.js.
354
+
355
+ From the project root:
356
+
357
+ ```sh
358
+ pnpm install
359
+ ruby -run -e httpd . -p 8000
360
+ ```
361
+
362
+ Open:
363
+
364
+ ```text
365
+ http://localhost:8000/#{relative_path(@target)}/
366
+ ```
367
+
368
+ Keep serving the project root. The browser runtime loads `node_modules/`, `lib/`, and this example over HTTP.
369
+ MARKDOWN
370
+ end
371
+
372
+ def ruby_readme
373
+ <<~MARKDOWN
374
+ # three-rb Ruby Example
375
+
376
+ This generated browser example runs Ruby through ruby.wasm and renders a faceted red gemstone with a three-dimensional `three-rb` title.
377
+
378
+ From the project root:
379
+
380
+ ```sh
381
+ pnpm install
382
+ ruby -run -e httpd . -p 8000
383
+ ```
384
+
385
+ Open:
386
+
387
+ ```text
388
+ http://localhost:8000/#{relative_path(@target)}/
389
+ ```
390
+
391
+ Keep serving the project root. The browser runtime loads `node_modules/`, `lib/`, and this example's local `assets/` directory over HTTP.
392
+ MARKDOWN
393
+ end
394
+ end
395
+ end
396
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../core/buffer_geometry"
4
+
5
+ module Three
6
+ class TextGeometry < BufferGeometry
7
+ attr_reader :text, :parameters
8
+
9
+ def initialize(
10
+ text,
11
+ font:,
12
+ size: 1,
13
+ depth: 0.2,
14
+ curve_segments: 12,
15
+ steps: 1,
16
+ bevel_enabled: false,
17
+ bevel_thickness: 0.01,
18
+ bevel_size: 0.01,
19
+ bevel_offset: 0,
20
+ bevel_segments: 3,
21
+ direction: "ltr"
22
+ )
23
+ super()
24
+ @type = "TextGeometry"
25
+ @text = text.to_s
26
+ @parameters = {
27
+ font: font,
28
+ size: size,
29
+ depth: depth,
30
+ curve_segments: curve_segments,
31
+ steps: steps,
32
+ bevel_enabled: bevel_enabled,
33
+ bevel_thickness: bevel_thickness,
34
+ bevel_size: bevel_size,
35
+ bevel_offset: bevel_offset,
36
+ bevel_segments: bevel_segments,
37
+ direction: direction
38
+ }
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../backends/threejs"
4
+
5
+ module Three
6
+ class Font
7
+ attr_reader :handle
8
+
9
+ def initialize(handle)
10
+ @handle = handle
11
+ end
12
+ end
13
+
14
+ module Loaders
15
+ class FontLoader
16
+ def initialize(adapter: nil, backend: nil)
17
+ @adapter = adapter || backend&.adapter || Backends::ThreeJS::RubyWasmAdapter.new
18
+ end
19
+
20
+ def load(source)
21
+ handle = @adapter.load_font(source)
22
+ handle = handle.await if handle.respond_to?(:await)
23
+ font = Font.new(handle)
24
+ yield font if block_given?
25
+ font
26
+ end
27
+ end
28
+ end
29
+ end
@@ -22,17 +22,20 @@ module Three
22
22
  end
23
23
 
24
24
  def build_texture(entry)
25
- case value(entry, :type)
26
- when "CubeTexture"
27
- CubeTexture.new(
28
- value(entry, :sources) || value(entry, :source),
29
- **texture_parameters(entry)
30
- )
31
- when "RGBETexture"
32
- RGBETexture.new(value(entry, :source), **texture_parameters(entry))
33
- else
34
- Texture.new(value(entry, :source), **texture_parameters(entry))
35
- end
25
+ texture =
26
+ case value(entry, :type)
27
+ when "CubeTexture"
28
+ CubeTexture.new(
29
+ value(entry, :sources) || value(entry, :source),
30
+ **texture_parameters(entry)
31
+ )
32
+ when "RGBETexture"
33
+ RGBETexture.new(value(entry, :source), **texture_parameters(entry))
34
+ else
35
+ Texture.new(value(entry, :source), **texture_parameters(entry))
36
+ end
37
+ texture.user_data = value(entry, :user_data) || {}
38
+ texture
36
39
  end
37
40
 
38
41
  def texture_parameters(entry)
@@ -57,25 +60,25 @@ module Three
57
60
  case value(entry, :type)
58
61
  when "BoxGeometry"
59
62
  parameters = value(entry, :parameters) || {}
60
- BoxGeometry.new(
63
+ apply_geometry_properties(BoxGeometry.new(
61
64
  value(parameters, :width) || 1,
62
65
  value(parameters, :height) || 1,
63
66
  value(parameters, :depth) || 1,
64
67
  width_segments: value(parameters, :width_segments) || 1,
65
68
  height_segments: value(parameters, :height_segments) || 1,
66
69
  depth_segments: value(parameters, :depth_segments) || 1
67
- )
70
+ ), entry)
68
71
  when "PlaneGeometry"
69
72
  parameters = value(entry, :parameters) || {}
70
- PlaneGeometry.new(
73
+ apply_geometry_properties(PlaneGeometry.new(
71
74
  value(parameters, :width) || 1,
72
75
  value(parameters, :height) || 1,
73
76
  width_segments: value(parameters, :width_segments) || 1,
74
77
  height_segments: value(parameters, :height_segments) || 1
75
- )
78
+ ), entry)
76
79
  when "SphereGeometry"
77
80
  parameters = value(entry, :parameters) || {}
78
- SphereGeometry.new(
81
+ apply_geometry_properties(SphereGeometry.new(
79
82
  value(parameters, :radius) || 1,
80
83
  width_segments: value(parameters, :width_segments) || 32,
81
84
  height_segments: value(parameters, :height_segments) || 16,
@@ -83,7 +86,7 @@ module Three
83
86
  phi_length: value(parameters, :phi_length) || Math::PI * 2,
84
87
  theta_start: value(parameters, :theta_start) || 0,
85
88
  theta_length: value(parameters, :theta_length) || Math::PI
86
- )
89
+ ), entry)
87
90
  else
88
91
  build_buffer_geometry(entry)
89
92
  end
@@ -91,7 +94,6 @@ module Three
91
94
 
92
95
  def build_buffer_geometry(entry)
93
96
  geometry = BufferGeometry.new
94
- geometry.name = value(entry, :name) if value(entry, :name)
95
97
  geometry.set_index(build_buffer_attribute(value(entry, :index))) if value(entry, :index)
96
98
 
97
99
  (value(entry, :attributes) || {}).each do |name, attribute_entry|
@@ -106,9 +108,29 @@ module Three
106
108
  )
107
109
  end
108
110
 
111
+ apply_geometry_properties(geometry, entry)
112
+ end
113
+
114
+ def apply_geometry_properties(geometry, entry)
115
+ geometry.name = value(entry, :name) if value(entry, :name)
116
+ geometry.user_data = value(entry, :user_data) || {}
117
+ draw_range = value(entry, :draw_range)
118
+ if draw_range
119
+ geometry.set_draw_range(
120
+ deserialize_number(value(draw_range, :start)),
121
+ deserialize_number(value(draw_range, :count))
122
+ )
123
+ end
109
124
  geometry
110
125
  end
111
126
 
127
+ def deserialize_number(value)
128
+ return Float::INFINITY if value == "Infinity"
129
+ return -Float::INFINITY if value == "-Infinity"
130
+
131
+ value
132
+ end
133
+
112
134
  def build_buffer_attribute(entry)
113
135
  component_type = (value(entry, :component_type) || :generic).to_sym
114
136
  array = value(entry, :array) || []
@@ -129,34 +151,37 @@ module Three
129
151
 
130
152
  def build_material(entry)
131
153
  parameters = material_parameters(entry)
132
- case value(entry, :type)
133
- when "MeshBasicMaterial"
134
- MeshBasicMaterial.new(parameters)
135
- when "LineBasicMaterial"
136
- LineBasicMaterial.new(parameters)
137
- when "MeshLambertMaterial"
138
- MeshLambertMaterial.new(parameters)
139
- when "MeshMatcapMaterial"
140
- MeshMatcapMaterial.new(parameters)
141
- when "MeshNormalMaterial"
142
- MeshNormalMaterial.new(parameters)
143
- when "MeshPhongMaterial"
144
- MeshPhongMaterial.new(parameters)
145
- when "MeshPhysicalMaterial"
146
- MeshPhysicalMaterial.new(parameters)
147
- when "MeshStandardMaterial"
148
- MeshStandardMaterial.new(parameters)
149
- when "MeshToonMaterial"
150
- MeshToonMaterial.new(parameters)
151
- when "PointsMaterial"
152
- PointsMaterial.new(parameters)
153
- when "ShadowMaterial"
154
- ShadowMaterial.new(parameters)
155
- when "SpriteMaterial"
156
- SpriteMaterial.new(parameters)
157
- else
158
- Material.new(parameters)
159
- end
154
+ material =
155
+ case value(entry, :type)
156
+ when "MeshBasicMaterial"
157
+ MeshBasicMaterial.new(parameters)
158
+ when "LineBasicMaterial"
159
+ LineBasicMaterial.new(parameters)
160
+ when "MeshLambertMaterial"
161
+ MeshLambertMaterial.new(parameters)
162
+ when "MeshMatcapMaterial"
163
+ MeshMatcapMaterial.new(parameters)
164
+ when "MeshNormalMaterial"
165
+ MeshNormalMaterial.new(parameters)
166
+ when "MeshPhongMaterial"
167
+ MeshPhongMaterial.new(parameters)
168
+ when "MeshPhysicalMaterial"
169
+ MeshPhysicalMaterial.new(parameters)
170
+ when "MeshStandardMaterial"
171
+ MeshStandardMaterial.new(parameters)
172
+ when "MeshToonMaterial"
173
+ MeshToonMaterial.new(parameters)
174
+ when "PointsMaterial"
175
+ PointsMaterial.new(parameters)
176
+ when "ShadowMaterial"
177
+ ShadowMaterial.new(parameters)
178
+ when "SpriteMaterial"
179
+ SpriteMaterial.new(parameters)
180
+ else
181
+ Material.new(parameters)
182
+ end
183
+ material.user_data = value(entry, :user_data) || {}
184
+ material
160
185
  end
161
186
 
162
187
  def material_parameters(entry)
@@ -312,9 +337,29 @@ module Three
312
337
  scene = Scene.new
313
338
  scene.background = @textures[value(entry, :background)] if value(entry, :background)
314
339
  scene.environment = @textures[value(entry, :environment)] if value(entry, :environment)
340
+ scene.fog = build_fog(value(entry, :fog)) if value(entry, :fog)
341
+ scene.override_material = @materials[value(entry, :override_material)] if value(entry, :override_material)
315
342
  scene
316
343
  end
317
344
 
345
+ def build_fog(entry)
346
+ case value(entry, :type)
347
+ when "FogExp2"
348
+ FogExp2.new(
349
+ value(entry, :color) || 0xffffff,
350
+ density: has_value?(entry, :density) ? value(entry, :density) : 0.00025,
351
+ name: value(entry, :name) || ""
352
+ )
353
+ else
354
+ Fog.new(
355
+ value(entry, :color) || 0xffffff,
356
+ near: has_value?(entry, :near) ? value(entry, :near) : 1,
357
+ far: has_value?(entry, :far) ? value(entry, :far) : 1000,
358
+ name: value(entry, :name) || ""
359
+ )
360
+ end
361
+ end
362
+
318
363
  def build_directional_light(entry)
319
364
  light = DirectionalLight.new(value(entry, :color) || 0xffffff, value(entry, :intensity) || 1)
320
365
  camera = value(entry, :shadow_camera)
@@ -361,6 +406,7 @@ module Three
361
406
  object.matrix_auto_update = value(entry, :matrix_auto_update) if has_value?(entry, :matrix_auto_update)
362
407
  object.user_data = value(entry, :user_data) || {}
363
408
 
409
+ object.matrix.from_array(value(entry, :matrix)) if value(entry, :matrix)
364
410
  object.position.set(*value(entry, :position)) if value(entry, :position)
365
411
  object.quaternion.set(*value(entry, :quaternion)) if value(entry, :quaternion)
366
412
  object.scale.set(*value(entry, :scale)) if value(entry, :scale)
@@ -140,7 +140,8 @@ module Three
140
140
  opacity: @opacity,
141
141
  transparent: @transparent,
142
142
  visible: @visible,
143
- vertex_colors: @vertex_colors
143
+ vertex_colors: @vertex_colors,
144
+ user_data: @user_data
144
145
  }
145
146
  end
146
147