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,315 @@
|
|
|
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
|
+
write_file(example_path("index.html"), index_html, overwrite: @force)
|
|
71
|
+
write_file(example_path("boot.mjs"), boot_js, overwrite: @force)
|
|
72
|
+
write_file(example_path("main.rb"), main_rb, overwrite: @force)
|
|
73
|
+
write_file(example_path("README.md"), readme, overwrite: @force)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def copy_tree(source, destination, overwrite:)
|
|
77
|
+
Dir.glob(File.join(source, "**/*"), File::FNM_DOTMATCH).each do |path|
|
|
78
|
+
next if File.directory?(path)
|
|
79
|
+
|
|
80
|
+
relative = Pathname.new(path).relative_path_from(Pathname.new(source)).to_s
|
|
81
|
+
copy_file(path, File.join(destination, relative), overwrite: overwrite)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def copy_file(source, destination, overwrite:)
|
|
86
|
+
if File.exist?(destination) && !overwrite
|
|
87
|
+
@skipped << relative_path(destination)
|
|
88
|
+
return
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
FileUtils.mkdir_p(File.dirname(destination))
|
|
92
|
+
FileUtils.cp(source, destination)
|
|
93
|
+
@created << relative_path(destination)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def write_file(path, content, overwrite:)
|
|
97
|
+
if File.exist?(path) && !overwrite
|
|
98
|
+
raise ArgumentError, "#{relative_path(path)} already exists; pass --force to overwrite generated example files"
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
102
|
+
File.write(path, content)
|
|
103
|
+
@created << relative_path(path)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def report
|
|
107
|
+
@stream.puts "Created browser example at #{relative_path(@target)}"
|
|
108
|
+
@stream.puts "Created #{created.length} files" unless created.empty?
|
|
109
|
+
@stream.puts "Skipped #{skipped.length} existing runtime files" unless skipped.empty?
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def runtime_path(relative)
|
|
113
|
+
File.expand_path(File.join("../../../", relative), __dir__)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def example_files
|
|
117
|
+
%w[index.html boot.mjs main.rb README.md].map { |name| example_path(name) }
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def example_path(name)
|
|
121
|
+
File.join(@target, name)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def relative_path(path)
|
|
125
|
+
Pathname.new(path).relative_path_from(Pathname.new(@root)).to_s
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def main_module_path
|
|
129
|
+
File.join(relative_path(@target), "main").delete_prefix("./")
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def require_three_path
|
|
133
|
+
Pathname
|
|
134
|
+
.new(File.join(@root, "lib", "three"))
|
|
135
|
+
.relative_path_from(Pathname.new(@target))
|
|
136
|
+
.to_s
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def shared_boot_path
|
|
140
|
+
Pathname
|
|
141
|
+
.new(File.join(File.dirname(@target), "shared", "boot.mjs"))
|
|
142
|
+
.relative_path_from(Pathname.new(@target))
|
|
143
|
+
.to_s
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def title
|
|
147
|
+
"three-rb #{File.basename(@target)}"
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def html_text(text)
|
|
151
|
+
CGI.escapeHTML(text)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def js_string(text)
|
|
155
|
+
JSON.generate(text)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def index_html
|
|
159
|
+
<<~HTML
|
|
160
|
+
<!doctype html>
|
|
161
|
+
<html lang="en">
|
|
162
|
+
<head>
|
|
163
|
+
<meta charset="utf-8">
|
|
164
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
165
|
+
<title>#{html_text(title)}</title>
|
|
166
|
+
<style>
|
|
167
|
+
:root {
|
|
168
|
+
color-scheme: dark;
|
|
169
|
+
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
170
|
+
background: #101418;
|
|
171
|
+
color: #eef3f7;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
* { box-sizing: border-box; }
|
|
175
|
+
html, body, #viewport { width: 100%; height: 100%; margin: 0; }
|
|
176
|
+
body { overflow: hidden; }
|
|
177
|
+
#viewport { position: relative; min-width: 320px; min-height: 320px; }
|
|
178
|
+
canvas { display: block; width: 100%; height: 100%; }
|
|
179
|
+
.hud {
|
|
180
|
+
position: absolute;
|
|
181
|
+
top: 16px;
|
|
182
|
+
left: 16px;
|
|
183
|
+
display: flex;
|
|
184
|
+
align-items: center;
|
|
185
|
+
gap: 8px;
|
|
186
|
+
padding: 8px 10px;
|
|
187
|
+
border: 1px solid rgba(255, 255, 255, 0.14);
|
|
188
|
+
border-radius: 6px;
|
|
189
|
+
background: rgba(10, 14, 18, 0.72);
|
|
190
|
+
color: #dce7ef;
|
|
191
|
+
font-size: 13px;
|
|
192
|
+
backdrop-filter: blur(10px);
|
|
193
|
+
}
|
|
194
|
+
.status-dot {
|
|
195
|
+
width: 8px;
|
|
196
|
+
height: 8px;
|
|
197
|
+
border-radius: 999px;
|
|
198
|
+
background: #f2b84b;
|
|
199
|
+
}
|
|
200
|
+
.status-dot[data-state="running"] { background: #4ed08f; }
|
|
201
|
+
.status-dot[data-state="error"] { background: #f15b5b; }
|
|
202
|
+
</style>
|
|
203
|
+
<script type="importmap">
|
|
204
|
+
{
|
|
205
|
+
"imports": {
|
|
206
|
+
"@bjorn3/browser_wasi_shim": "/node_modules/@bjorn3/browser_wasi_shim/dist/index.js",
|
|
207
|
+
"@ruby/wasm-wasi/browser": "/node_modules/@ruby/wasm-wasi/dist/esm/browser.js",
|
|
208
|
+
"three": "/node_modules/three/build/three.module.js",
|
|
209
|
+
"three/addons/": "/node_modules/three/examples/jsm/"
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
</script>
|
|
213
|
+
<script type="module">
|
|
214
|
+
const status = document.querySelector("#status");
|
|
215
|
+
const statusDot = document.querySelector("#status-dot");
|
|
216
|
+
|
|
217
|
+
globalThis.__threeRbSetStatus = (message, state) => {
|
|
218
|
+
if (status) status.textContent = message;
|
|
219
|
+
if (statusDot) statusDot.dataset.state = state;
|
|
220
|
+
};
|
|
221
|
+
globalThis.__threeRbBootFailed = (message) => globalThis.__threeRbSetStatus(message, "error");
|
|
222
|
+
globalThis.addEventListener("error", (event) => globalThis.__threeRbBootFailed(event.message || "Browser error"));
|
|
223
|
+
globalThis.addEventListener("unhandledrejection", (event) => {
|
|
224
|
+
const reason = event.reason;
|
|
225
|
+
globalThis.__threeRbBootFailed(reason && reason.message ? reason.message : "Ruby boot failed");
|
|
226
|
+
});
|
|
227
|
+
globalThis.__threeRbSetStatus("Loading ruby.wasm", "loading");
|
|
228
|
+
</script>
|
|
229
|
+
<script type="module" src="./boot.mjs"></script>
|
|
230
|
+
</head>
|
|
231
|
+
<body>
|
|
232
|
+
<main id="viewport">
|
|
233
|
+
<canvas id="scene" data-testid="scene-canvas"></canvas>
|
|
234
|
+
<div class="hud" aria-live="polite">
|
|
235
|
+
<span class="status-dot" id="status-dot"></span>
|
|
236
|
+
<span id="status" data-testid="status">Loading ruby.wasm</span>
|
|
237
|
+
</div>
|
|
238
|
+
</main>
|
|
239
|
+
</body>
|
|
240
|
+
</html>
|
|
241
|
+
HTML
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def boot_js
|
|
245
|
+
<<~JS
|
|
246
|
+
import { bootRubyExample } from #{js_string(shared_boot_path)};
|
|
247
|
+
|
|
248
|
+
await bootRubyExample({
|
|
249
|
+
main: #{js_string(main_module_path)},
|
|
250
|
+
clearColor: 0x101418
|
|
251
|
+
});
|
|
252
|
+
JS
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def main_rb
|
|
256
|
+
<<~RUBY
|
|
257
|
+
# frozen_string_literal: true
|
|
258
|
+
|
|
259
|
+
require_relative "#{require_three_path}"
|
|
260
|
+
|
|
261
|
+
Three::Browser.run(starting: "Starting Ruby scene") do |app|
|
|
262
|
+
scene = Three::Scene.new
|
|
263
|
+
camera = Three::PerspectiveCamera.new(70, aspect: 1.0, near: 0.1, far: 100)
|
|
264
|
+
camera.position.z = 3
|
|
265
|
+
|
|
266
|
+
cube = Three::Mesh.new(
|
|
267
|
+
Three::BoxGeometry.new(1, 1, 1),
|
|
268
|
+
Three::MeshBasicMaterial.new(color: 0x4ed08f)
|
|
269
|
+
)
|
|
270
|
+
scene.add(cube)
|
|
271
|
+
|
|
272
|
+
renderer = Three::Renderers::ThreeJSRenderer.new(
|
|
273
|
+
canvas: "#scene",
|
|
274
|
+
antialias: true,
|
|
275
|
+
alpha: false
|
|
276
|
+
)
|
|
277
|
+
renderer.set_clear_color(0x101418, 1)
|
|
278
|
+
|
|
279
|
+
app.resize_renderer(renderer, camera)
|
|
280
|
+
renderer.render(scene, camera)
|
|
281
|
+
|
|
282
|
+
app.animation_loop(renderer) do
|
|
283
|
+
cube.rotation.x += 0.01
|
|
284
|
+
cube.rotation.y += 0.015
|
|
285
|
+
renderer.render(scene, camera)
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
RUBY
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def readme
|
|
292
|
+
<<~MARKDOWN
|
|
293
|
+
# #{title}
|
|
294
|
+
|
|
295
|
+
This generated browser example runs Ruby through ruby.wasm and renders with three.js.
|
|
296
|
+
|
|
297
|
+
From the project root:
|
|
298
|
+
|
|
299
|
+
```sh
|
|
300
|
+
pnpm install
|
|
301
|
+
ruby -run -e httpd . -p 8000
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Open:
|
|
305
|
+
|
|
306
|
+
```text
|
|
307
|
+
http://localhost:8000/#{relative_path(@target)}/
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
Keep serving the project root. The browser runtime loads `node_modules/`, `lib/`, and this example over HTTP.
|
|
311
|
+
MARKDOWN
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
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
|
-
|
|
26
|
-
|
|
27
|
-
CubeTexture
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
RGBETexture
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
MeshBasicMaterial
|
|
135
|
-
|
|
136
|
-
LineBasicMaterial
|
|
137
|
-
|
|
138
|
-
MeshLambertMaterial
|
|
139
|
-
|
|
140
|
-
MeshMatcapMaterial
|
|
141
|
-
|
|
142
|
-
MeshNormalMaterial
|
|
143
|
-
|
|
144
|
-
MeshPhongMaterial
|
|
145
|
-
|
|
146
|
-
MeshPhysicalMaterial
|
|
147
|
-
|
|
148
|
-
MeshStandardMaterial
|
|
149
|
-
|
|
150
|
-
MeshToonMaterial
|
|
151
|
-
|
|
152
|
-
PointsMaterial
|
|
153
|
-
|
|
154
|
-
ShadowMaterial
|
|
155
|
-
|
|
156
|
-
SpriteMaterial
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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)
|