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,389 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Three
|
|
4
|
+
module Browser
|
|
5
|
+
class Error < RuntimeError; end
|
|
6
|
+
|
|
7
|
+
class Element
|
|
8
|
+
attr_reader :handle
|
|
9
|
+
|
|
10
|
+
def initialize(handle)
|
|
11
|
+
@handle = handle
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.wrap(value)
|
|
15
|
+
value.is_a?(self) ? value : new(value)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def client_width
|
|
19
|
+
[@handle[:clientWidth].to_i, 1].max
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def client_height
|
|
23
|
+
[@handle[:clientHeight].to_i, 1].max
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def size
|
|
27
|
+
[client_width, client_height]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def add_event_listener(name, &block)
|
|
31
|
+
@handle.call(:addEventListener, name.to_s, block)
|
|
32
|
+
self
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
alias on add_event_listener
|
|
36
|
+
|
|
37
|
+
def bounding_client_rect
|
|
38
|
+
rect = @handle.call(:getBoundingClientRect)
|
|
39
|
+
{
|
|
40
|
+
left: rect[:left].to_f,
|
|
41
|
+
top: rect[:top].to_f,
|
|
42
|
+
width: rect[:width].to_f,
|
|
43
|
+
height: rect[:height].to_f
|
|
44
|
+
}
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def pointer_ndc(event)
|
|
48
|
+
rect = bounding_client_rect
|
|
49
|
+
x = ((event[:clientX].to_f - rect[:left]) / rect[:width]) * 2 - 1
|
|
50
|
+
y = -(((event[:clientY].to_f - rect[:top]) / rect[:height]) * 2 - 1)
|
|
51
|
+
[x, y]
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
class Storage
|
|
56
|
+
attr_reader :handle
|
|
57
|
+
|
|
58
|
+
def initialize(handle)
|
|
59
|
+
@handle = handle
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def set(key, value)
|
|
63
|
+
@handle.call(:setItem, key.to_s, value)
|
|
64
|
+
self
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def get(key)
|
|
68
|
+
@handle.call(:getItem, key.to_s)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def delete(key)
|
|
72
|
+
@handle.call(:removeItem, key.to_s)
|
|
73
|
+
self
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def clear
|
|
77
|
+
@handle.call(:clear)
|
|
78
|
+
self
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def length
|
|
82
|
+
@handle[:length].to_i
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def key(index)
|
|
86
|
+
@handle.call(:key, index)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
class Application
|
|
91
|
+
attr_reader :document, :window
|
|
92
|
+
|
|
93
|
+
def initialize(starting: nil, status_selector: "#status", status_dot_selector: "#status-dot")
|
|
94
|
+
Browser.ready!
|
|
95
|
+
@document = Browser.document
|
|
96
|
+
@window = Browser.window
|
|
97
|
+
@status = status_selector ? query(status_selector) : nil
|
|
98
|
+
@status_dot = status_dot_selector ? query(status_dot_selector) : nil
|
|
99
|
+
status(starting, state: "loading") if starting
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def query(selector)
|
|
103
|
+
Element.new(@document.call(:querySelector, selector))
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def element(handle)
|
|
107
|
+
Element.wrap(handle)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def status(message, state: nil)
|
|
111
|
+
setter = Browser.global[:__threeRbSetStatus]
|
|
112
|
+
if js_function?(setter)
|
|
113
|
+
Browser.global.call(:__threeRbSetStatus, message, state)
|
|
114
|
+
else
|
|
115
|
+
@status.handle[:textContent] = message if @status
|
|
116
|
+
@status_dot.handle[:dataset][:state] = state if @status_dot && state
|
|
117
|
+
end
|
|
118
|
+
self
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def running!
|
|
122
|
+
status("Running", state: "running")
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def boot_failed(message)
|
|
126
|
+
failure = Browser.global[:__threeRbBootFailed]
|
|
127
|
+
if js_function?(failure)
|
|
128
|
+
Browser.global.call(:__threeRbBootFailed, message)
|
|
129
|
+
else
|
|
130
|
+
status(message, state: "error")
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def on_resize(viewport: "#viewport", &block)
|
|
135
|
+
raise ArgumentError, "block is required" unless block
|
|
136
|
+
|
|
137
|
+
viewport_element = viewport.is_a?(Element) ? viewport : query(viewport)
|
|
138
|
+
callback = proc do
|
|
139
|
+
width, height = viewport_element.size
|
|
140
|
+
block.call(width, height, width.to_f / height)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
callback.call
|
|
144
|
+
@window.call(:addEventListener, "resize", callback)
|
|
145
|
+
callback
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def on_key(name = "keydown", key: nil, target: nil, &block)
|
|
149
|
+
raise ArgumentError, "block is required" unless block
|
|
150
|
+
|
|
151
|
+
element = event_target(target || @window)
|
|
152
|
+
callback = proc do |event|
|
|
153
|
+
next if key && event[:key].to_s != key.to_s
|
|
154
|
+
|
|
155
|
+
block.call(event)
|
|
156
|
+
end
|
|
157
|
+
element.on(name, &callback)
|
|
158
|
+
callback
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def on_pointer(name = "pointermove", target: "#viewport", &block)
|
|
162
|
+
raise ArgumentError, "block is required" unless block
|
|
163
|
+
|
|
164
|
+
element = event_target(target)
|
|
165
|
+
callback = proc do |event|
|
|
166
|
+
block.call(event, *element.pointer_ndc(event))
|
|
167
|
+
end
|
|
168
|
+
element.on(name, &callback)
|
|
169
|
+
callback
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def pointer_ndc(event, target: "#viewport")
|
|
173
|
+
event_target(target).pointer_ndc(event)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def storage(kind = :local)
|
|
177
|
+
handle =
|
|
178
|
+
case kind
|
|
179
|
+
when :local, "local" then Browser.global[:localStorage]
|
|
180
|
+
when :session, "session" then Browser.global[:sessionStorage]
|
|
181
|
+
else kind
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
Storage.new(handle)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def animation_loop(renderer = nil, &block)
|
|
188
|
+
raise ArgumentError, "block is required" unless block
|
|
189
|
+
|
|
190
|
+
return renderer.animation_loop(&block) if renderer
|
|
191
|
+
|
|
192
|
+
callback = nil
|
|
193
|
+
callback = proc do |time|
|
|
194
|
+
block.call(time)
|
|
195
|
+
@window.call(:requestAnimationFrame, callback)
|
|
196
|
+
end
|
|
197
|
+
@window.call(:requestAnimationFrame, callback)
|
|
198
|
+
callback
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def resize_renderer(renderer, camera, viewport: "#viewport")
|
|
202
|
+
on_resize(viewport: viewport) do |width, height, aspect|
|
|
203
|
+
if block_given?
|
|
204
|
+
yield(width, height, aspect)
|
|
205
|
+
elsif camera.respond_to?(:aspect=)
|
|
206
|
+
camera.aspect = aspect
|
|
207
|
+
camera.update_projection_matrix if camera.respond_to?(:update_projection_matrix)
|
|
208
|
+
end
|
|
209
|
+
renderer.set_size(width, height)
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def expose(values, renderer: nil, prefix: "__threeRb")
|
|
214
|
+
values.each do |name, value|
|
|
215
|
+
set(name, value, renderer: renderer, prefix: prefix)
|
|
216
|
+
end
|
|
217
|
+
self
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def set(name, value, renderer: nil, prefix: "__threeRb")
|
|
221
|
+
Browser.global[global_name(name, prefix)] = exposed_value(value, renderer)
|
|
222
|
+
self
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def get(name, prefix: "__threeRb")
|
|
226
|
+
Browser.global[global_name(name, prefix)]
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def increment(name, by: 1, prefix: "__threeRb")
|
|
230
|
+
value = get(name, prefix: prefix).to_i + by
|
|
231
|
+
set(name, value, prefix: prefix)
|
|
232
|
+
value
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
private
|
|
236
|
+
|
|
237
|
+
def event_target(target)
|
|
238
|
+
case target
|
|
239
|
+
when Element then target
|
|
240
|
+
when String, Symbol then query(target.to_s)
|
|
241
|
+
else Element.wrap(target)
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def js_function?(value)
|
|
246
|
+
value.respond_to?(:typeof) && value.typeof == "function"
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def camelize(value)
|
|
250
|
+
value.to_s.split("_").map(&:capitalize).join
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def global_name(name, prefix)
|
|
254
|
+
:"#{prefix}#{camelize(name)}"
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def exposed_value(value, renderer)
|
|
258
|
+
if value.is_a?(Array)
|
|
259
|
+
js_array(value, renderer)
|
|
260
|
+
elsif renderer && materializable?(value)
|
|
261
|
+
renderer.backend.materialize(value)
|
|
262
|
+
elsif value.respond_to?(:handle)
|
|
263
|
+
value.handle
|
|
264
|
+
else
|
|
265
|
+
value
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def js_array(values, renderer)
|
|
270
|
+
array = Browser.global[:Array].new
|
|
271
|
+
values.each { |value| array.call(:push, exposed_value(value, renderer)) }
|
|
272
|
+
array
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def materializable?(value)
|
|
276
|
+
value.respond_to?(:uuid) || value.respond_to?(:source) || value.respond_to?(:sources)
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
class << self
|
|
281
|
+
def run(**options)
|
|
282
|
+
app = nil
|
|
283
|
+
begin
|
|
284
|
+
install_runtime_extensions
|
|
285
|
+
app = Application.new(**options)
|
|
286
|
+
result = yield app
|
|
287
|
+
app.running!
|
|
288
|
+
result
|
|
289
|
+
rescue StandardError => error
|
|
290
|
+
app ? app.boot_failed(error.message) : notify_boot_failed(error.message)
|
|
291
|
+
raise
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def ready!
|
|
296
|
+
ready = global[:__threeReady]
|
|
297
|
+
ready.await if ready.respond_to?(:await)
|
|
298
|
+
self
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
def document
|
|
302
|
+
global[:document]
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def window
|
|
306
|
+
global[:window]
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def js
|
|
310
|
+
require "js"
|
|
311
|
+
JS.global
|
|
312
|
+
rescue LoadError
|
|
313
|
+
raise Error, "Three::Browser.js requires ruby.wasm's js gem"
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
def global
|
|
317
|
+
js
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
private
|
|
321
|
+
|
|
322
|
+
def install_runtime_extensions
|
|
323
|
+
install_renderer_extensions
|
|
324
|
+
install_backend_extensions
|
|
325
|
+
install_adapter_extensions
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def install_renderer_extensions
|
|
329
|
+
renderer = Three::Renderers::ThreeJSRenderer
|
|
330
|
+
unless renderer.method_defined?(:on_dispose)
|
|
331
|
+
renderer.class_eval do
|
|
332
|
+
def on_dispose(object, &block)
|
|
333
|
+
@backend.add_event_listener(object, :dispose, block)
|
|
334
|
+
self
|
|
335
|
+
end
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
return if renderer.method_defined?(:cached?)
|
|
340
|
+
|
|
341
|
+
renderer.class_eval do
|
|
342
|
+
def cached?(object)
|
|
343
|
+
@backend.cached?(object)
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def install_backend_extensions
|
|
349
|
+
backend = Three::Backends::ThreeJS
|
|
350
|
+
unless backend.method_defined?(:add_event_listener)
|
|
351
|
+
backend.class_eval do
|
|
352
|
+
def add_event_listener(object, type, callback)
|
|
353
|
+
raise ArgumentError, "callback is required" unless callback
|
|
354
|
+
|
|
355
|
+
@adapter.add_event_listener(materialize(object), type, callback)
|
|
356
|
+
end
|
|
357
|
+
end
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
return if backend.method_defined?(:cached?)
|
|
361
|
+
|
|
362
|
+
backend.class_eval do
|
|
363
|
+
def cached?(object)
|
|
364
|
+
key = cache_key(object)
|
|
365
|
+
key ? @handles.key?(key) : false
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
def install_adapter_extensions
|
|
371
|
+
adapter = Three::Backends::ThreeJS::RubyWasmAdapter
|
|
372
|
+
return if adapter.method_defined?(:add_event_listener)
|
|
373
|
+
|
|
374
|
+
adapter.class_eval do
|
|
375
|
+
def add_event_listener(handle, type, callback)
|
|
376
|
+
handle.call(:addEventListener, type.to_s, callback)
|
|
377
|
+
end
|
|
378
|
+
end
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
def notify_boot_failed(message)
|
|
382
|
+
failure = global[:__threeRbBootFailed]
|
|
383
|
+
global.call(:__threeRbBootFailed, message) if failure.respond_to?(:typeof) && failure.typeof == "function"
|
|
384
|
+
rescue Error
|
|
385
|
+
nil
|
|
386
|
+
end
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
end
|
data/lib/three/constants.rb
CHANGED
|
@@ -84,7 +84,11 @@ module Three
|
|
|
84
84
|
end
|
|
85
85
|
|
|
86
86
|
def clone
|
|
87
|
-
|
|
87
|
+
if instance_of?(BufferAttribute)
|
|
88
|
+
self.class.new(@array, @item_size, @normalized, component_type: @component_type)
|
|
89
|
+
else
|
|
90
|
+
self.class.new(@array, @item_size, @normalized)
|
|
91
|
+
end
|
|
88
92
|
end
|
|
89
93
|
|
|
90
94
|
def get_component(index, component)
|
|
@@ -99,6 +99,18 @@ module Three
|
|
|
99
99
|
self
|
|
100
100
|
end
|
|
101
101
|
|
|
102
|
+
def center
|
|
103
|
+
@centered = true
|
|
104
|
+
mark_dirty!(:geometry_operations)
|
|
105
|
+
self
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
alias center! center
|
|
109
|
+
|
|
110
|
+
def centered?
|
|
111
|
+
@centered == true
|
|
112
|
+
end
|
|
113
|
+
|
|
102
114
|
def compute_bounding_box
|
|
103
115
|
position = get_attribute(:position)
|
|
104
116
|
@bounding_box = nil
|
|
@@ -153,8 +165,10 @@ module Three
|
|
|
153
165
|
index: @index&.to_h,
|
|
154
166
|
attributes: @attributes.transform_values(&:to_h),
|
|
155
167
|
groups: @groups.map(&:dup),
|
|
168
|
+
draw_range: serialize_draw_range(@draw_range),
|
|
156
169
|
bounding_box: serialize_bounds(@bounding_box),
|
|
157
|
-
bounding_sphere: serialize_sphere(@bounding_sphere)
|
|
170
|
+
bounding_sphere: serialize_sphere(@bounding_sphere),
|
|
171
|
+
user_data: @user_data
|
|
158
172
|
}
|
|
159
173
|
end
|
|
160
174
|
|
|
@@ -172,6 +186,20 @@ module Three
|
|
|
172
186
|
{ center: sphere[:center].to_a, radius: sphere[:radius] }
|
|
173
187
|
end
|
|
174
188
|
|
|
189
|
+
def serialize_draw_range(draw_range)
|
|
190
|
+
{
|
|
191
|
+
start: serialize_number(draw_range[:start]),
|
|
192
|
+
count: serialize_number(draw_range[:count])
|
|
193
|
+
}
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def serialize_number(value)
|
|
197
|
+
return "Infinity" if value == Float::INFINITY
|
|
198
|
+
return "-Infinity" if value == -Float::INFINITY
|
|
199
|
+
|
|
200
|
+
value
|
|
201
|
+
end
|
|
202
|
+
|
|
175
203
|
def bind_attribute_change(attribute)
|
|
176
204
|
return unless attribute.respond_to?(:on)
|
|
177
205
|
|
data/lib/three/core/object3d.rb
CHANGED
|
@@ -27,8 +27,9 @@ module Three
|
|
|
27
27
|
attr_reader :layers
|
|
28
28
|
attr_reader :position, :rotation, :quaternion, :scale
|
|
29
29
|
attr_reader :matrix, :matrix_world
|
|
30
|
+
attr_reader :matrix_auto_update
|
|
30
31
|
attr_reader :name, :type, :up, :visible, :cast_shadow, :receive_shadow
|
|
31
|
-
attr_accessor :
|
|
32
|
+
attr_accessor :matrix_world_auto_update, :matrix_world_needs_update
|
|
32
33
|
attr_accessor :user_data
|
|
33
34
|
|
|
34
35
|
def initialize
|
|
@@ -52,6 +53,7 @@ module Three
|
|
|
52
53
|
@matrix_auto_update = DEFAULT_MATRIX_AUTO_UPDATE
|
|
53
54
|
@matrix_world_auto_update = DEFAULT_MATRIX_WORLD_AUTO_UPDATE
|
|
54
55
|
@matrix_world_needs_update = false
|
|
56
|
+
@suppress_matrix_change = false
|
|
55
57
|
@visible = true
|
|
56
58
|
@cast_shadow = false
|
|
57
59
|
@receive_shadow = false
|
|
@@ -59,6 +61,7 @@ module Three
|
|
|
59
61
|
|
|
60
62
|
bind_rotation_and_quaternion
|
|
61
63
|
bind_transform_changes
|
|
64
|
+
bind_matrix_changes
|
|
62
65
|
bind_layer_changes
|
|
63
66
|
mark_dirty!
|
|
64
67
|
end
|
|
@@ -93,6 +96,18 @@ module Three
|
|
|
93
96
|
mark_dirty!(:properties)
|
|
94
97
|
end
|
|
95
98
|
|
|
99
|
+
def matrix=(value)
|
|
100
|
+
@matrix = coerce_matrix4(value)
|
|
101
|
+
bind_matrix_changes
|
|
102
|
+
@matrix_world_needs_update = true
|
|
103
|
+
mark_dirty!(:transform)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def matrix_auto_update=(value)
|
|
107
|
+
@matrix_auto_update = value
|
|
108
|
+
mark_dirty!(:transform)
|
|
109
|
+
end
|
|
110
|
+
|
|
96
111
|
def mark_dirty!(field = :all)
|
|
97
112
|
super
|
|
98
113
|
@parent&.mark_descendant_dirty!
|
|
@@ -188,9 +203,13 @@ module Three
|
|
|
188
203
|
end
|
|
189
204
|
|
|
190
205
|
def update_matrix
|
|
206
|
+
@suppress_matrix_change = true
|
|
191
207
|
@matrix.compose(@position, @quaternion, @scale)
|
|
208
|
+
@suppress_matrix_change = false
|
|
192
209
|
@matrix_world_needs_update = true
|
|
193
210
|
self
|
|
211
|
+
ensure
|
|
212
|
+
@suppress_matrix_change = false
|
|
194
213
|
end
|
|
195
214
|
|
|
196
215
|
def update_matrix_world(force = false)
|
|
@@ -324,8 +343,27 @@ module Three
|
|
|
324
343
|
end
|
|
325
344
|
end
|
|
326
345
|
|
|
346
|
+
def bind_matrix_changes
|
|
347
|
+
@matrix.on_change do
|
|
348
|
+
next if @suppress_matrix_change
|
|
349
|
+
|
|
350
|
+
@matrix_world_needs_update = true
|
|
351
|
+
mark_dirty!(:transform)
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
|
|
327
355
|
def bind_layer_changes
|
|
328
356
|
@layers.on_change { mark_dirty!(:properties) }
|
|
329
357
|
end
|
|
358
|
+
|
|
359
|
+
def coerce_matrix4(value)
|
|
360
|
+
return value if value.is_a?(Matrix4)
|
|
361
|
+
|
|
362
|
+
array = value.to_ary if value.respond_to?(:to_ary)
|
|
363
|
+
array ||= value.to_a if value.respond_to?(:to_a)
|
|
364
|
+
return Matrix4.new.from_array(array) if array && array.length >= 16
|
|
365
|
+
|
|
366
|
+
raise TypeError, "matrix must be a Three::Matrix4 or an array-like with 16 elements"
|
|
367
|
+
end
|
|
330
368
|
end
|
|
331
369
|
end
|
|
@@ -58,6 +58,7 @@ module Three
|
|
|
58
58
|
user_data: object.user_data,
|
|
59
59
|
children: object.children.map { |child| serialize_object(child) }
|
|
60
60
|
}
|
|
61
|
+
data[:matrix] = object.matrix.to_a unless object.matrix_auto_update
|
|
61
62
|
|
|
62
63
|
serialize_scene(object, data) if object.is_a?(Scene)
|
|
63
64
|
serialize_camera(object, data) if object.is_a?(Camera)
|
|
@@ -71,6 +72,8 @@ module Three
|
|
|
71
72
|
def serialize_scene(scene, data)
|
|
72
73
|
data[:background] = serialize_texture_reference(scene.background)
|
|
73
74
|
data[:environment] = serialize_texture_reference(scene.environment)
|
|
75
|
+
data[:fog] = scene.fog.to_h if scene.fog
|
|
76
|
+
data[:override_material] = register_material(scene.override_material) if scene.override_material
|
|
74
77
|
end
|
|
75
78
|
|
|
76
79
|
def serialize_camera(camera, data)
|