three-rb 0.1.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.
Files changed (145) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +19 -0
  3. data/LICENSE +21 -0
  4. data/README.md +153 -0
  5. data/docs/browser-runtime.md +153 -0
  6. data/docs/implementation-plan.md +874 -0
  7. data/docs/loaded-assets-design.md +400 -0
  8. data/docs/next-work.md +107 -0
  9. data/docs/publishing.md +64 -0
  10. data/docs/release-readiness.md +83 -0
  11. data/examples/browser/README.md +54 -0
  12. data/examples/browser/assets/animated_triangle.gltf +123 -0
  13. data/examples/browser/assets/checker.svg +11 -0
  14. data/examples/browser/assets/compressed_triangle.gltf +74 -0
  15. data/examples/browser/assets/studio.hdr +5 -0
  16. data/examples/browser/assets/triangle.gltf +67 -0
  17. data/examples/browser/composition/README.md +35 -0
  18. data/examples/browser/composition/boot.mjs +6 -0
  19. data/examples/browser/composition/index.html +136 -0
  20. data/examples/browser/composition/main.rb +216 -0
  21. data/examples/browser/composition/smoke_test.mjs +266 -0
  22. data/examples/browser/cube/README.md +41 -0
  23. data/examples/browser/cube/boot.mjs +6 -0
  24. data/examples/browser/cube/index.html +142 -0
  25. data/examples/browser/cube/main.rb +62 -0
  26. data/examples/browser/cube/smoke_test.mjs +99 -0
  27. data/examples/browser/cubemap/README.md +23 -0
  28. data/examples/browser/cubemap/boot.mjs +6 -0
  29. data/examples/browser/cubemap/index.html +142 -0
  30. data/examples/browser/cubemap/main.rb +84 -0
  31. data/examples/browser/cubemap/smoke_test.mjs +91 -0
  32. data/examples/browser/gltf/README.md +23 -0
  33. data/examples/browser/gltf/boot.mjs +6 -0
  34. data/examples/browser/gltf/index.html +142 -0
  35. data/examples/browser/gltf/main.rb +105 -0
  36. data/examples/browser/gltf/smoke_test.mjs +162 -0
  37. data/examples/browser/picking/README.md +33 -0
  38. data/examples/browser/picking/boot.mjs +6 -0
  39. data/examples/browser/picking/index.html +142 -0
  40. data/examples/browser/picking/main.rb +113 -0
  41. data/examples/browser/picking/smoke_test.mjs +78 -0
  42. data/examples/browser/postprocessing/README.md +26 -0
  43. data/examples/browser/postprocessing/boot.mjs +6 -0
  44. data/examples/browser/postprocessing/index.html +142 -0
  45. data/examples/browser/postprocessing/main.rb +117 -0
  46. data/examples/browser/postprocessing/smoke_test.mjs +121 -0
  47. data/examples/browser/primitives/README.md +33 -0
  48. data/examples/browser/primitives/boot.mjs +6 -0
  49. data/examples/browser/primitives/index.html +142 -0
  50. data/examples/browser/primitives/main.rb +116 -0
  51. data/examples/browser/primitives/smoke_test.mjs +113 -0
  52. data/examples/browser/serialization/README.md +33 -0
  53. data/examples/browser/serialization/boot.mjs +6 -0
  54. data/examples/browser/serialization/index.html +142 -0
  55. data/examples/browser/serialization/main.rb +97 -0
  56. data/examples/browser/serialization/smoke_test.mjs +67 -0
  57. data/examples/browser/shared/boot.mjs +79 -0
  58. data/examples/browser/shared/smoke_test_helpers.mjs +151 -0
  59. data/examples/browser/textures/README.md +35 -0
  60. data/examples/browser/textures/boot.mjs +6 -0
  61. data/examples/browser/textures/index.html +142 -0
  62. data/examples/browser/textures/main.rb +142 -0
  63. data/examples/browser/textures/smoke_test.mjs +189 -0
  64. data/lib/three/animation/animation_action.rb +57 -0
  65. data/lib/three/animation/animation_clip.rb +22 -0
  66. data/lib/three/animation/animation_mixer.rb +43 -0
  67. data/lib/three/backends/base.rb +87 -0
  68. data/lib/three/backends/threejs/materialization.rb +143 -0
  69. data/lib/three/backends/threejs/parameters.rb +97 -0
  70. data/lib/three/backends/threejs/resource_management.rb +69 -0
  71. data/lib/three/backends/threejs/ruby_wasm_adapter.rb +873 -0
  72. data/lib/three/backends/threejs/synchronization.rb +224 -0
  73. data/lib/three/backends/threejs.rb +365 -0
  74. data/lib/three/cameras/camera.rb +39 -0
  75. data/lib/three/cameras/orthographic_camera.rb +107 -0
  76. data/lib/three/cameras/perspective_camera.rb +137 -0
  77. data/lib/three/constants.rb +40 -0
  78. data/lib/three/controls/orbit_controls.rb +118 -0
  79. data/lib/three/core/buffer_attribute.rb +151 -0
  80. data/lib/three/core/buffer_geometry.rb +181 -0
  81. data/lib/three/core/clock.rb +58 -0
  82. data/lib/three/core/event_dispatcher.rb +57 -0
  83. data/lib/three/core/layers.rb +75 -0
  84. data/lib/three/core/object3d.rb +331 -0
  85. data/lib/three/core/raycaster.rb +73 -0
  86. data/lib/three/dirty.rb +58 -0
  87. data/lib/three/exporters/three_json_exporter.rb +187 -0
  88. data/lib/three/geometries/box_geometry.rb +97 -0
  89. data/lib/three/geometries/plane_geometry.rb +70 -0
  90. data/lib/three/geometries/sphere_geometry.rb +107 -0
  91. data/lib/three/lights/ambient_light.rb +12 -0
  92. data/lib/three/lights/directional_light.rb +38 -0
  93. data/lib/three/lights/hemisphere_light.rb +34 -0
  94. data/lib/three/lights/light.rb +85 -0
  95. data/lib/three/lights/point_light.rb +33 -0
  96. data/lib/three/loaders/cube_texture_loader.rb +13 -0
  97. data/lib/three/loaders/gltf_loader.rb +48 -0
  98. data/lib/three/loaders/rgbe_loader.rb +15 -0
  99. data/lib/three/loaders/texture_loader.rb +13 -0
  100. data/lib/three/loaders/three_json_loader.rb +409 -0
  101. data/lib/three/materials/line_basic_material.rb +65 -0
  102. data/lib/three/materials/material.rb +158 -0
  103. data/lib/three/materials/mesh_basic_material.rb +64 -0
  104. data/lib/three/materials/mesh_lambert_material.rb +71 -0
  105. data/lib/three/materials/mesh_matcap_material.rb +86 -0
  106. data/lib/three/materials/mesh_normal_material.rb +42 -0
  107. data/lib/three/materials/mesh_phong_material.rb +119 -0
  108. data/lib/three/materials/mesh_physical_material.rb +155 -0
  109. data/lib/three/materials/mesh_standard_material.rb +149 -0
  110. data/lib/three/materials/mesh_toon_material.rb +98 -0
  111. data/lib/three/materials/points_material.rb +74 -0
  112. data/lib/three/materials/shadow_material.rb +45 -0
  113. data/lib/three/materials/sprite_material.rb +75 -0
  114. data/lib/three/math/color.rb +133 -0
  115. data/lib/three/math/euler.rb +197 -0
  116. data/lib/three/math/math_utils.rb +36 -0
  117. data/lib/three/math/matrix3.rb +255 -0
  118. data/lib/three/math/matrix4.rb +448 -0
  119. data/lib/three/math/quaternion.rb +277 -0
  120. data/lib/three/math/vector2.rb +95 -0
  121. data/lib/three/math/vector3.rb +396 -0
  122. data/lib/three/objects/external_object3d.rb +28 -0
  123. data/lib/three/objects/group.rb +12 -0
  124. data/lib/three/objects/instanced_mesh.rb +110 -0
  125. data/lib/three/objects/line.rb +41 -0
  126. data/lib/three/objects/mesh.rb +45 -0
  127. data/lib/three/objects/points.rb +41 -0
  128. data/lib/three/objects/sprite.rb +57 -0
  129. data/lib/three/postprocessing/dot_screen_pass.rb +83 -0
  130. data/lib/three/postprocessing/effect_composer.rb +56 -0
  131. data/lib/three/postprocessing/output_pass.rb +40 -0
  132. data/lib/three/postprocessing/render_pass.rb +42 -0
  133. data/lib/three/postprocessing/unreal_bloom_pass.rb +56 -0
  134. data/lib/three/renderers/renderer.rb +11 -0
  135. data/lib/three/renderers/threejs_renderer.rb +85 -0
  136. data/lib/three/scenes/scene.rb +29 -0
  137. data/lib/three/textures/cube_texture.rb +72 -0
  138. data/lib/three/textures/rgbe_texture.rb +45 -0
  139. data/lib/three/textures/texture.rb +200 -0
  140. data/lib/three/version.rb +5 -0
  141. data/lib/three-rb.rb +3 -0
  142. data/lib/three.rb +77 -0
  143. data/package.json +30 -0
  144. data/pnpm-lock.yaml +86 -0
  145. metadata +216 -0
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../constants"
4
+ require_relative "../core/event_dispatcher"
5
+ require_relative "../dirty"
6
+ require_relative "../math/math_utils"
7
+
8
+ module Three
9
+ class Material < EventDispatcher
10
+ include Dirty
11
+
12
+ @next_id = 0
13
+
14
+ class << self
15
+ attr_accessor :next_id
16
+ end
17
+
18
+ attr_reader :id, :uuid
19
+ attr_reader :name, :type, :side, :opacity, :transparent, :visible, :blending, :vertex_colors
20
+ attr_accessor :user_data
21
+
22
+ def initialize(parameters = nil)
23
+ super()
24
+ @id = self.class.allocate_id
25
+ @uuid = MathUtils.generate_uuid
26
+ @name = ""
27
+ @type = "Material"
28
+ @blending = NormalBlending
29
+ @side = FrontSide
30
+ @vertex_colors = false
31
+ @opacity = 1
32
+ @transparent = false
33
+ @visible = true
34
+ @user_data = {}
35
+ @needs_update = false
36
+ set_values(parameters) if parameters
37
+ mark_dirty!
38
+ end
39
+
40
+ def name=(value)
41
+ @name = value
42
+ mark_dirty!(:parameters)
43
+ end
44
+
45
+ def type=(value)
46
+ @type = value
47
+ mark_dirty!(:parameters)
48
+ end
49
+
50
+ def blending=(value)
51
+ @blending = value
52
+ mark_dirty!(:parameters)
53
+ end
54
+
55
+ def side=(value)
56
+ @side = value
57
+ mark_dirty!(:parameters)
58
+ end
59
+
60
+ def vertex_colors=(value)
61
+ @vertex_colors = value
62
+ mark_dirty!(:parameters)
63
+ end
64
+
65
+ def opacity=(value)
66
+ @opacity = value
67
+ mark_dirty!(:parameters)
68
+ end
69
+
70
+ def transparent=(value)
71
+ @transparent = value
72
+ mark_dirty!(:parameters)
73
+ end
74
+
75
+ def visible=(value)
76
+ @visible = value
77
+ mark_dirty!(:parameters)
78
+ end
79
+
80
+ def needs_update
81
+ @needs_update
82
+ end
83
+
84
+ def needs_update=(value)
85
+ @needs_update = value
86
+ mark_dirty!(:parameters) if value
87
+ end
88
+
89
+ def needs_update!
90
+ self.needs_update = true
91
+ self
92
+ end
93
+
94
+ def self.allocate_id
95
+ id = Material.next_id
96
+ Material.next_id += 1
97
+ id
98
+ end
99
+
100
+ def set_values(values)
101
+ values.each do |key, value|
102
+ setter = "#{key}="
103
+ next unless respond_to?(setter)
104
+
105
+ current_value = public_send(key) if respond_to?(key)
106
+ if current_value.respond_to?(:set)
107
+ current_value.set(value)
108
+ else
109
+ public_send(setter, value)
110
+ end
111
+ end
112
+ self
113
+ end
114
+
115
+ def dispose
116
+ dispatch_event(:dispose)
117
+ end
118
+
119
+ def dirty_dependency_changed(_resource, _field)
120
+ mark_dirty!(:textures)
121
+ end
122
+
123
+ def texture_slots
124
+ respond_to?(:map) ? [:map] : []
125
+ end
126
+
127
+ def textures
128
+ texture_slots.each_with_object([]) do |slot, result|
129
+ texture = public_send(slot)
130
+ result << texture if texture && !result.include?(texture)
131
+ end
132
+ end
133
+
134
+ def to_h
135
+ {
136
+ uuid: @uuid,
137
+ type: @type,
138
+ name: @name,
139
+ side: @side,
140
+ opacity: @opacity,
141
+ transparent: @transparent,
142
+ visible: @visible,
143
+ vertex_colors: @vertex_colors
144
+ }
145
+ end
146
+
147
+ private
148
+
149
+ def replace_texture_slot(slot, value)
150
+ current = instance_variable_get(:"@#{slot}")
151
+ current.remove_dirty_dependent(self) if current.respond_to?(:remove_dirty_dependent)
152
+
153
+ instance_variable_set(:"@#{slot}", value)
154
+ value.add_dirty_dependent(self) if value.respond_to?(:add_dirty_dependent)
155
+ mark_dirty!(:parameters)
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../math/color"
4
+ require_relative "material"
5
+
6
+ module Three
7
+ class MeshBasicMaterial < Material
8
+ attr_reader :color, :wireframe, :wireframe_linewidth, :fog, :map
9
+
10
+ def initialize(parameters = nil)
11
+ super(nil)
12
+ @type = "MeshBasicMaterial"
13
+ @color = Color.new(0xffffff)
14
+ @map = nil
15
+ @wireframe = false
16
+ @wireframe_linewidth = 1
17
+ @fog = true
18
+ bind_color_changes
19
+ set_values(parameters) if parameters
20
+ mark_dirty!
21
+ end
22
+
23
+ def color=(value)
24
+ @color = value.is_a?(Color) ? value : Color.new(value)
25
+ bind_color_changes
26
+ mark_dirty!(:parameters)
27
+ end
28
+
29
+ def map=(value)
30
+ replace_texture_slot(:map, value)
31
+ end
32
+
33
+ def wireframe=(value)
34
+ @wireframe = value
35
+ mark_dirty!(:parameters)
36
+ end
37
+
38
+ def wireframe_linewidth=(value)
39
+ @wireframe_linewidth = value
40
+ mark_dirty!(:parameters)
41
+ end
42
+
43
+ def fog=(value)
44
+ @fog = value
45
+ mark_dirty!(:parameters)
46
+ end
47
+
48
+ def to_h
49
+ super.merge(
50
+ color: @color.hex,
51
+ map: @map&.to_h,
52
+ wireframe: @wireframe,
53
+ wireframe_linewidth: @wireframe_linewidth,
54
+ fog: @fog
55
+ )
56
+ end
57
+
58
+ private
59
+
60
+ def bind_color_changes
61
+ @color.on_change { mark_dirty!(:parameters) }
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../math/color"
4
+ require_relative "material"
5
+
6
+ module Three
7
+ class MeshLambertMaterial < Material
8
+ attr_reader :color, :wireframe, :wireframe_linewidth, :fog, :flat_shading, :map
9
+
10
+ def initialize(parameters = nil)
11
+ super(nil)
12
+ @type = "MeshLambertMaterial"
13
+ @color = Color.new(0xffffff)
14
+ @map = nil
15
+ @wireframe = false
16
+ @wireframe_linewidth = 1
17
+ @fog = true
18
+ @flat_shading = false
19
+ bind_color_changes
20
+ set_values(parameters) if parameters
21
+ mark_dirty!
22
+ end
23
+
24
+ def color=(value)
25
+ @color = value.is_a?(Color) ? value : Color.new(value)
26
+ bind_color_changes
27
+ mark_dirty!(:parameters)
28
+ end
29
+
30
+ def map=(value)
31
+ replace_texture_slot(:map, value)
32
+ end
33
+
34
+ def wireframe=(value)
35
+ @wireframe = value
36
+ mark_dirty!(:parameters)
37
+ end
38
+
39
+ def wireframe_linewidth=(value)
40
+ @wireframe_linewidth = value
41
+ mark_dirty!(:parameters)
42
+ end
43
+
44
+ def fog=(value)
45
+ @fog = value
46
+ mark_dirty!(:parameters)
47
+ end
48
+
49
+ def flat_shading=(value)
50
+ @flat_shading = value
51
+ mark_dirty!(:parameters)
52
+ end
53
+
54
+ def to_h
55
+ super.merge(
56
+ color: @color.hex,
57
+ map: @map&.to_h,
58
+ wireframe: @wireframe,
59
+ wireframe_linewidth: @wireframe_linewidth,
60
+ fog: @fog,
61
+ flat_shading: @flat_shading
62
+ )
63
+ end
64
+
65
+ private
66
+
67
+ def bind_color_changes
68
+ @color.on_change { mark_dirty!(:parameters) }
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../math/color"
4
+ require_relative "material"
5
+
6
+ module Three
7
+ class MeshMatcapMaterial < Material
8
+ TEXTURE_SLOTS = %i[
9
+ matcap
10
+ map
11
+ alpha_map
12
+ bump_map
13
+ displacement_map
14
+ normal_map
15
+ ].freeze
16
+
17
+ attr_reader :color, :wireframe, :wireframe_linewidth, :fog, :flat_shading
18
+ attr_reader(*TEXTURE_SLOTS)
19
+
20
+ def initialize(parameters = nil)
21
+ super(nil)
22
+ @type = "MeshMatcapMaterial"
23
+ @color = Color.new(0xffffff)
24
+ TEXTURE_SLOTS.each { |slot| instance_variable_set(:"@#{slot}", nil) }
25
+ @wireframe = false
26
+ @wireframe_linewidth = 1
27
+ @fog = true
28
+ @flat_shading = false
29
+ bind_color_changes
30
+ set_values(parameters) if parameters
31
+ mark_dirty!
32
+ end
33
+
34
+ def color=(value)
35
+ @color = value.is_a?(Color) ? value : Color.new(value)
36
+ bind_color_changes
37
+ mark_dirty!(:parameters)
38
+ end
39
+
40
+ TEXTURE_SLOTS.each do |slot|
41
+ define_method(:"#{slot}=") { |value| replace_texture_slot(slot, value) }
42
+ end
43
+
44
+ def wireframe=(value)
45
+ @wireframe = value
46
+ mark_dirty!(:parameters)
47
+ end
48
+
49
+ def wireframe_linewidth=(value)
50
+ @wireframe_linewidth = value
51
+ mark_dirty!(:parameters)
52
+ end
53
+
54
+ def fog=(value)
55
+ @fog = value
56
+ mark_dirty!(:parameters)
57
+ end
58
+
59
+ def flat_shading=(value)
60
+ @flat_shading = value
61
+ mark_dirty!(:parameters)
62
+ end
63
+
64
+ def texture_slots
65
+ TEXTURE_SLOTS
66
+ end
67
+
68
+ def to_h
69
+ result = super.merge(
70
+ color: @color.hex,
71
+ wireframe: @wireframe,
72
+ wireframe_linewidth: @wireframe_linewidth,
73
+ fog: @fog,
74
+ flat_shading: @flat_shading
75
+ )
76
+ TEXTURE_SLOTS.each { |slot| result[slot] = public_send(slot)&.to_h }
77
+ result
78
+ end
79
+
80
+ private
81
+
82
+ def bind_color_changes
83
+ @color.on_change { mark_dirty!(:parameters) }
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "material"
4
+
5
+ module Three
6
+ class MeshNormalMaterial < Material
7
+ attr_reader :wireframe, :wireframe_linewidth, :flat_shading
8
+
9
+ def initialize(parameters = nil)
10
+ super(nil)
11
+ @type = "MeshNormalMaterial"
12
+ @wireframe = false
13
+ @wireframe_linewidth = 1
14
+ @flat_shading = false
15
+ set_values(parameters) if parameters
16
+ mark_dirty!
17
+ end
18
+
19
+ def wireframe=(value)
20
+ @wireframe = value
21
+ mark_dirty!(:parameters)
22
+ end
23
+
24
+ def wireframe_linewidth=(value)
25
+ @wireframe_linewidth = value
26
+ mark_dirty!(:parameters)
27
+ end
28
+
29
+ def flat_shading=(value)
30
+ @flat_shading = value
31
+ mark_dirty!(:parameters)
32
+ end
33
+
34
+ def to_h
35
+ super.merge(
36
+ wireframe: @wireframe,
37
+ wireframe_linewidth: @wireframe_linewidth,
38
+ flat_shading: @flat_shading
39
+ )
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../math/color"
4
+ require_relative "material"
5
+
6
+ module Three
7
+ class MeshPhongMaterial < Material
8
+ TEXTURE_SLOTS = %i[
9
+ map
10
+ alpha_map
11
+ ao_map
12
+ bump_map
13
+ displacement_map
14
+ emissive_map
15
+ env_map
16
+ light_map
17
+ normal_map
18
+ specular_map
19
+ ].freeze
20
+
21
+ attr_reader :color, :specular, :emissive, :shininess, :wireframe, :wireframe_linewidth, :fog, :flat_shading
22
+ attr_reader(*TEXTURE_SLOTS)
23
+
24
+ def initialize(parameters = nil)
25
+ super(nil)
26
+ @type = "MeshPhongMaterial"
27
+ @color = Color.new(0xffffff)
28
+ @specular = Color.new(0x111111)
29
+ @emissive = Color.new(0x000000)
30
+ @shininess = 30
31
+ TEXTURE_SLOTS.each { |slot| instance_variable_set(:"@#{slot}", nil) }
32
+ @wireframe = false
33
+ @wireframe_linewidth = 1
34
+ @fog = true
35
+ @flat_shading = false
36
+ bind_color_changes
37
+ set_values(parameters) if parameters
38
+ mark_dirty!
39
+ end
40
+
41
+ def color=(value)
42
+ @color = value.is_a?(Color) ? value : Color.new(value)
43
+ bind_color_changes
44
+ mark_dirty!(:parameters)
45
+ end
46
+
47
+ def specular=(value)
48
+ @specular = value.is_a?(Color) ? value : Color.new(value)
49
+ bind_color_changes
50
+ mark_dirty!(:parameters)
51
+ end
52
+
53
+ def emissive=(value)
54
+ @emissive = value.is_a?(Color) ? value : Color.new(value)
55
+ bind_color_changes
56
+ mark_dirty!(:parameters)
57
+ end
58
+
59
+ def shininess=(value)
60
+ @shininess = value
61
+ mark_dirty!(:parameters)
62
+ end
63
+
64
+ TEXTURE_SLOTS.each do |slot|
65
+ define_method(:"#{slot}=") { |value| set_texture_slot(slot, value) }
66
+ end
67
+
68
+ def wireframe=(value)
69
+ @wireframe = value
70
+ mark_dirty!(:parameters)
71
+ end
72
+
73
+ def wireframe_linewidth=(value)
74
+ @wireframe_linewidth = value
75
+ mark_dirty!(:parameters)
76
+ end
77
+
78
+ def fog=(value)
79
+ @fog = value
80
+ mark_dirty!(:parameters)
81
+ end
82
+
83
+ def flat_shading=(value)
84
+ @flat_shading = value
85
+ mark_dirty!(:parameters)
86
+ end
87
+
88
+ def texture_slots
89
+ TEXTURE_SLOTS
90
+ end
91
+
92
+ def to_h
93
+ result = super.merge(
94
+ color: @color.hex,
95
+ specular: @specular.hex,
96
+ emissive: @emissive.hex,
97
+ shininess: @shininess,
98
+ wireframe: @wireframe,
99
+ wireframe_linewidth: @wireframe_linewidth,
100
+ fog: @fog,
101
+ flat_shading: @flat_shading
102
+ )
103
+ TEXTURE_SLOTS.each { |slot| result[slot] = public_send(slot)&.to_h }
104
+ result
105
+ end
106
+
107
+ private
108
+
109
+ def bind_color_changes
110
+ @color.on_change { mark_dirty!(:parameters) }
111
+ @specular.on_change { mark_dirty!(:parameters) }
112
+ @emissive.on_change { mark_dirty!(:parameters) }
113
+ end
114
+
115
+ def set_texture_slot(slot, value)
116
+ replace_texture_slot(slot, value)
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../math/color"
4
+ require_relative "mesh_standard_material"
5
+
6
+ module Three
7
+ class MeshPhysicalMaterial < MeshStandardMaterial
8
+ PHYSICAL_TEXTURE_SLOTS = %i[
9
+ anisotropy_map
10
+ clearcoat_map
11
+ clearcoat_normal_map
12
+ clearcoat_roughness_map
13
+ transmission_map
14
+ thickness_map
15
+ iridescence_map
16
+ iridescence_thickness_map
17
+ sheen_color_map
18
+ sheen_roughness_map
19
+ specular_color_map
20
+ specular_intensity_map
21
+ ].freeze
22
+
23
+ TEXTURE_SLOTS = (MeshStandardMaterial::TEXTURE_SLOTS + PHYSICAL_TEXTURE_SLOTS).freeze
24
+
25
+ attr_reader :anisotropy, :anisotropy_rotation, :clearcoat, :clearcoat_roughness, :transmission, :thickness, :ior
26
+ attr_reader :iridescence, :iridescence_ior, :iridescence_thickness_range, :sheen, :sheen_color, :sheen_roughness
27
+ attr_reader :dispersion, :specular_intensity, :specular_color, :attenuation_distance, :attenuation_color
28
+ attr_reader(*PHYSICAL_TEXTURE_SLOTS)
29
+
30
+ def initialize(parameters = nil)
31
+ super(nil)
32
+ @type = "MeshPhysicalMaterial"
33
+ @anisotropy = 0
34
+ @anisotropy_rotation = 0
35
+ @clearcoat = 0
36
+ @clearcoat_roughness = 0
37
+ @transmission = 0
38
+ @thickness = 0
39
+ @ior = 1.5
40
+ @iridescence = 0
41
+ @iridescence_ior = 1.3
42
+ @iridescence_thickness_range = [100, 400]
43
+ @sheen = 0
44
+ @sheen_color = Color.new(0x000000)
45
+ @sheen_roughness = 1
46
+ @dispersion = 0
47
+ @specular_intensity = 1
48
+ @specular_color = Color.new(0xffffff)
49
+ @attenuation_distance = nil
50
+ @attenuation_color = Color.new(0xffffff)
51
+ PHYSICAL_TEXTURE_SLOTS.each { |slot| instance_variable_set(:"@#{slot}", nil) }
52
+ bind_physical_color_changes
53
+ set_values(parameters) if parameters
54
+ mark_dirty!
55
+ end
56
+
57
+ %i[
58
+ anisotropy
59
+ anisotropy_rotation
60
+ clearcoat
61
+ clearcoat_roughness
62
+ transmission
63
+ thickness
64
+ ior
65
+ iridescence
66
+ iridescence_ior
67
+ sheen
68
+ sheen_roughness
69
+ dispersion
70
+ specular_intensity
71
+ attenuation_distance
72
+ ].each do |name|
73
+ define_method("#{name}=") do |value|
74
+ instance_variable_set(:"@#{name}", value)
75
+ mark_dirty!(:parameters)
76
+ end
77
+ end
78
+
79
+ def iridescence_thickness_range=(value)
80
+ @iridescence_thickness_range = value&.dup
81
+ mark_dirty!(:parameters)
82
+ end
83
+
84
+ def reflectivity
85
+ MathUtils.clamp(2.5 * (@ior - 1) / (@ior + 1), 0, 1)
86
+ end
87
+
88
+ def reflectivity=(value)
89
+ return if value.nil?
90
+
91
+ @ior = (1 + 0.4 * value) / (1 - 0.4 * value)
92
+ mark_dirty!(:parameters)
93
+ end
94
+
95
+ def sheen_color=(value)
96
+ @sheen_color = value.is_a?(Color) ? value : Color.new(value)
97
+ bind_physical_color_changes
98
+ mark_dirty!(:parameters)
99
+ end
100
+
101
+ def specular_color=(value)
102
+ @specular_color = value.is_a?(Color) ? value : Color.new(value)
103
+ bind_physical_color_changes
104
+ mark_dirty!(:parameters)
105
+ end
106
+
107
+ def attenuation_color=(value)
108
+ @attenuation_color = value.is_a?(Color) ? value : Color.new(value)
109
+ bind_physical_color_changes
110
+ mark_dirty!(:parameters)
111
+ end
112
+
113
+ PHYSICAL_TEXTURE_SLOTS.each do |slot|
114
+ define_method(:"#{slot}=") { |value| set_texture_slot(slot, value) }
115
+ end
116
+
117
+ def texture_slots
118
+ TEXTURE_SLOTS
119
+ end
120
+
121
+ def to_h
122
+ result = super.merge(
123
+ anisotropy: @anisotropy,
124
+ anisotropy_rotation: @anisotropy_rotation,
125
+ clearcoat: @clearcoat,
126
+ clearcoat_roughness: @clearcoat_roughness,
127
+ transmission: @transmission,
128
+ thickness: @thickness,
129
+ ior: @ior,
130
+ reflectivity: reflectivity,
131
+ iridescence: @iridescence,
132
+ iridescence_ior: @iridescence_ior,
133
+ iridescence_thickness_range: @iridescence_thickness_range&.dup,
134
+ sheen: @sheen,
135
+ sheen_color: @sheen_color.hex,
136
+ sheen_roughness: @sheen_roughness,
137
+ dispersion: @dispersion,
138
+ specular_intensity: @specular_intensity,
139
+ specular_color: @specular_color.hex,
140
+ attenuation_distance: @attenuation_distance,
141
+ attenuation_color: @attenuation_color.hex
142
+ )
143
+ PHYSICAL_TEXTURE_SLOTS.each { |slot| result[slot] = public_send(slot)&.to_h }
144
+ result
145
+ end
146
+
147
+ private
148
+
149
+ def bind_physical_color_changes
150
+ @sheen_color.on_change { mark_dirty!(:parameters) }
151
+ @specular_color.on_change { mark_dirty!(:parameters) }
152
+ @attenuation_color.on_change { mark_dirty!(:parameters) }
153
+ end
154
+ end
155
+ end