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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +19 -0
- data/LICENSE +21 -0
- data/README.md +153 -0
- data/docs/browser-runtime.md +153 -0
- data/docs/implementation-plan.md +874 -0
- data/docs/loaded-assets-design.md +400 -0
- data/docs/next-work.md +107 -0
- data/docs/publishing.md +64 -0
- data/docs/release-readiness.md +83 -0
- data/examples/browser/README.md +54 -0
- data/examples/browser/assets/animated_triangle.gltf +123 -0
- data/examples/browser/assets/checker.svg +11 -0
- data/examples/browser/assets/compressed_triangle.gltf +74 -0
- data/examples/browser/assets/studio.hdr +5 -0
- data/examples/browser/assets/triangle.gltf +67 -0
- data/examples/browser/composition/README.md +35 -0
- data/examples/browser/composition/boot.mjs +6 -0
- data/examples/browser/composition/index.html +136 -0
- data/examples/browser/composition/main.rb +216 -0
- data/examples/browser/composition/smoke_test.mjs +266 -0
- data/examples/browser/cube/README.md +41 -0
- data/examples/browser/cube/boot.mjs +6 -0
- data/examples/browser/cube/index.html +142 -0
- data/examples/browser/cube/main.rb +62 -0
- data/examples/browser/cube/smoke_test.mjs +99 -0
- data/examples/browser/cubemap/README.md +23 -0
- data/examples/browser/cubemap/boot.mjs +6 -0
- data/examples/browser/cubemap/index.html +142 -0
- data/examples/browser/cubemap/main.rb +84 -0
- data/examples/browser/cubemap/smoke_test.mjs +91 -0
- data/examples/browser/gltf/README.md +23 -0
- data/examples/browser/gltf/boot.mjs +6 -0
- data/examples/browser/gltf/index.html +142 -0
- data/examples/browser/gltf/main.rb +105 -0
- data/examples/browser/gltf/smoke_test.mjs +162 -0
- data/examples/browser/picking/README.md +33 -0
- data/examples/browser/picking/boot.mjs +6 -0
- data/examples/browser/picking/index.html +142 -0
- data/examples/browser/picking/main.rb +113 -0
- data/examples/browser/picking/smoke_test.mjs +78 -0
- data/examples/browser/postprocessing/README.md +26 -0
- data/examples/browser/postprocessing/boot.mjs +6 -0
- data/examples/browser/postprocessing/index.html +142 -0
- data/examples/browser/postprocessing/main.rb +117 -0
- data/examples/browser/postprocessing/smoke_test.mjs +121 -0
- data/examples/browser/primitives/README.md +33 -0
- data/examples/browser/primitives/boot.mjs +6 -0
- data/examples/browser/primitives/index.html +142 -0
- data/examples/browser/primitives/main.rb +116 -0
- data/examples/browser/primitives/smoke_test.mjs +113 -0
- data/examples/browser/serialization/README.md +33 -0
- data/examples/browser/serialization/boot.mjs +6 -0
- data/examples/browser/serialization/index.html +142 -0
- data/examples/browser/serialization/main.rb +97 -0
- data/examples/browser/serialization/smoke_test.mjs +67 -0
- data/examples/browser/shared/boot.mjs +79 -0
- data/examples/browser/shared/smoke_test_helpers.mjs +151 -0
- data/examples/browser/textures/README.md +35 -0
- data/examples/browser/textures/boot.mjs +6 -0
- data/examples/browser/textures/index.html +142 -0
- data/examples/browser/textures/main.rb +142 -0
- data/examples/browser/textures/smoke_test.mjs +189 -0
- data/lib/three/animation/animation_action.rb +57 -0
- data/lib/three/animation/animation_clip.rb +22 -0
- data/lib/three/animation/animation_mixer.rb +43 -0
- data/lib/three/backends/base.rb +87 -0
- data/lib/three/backends/threejs/materialization.rb +143 -0
- data/lib/three/backends/threejs/parameters.rb +97 -0
- data/lib/three/backends/threejs/resource_management.rb +69 -0
- data/lib/three/backends/threejs/ruby_wasm_adapter.rb +873 -0
- data/lib/three/backends/threejs/synchronization.rb +224 -0
- data/lib/three/backends/threejs.rb +365 -0
- data/lib/three/cameras/camera.rb +39 -0
- data/lib/three/cameras/orthographic_camera.rb +107 -0
- data/lib/three/cameras/perspective_camera.rb +137 -0
- data/lib/three/constants.rb +40 -0
- data/lib/three/controls/orbit_controls.rb +118 -0
- data/lib/three/core/buffer_attribute.rb +151 -0
- data/lib/three/core/buffer_geometry.rb +181 -0
- data/lib/three/core/clock.rb +58 -0
- data/lib/three/core/event_dispatcher.rb +57 -0
- data/lib/three/core/layers.rb +75 -0
- data/lib/three/core/object3d.rb +331 -0
- data/lib/three/core/raycaster.rb +73 -0
- data/lib/three/dirty.rb +58 -0
- data/lib/three/exporters/three_json_exporter.rb +187 -0
- data/lib/three/geometries/box_geometry.rb +97 -0
- data/lib/three/geometries/plane_geometry.rb +70 -0
- data/lib/three/geometries/sphere_geometry.rb +107 -0
- data/lib/three/lights/ambient_light.rb +12 -0
- data/lib/three/lights/directional_light.rb +38 -0
- data/lib/three/lights/hemisphere_light.rb +34 -0
- data/lib/three/lights/light.rb +85 -0
- data/lib/three/lights/point_light.rb +33 -0
- data/lib/three/loaders/cube_texture_loader.rb +13 -0
- data/lib/three/loaders/gltf_loader.rb +48 -0
- data/lib/three/loaders/rgbe_loader.rb +15 -0
- data/lib/three/loaders/texture_loader.rb +13 -0
- data/lib/three/loaders/three_json_loader.rb +409 -0
- data/lib/three/materials/line_basic_material.rb +65 -0
- data/lib/three/materials/material.rb +158 -0
- data/lib/three/materials/mesh_basic_material.rb +64 -0
- data/lib/three/materials/mesh_lambert_material.rb +71 -0
- data/lib/three/materials/mesh_matcap_material.rb +86 -0
- data/lib/three/materials/mesh_normal_material.rb +42 -0
- data/lib/three/materials/mesh_phong_material.rb +119 -0
- data/lib/three/materials/mesh_physical_material.rb +155 -0
- data/lib/three/materials/mesh_standard_material.rb +149 -0
- data/lib/three/materials/mesh_toon_material.rb +98 -0
- data/lib/three/materials/points_material.rb +74 -0
- data/lib/three/materials/shadow_material.rb +45 -0
- data/lib/three/materials/sprite_material.rb +75 -0
- data/lib/three/math/color.rb +133 -0
- data/lib/three/math/euler.rb +197 -0
- data/lib/three/math/math_utils.rb +36 -0
- data/lib/three/math/matrix3.rb +255 -0
- data/lib/three/math/matrix4.rb +448 -0
- data/lib/three/math/quaternion.rb +277 -0
- data/lib/three/math/vector2.rb +95 -0
- data/lib/three/math/vector3.rb +396 -0
- data/lib/three/objects/external_object3d.rb +28 -0
- data/lib/three/objects/group.rb +12 -0
- data/lib/three/objects/instanced_mesh.rb +110 -0
- data/lib/three/objects/line.rb +41 -0
- data/lib/three/objects/mesh.rb +45 -0
- data/lib/three/objects/points.rb +41 -0
- data/lib/three/objects/sprite.rb +57 -0
- data/lib/three/postprocessing/dot_screen_pass.rb +83 -0
- data/lib/three/postprocessing/effect_composer.rb +56 -0
- data/lib/three/postprocessing/output_pass.rb +40 -0
- data/lib/three/postprocessing/render_pass.rb +42 -0
- data/lib/three/postprocessing/unreal_bloom_pass.rb +56 -0
- data/lib/three/renderers/renderer.rb +11 -0
- data/lib/three/renderers/threejs_renderer.rb +85 -0
- data/lib/three/scenes/scene.rb +29 -0
- data/lib/three/textures/cube_texture.rb +72 -0
- data/lib/three/textures/rgbe_texture.rb +45 -0
- data/lib/three/textures/texture.rb +200 -0
- data/lib/three/version.rb +5 -0
- data/lib/three-rb.rb +3 -0
- data/lib/three.rb +77 -0
- data/package.json +30 -0
- data/pnpm-lock.yaml +86 -0
- metadata +216 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../math/vector2"
|
|
4
|
+
require_relative "../math/vector3"
|
|
5
|
+
require_relative "../backends/threejs"
|
|
6
|
+
|
|
7
|
+
module Three
|
|
8
|
+
class Raycaster
|
|
9
|
+
class Intersection
|
|
10
|
+
attr_reader :distance, :point, :object, :object_handle, :uv, :face_index, :index, :instance_id, :raw
|
|
11
|
+
|
|
12
|
+
def initialize(distance:, point:, object:, object_handle:, uv: nil, face_index: nil, index: nil, instance_id: nil, raw: nil)
|
|
13
|
+
@distance = distance
|
|
14
|
+
@point = point
|
|
15
|
+
@object = object
|
|
16
|
+
@object_handle = object_handle
|
|
17
|
+
@uv = uv
|
|
18
|
+
@face_index = face_index
|
|
19
|
+
@index = index
|
|
20
|
+
@instance_id = instance_id
|
|
21
|
+
@raw = raw
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
attr_reader :backend, :handle
|
|
26
|
+
|
|
27
|
+
def initialize(backend: Backends::ThreeJS.new)
|
|
28
|
+
@backend = backend
|
|
29
|
+
@handle = @backend.create_raycaster
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def set_from_camera(coords, camera)
|
|
33
|
+
@backend.set_raycaster_from_camera(@handle, coerce_vector2(coords), camera)
|
|
34
|
+
self
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def intersect_object(object, recursive: false)
|
|
38
|
+
intersect_objects([object], recursive: recursive)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def intersect_objects(objects, recursive: false)
|
|
42
|
+
@backend.intersect_objects(@handle, objects, recursive: recursive).map do |entry|
|
|
43
|
+
build_intersection(entry)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def build_intersection(entry)
|
|
50
|
+
Intersection.new(
|
|
51
|
+
distance: entry[:distance],
|
|
52
|
+
point: entry[:point] ? Vector3.new(*entry[:point]) : nil,
|
|
53
|
+
object: entry[:object],
|
|
54
|
+
object_handle: entry[:object_handle],
|
|
55
|
+
uv: entry[:uv] ? Vector2.new(*entry[:uv]) : nil,
|
|
56
|
+
face_index: entry[:face_index],
|
|
57
|
+
index: entry[:index],
|
|
58
|
+
instance_id: entry[:instance_id],
|
|
59
|
+
raw: entry[:raw]
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def coerce_vector2(value)
|
|
64
|
+
return value.to_a if value.is_a?(Vector2)
|
|
65
|
+
|
|
66
|
+
array = value.to_ary if value.respond_to?(:to_ary)
|
|
67
|
+
array ||= value.to_a if value.respond_to?(:to_a)
|
|
68
|
+
raise TypeError, "coords must be a Three::Vector2 or an array-like [x, y]" unless array&.length == 2
|
|
69
|
+
|
|
70
|
+
array
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
data/lib/three/dirty.rb
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Three
|
|
4
|
+
module Dirty
|
|
5
|
+
def dirty?
|
|
6
|
+
!dirty_fields.empty?
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def dirty_fields
|
|
10
|
+
@dirty_fields ||= {}
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def dirty_field?(field)
|
|
14
|
+
dirty_fields.key?(:all) || dirty_fields.key?(field)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def dirty_dependents
|
|
18
|
+
@dirty_dependents ||= []
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def add_dirty_dependent(dependent)
|
|
22
|
+
dirty_dependents << dependent unless dirty_dependents.include?(dependent)
|
|
23
|
+
self
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def remove_dirty_dependent(dependent)
|
|
27
|
+
dirty_dependents.delete(dependent)
|
|
28
|
+
self
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def mark_dirty!(field = :all)
|
|
32
|
+
dirty_fields[field] = true
|
|
33
|
+
notify_dirty_dependents(field)
|
|
34
|
+
self
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def mark_clean!(*fields)
|
|
38
|
+
if fields.empty? || fields.include?(:all)
|
|
39
|
+
dirty_fields.clear
|
|
40
|
+
else
|
|
41
|
+
fields.each { |field| dirty_fields.delete(field) }
|
|
42
|
+
end
|
|
43
|
+
self
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def notify_dirty_dependents(field)
|
|
49
|
+
dirty_dependents.dup.each do |dependent|
|
|
50
|
+
if dependent.respond_to?(:dirty_dependency_changed)
|
|
51
|
+
dependent.dirty_dependency_changed(self, field)
|
|
52
|
+
elsif dependent.respond_to?(:mark_dirty!)
|
|
53
|
+
dependent.mark_dirty!(:dependency)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Three
|
|
6
|
+
module Exporters
|
|
7
|
+
class ThreeJSONExporter
|
|
8
|
+
FORMAT_VERSION = 1
|
|
9
|
+
|
|
10
|
+
def initialize(deterministic_ids: false)
|
|
11
|
+
@deterministic_ids = deterministic_ids
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def export(object)
|
|
15
|
+
raise TypeError, "object must be a Three::Object3D" unless object.is_a?(Object3D)
|
|
16
|
+
|
|
17
|
+
reset!
|
|
18
|
+
{
|
|
19
|
+
metadata: {
|
|
20
|
+
version: FORMAT_VERSION,
|
|
21
|
+
generator: "three-rb",
|
|
22
|
+
type: "Object"
|
|
23
|
+
},
|
|
24
|
+
object: serialize_object(object),
|
|
25
|
+
geometries: @geometries.values,
|
|
26
|
+
materials: @materials.values,
|
|
27
|
+
textures: @textures.values
|
|
28
|
+
}
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def to_json(object, *args)
|
|
32
|
+
export(object).to_json(*args)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def reset!
|
|
38
|
+
@geometries = {}
|
|
39
|
+
@materials = {}
|
|
40
|
+
@textures = {}
|
|
41
|
+
@stable_ids = {}
|
|
42
|
+
@stable_id_counts = Hash.new(0)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def serialize_object(object)
|
|
46
|
+
data = {
|
|
47
|
+
uuid: export_id(object, :object),
|
|
48
|
+
type: object.type,
|
|
49
|
+
name: object.name,
|
|
50
|
+
visible: object.visible,
|
|
51
|
+
layers: object.layers.mask,
|
|
52
|
+
cast_shadow: object.cast_shadow,
|
|
53
|
+
receive_shadow: object.receive_shadow,
|
|
54
|
+
matrix_auto_update: object.matrix_auto_update,
|
|
55
|
+
position: object.position.to_a,
|
|
56
|
+
quaternion: object.quaternion.to_a,
|
|
57
|
+
scale: object.scale.to_a,
|
|
58
|
+
user_data: object.user_data,
|
|
59
|
+
children: object.children.map { |child| serialize_object(child) }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
serialize_scene(object, data) if object.is_a?(Scene)
|
|
63
|
+
serialize_camera(object, data) if object.is_a?(Camera)
|
|
64
|
+
serialize_light(object, data) if object.is_a?(Light)
|
|
65
|
+
serialize_geometry_material_object(object, data) if geometry_material_object?(object)
|
|
66
|
+
data[:external] = true if object.is_a?(ExternalObject3D)
|
|
67
|
+
|
|
68
|
+
data
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def serialize_scene(scene, data)
|
|
72
|
+
data[:background] = serialize_texture_reference(scene.background)
|
|
73
|
+
data[:environment] = serialize_texture_reference(scene.environment)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def serialize_camera(camera, data)
|
|
77
|
+
data[:zoom] = camera.zoom if camera.respond_to?(:zoom)
|
|
78
|
+
|
|
79
|
+
case camera
|
|
80
|
+
when PerspectiveCamera
|
|
81
|
+
data[:fov] = camera.fov
|
|
82
|
+
data[:aspect] = camera.aspect
|
|
83
|
+
data[:near] = camera.near
|
|
84
|
+
data[:far] = camera.far
|
|
85
|
+
when OrthographicCamera
|
|
86
|
+
data[:left] = camera.left
|
|
87
|
+
data[:right] = camera.right
|
|
88
|
+
data[:top] = camera.top
|
|
89
|
+
data[:bottom] = camera.bottom
|
|
90
|
+
data[:near] = camera.near
|
|
91
|
+
data[:far] = camera.far
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def serialize_light(light, data)
|
|
96
|
+
data[:color] = light.color.hex
|
|
97
|
+
data[:intensity] = light.intensity
|
|
98
|
+
data[:shadow_map_size] = light.shadow_map_size.dup
|
|
99
|
+
data[:shadow_bias] = light.shadow_bias
|
|
100
|
+
data[:shadow_normal_bias] = light.shadow_normal_bias
|
|
101
|
+
data[:shadow_radius] = light.shadow_radius
|
|
102
|
+
data[:shadow_camera] = light.shadow_camera.dup if light.respond_to?(:shadow_camera)
|
|
103
|
+
data[:distance] = light.distance if light.respond_to?(:distance)
|
|
104
|
+
data[:decay] = light.decay if light.respond_to?(:decay)
|
|
105
|
+
data[:ground_color] = light.ground_color.hex if light.respond_to?(:ground_color)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def geometry_material_object?(object)
|
|
109
|
+
object.is_a?(Mesh) || object.is_a?(Line) || object.is_a?(Points) || object.is_a?(Sprite)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def serialize_geometry_material_object(object, data)
|
|
113
|
+
data[:geometry] = register_geometry(object.geometry) if object.respond_to?(:geometry)
|
|
114
|
+
data[:material] = register_material(object.material)
|
|
115
|
+
data[:center] = object.center.to_a if object.is_a?(Sprite)
|
|
116
|
+
|
|
117
|
+
return unless object.is_a?(InstancedMesh)
|
|
118
|
+
|
|
119
|
+
data[:count] = object.count
|
|
120
|
+
data[:capacity] = object.capacity
|
|
121
|
+
data[:instance_matrices] = object.instance_matrices.map(&:to_a)
|
|
122
|
+
data[:instance_colors] = object.instance_colors&.map(&:to_a)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def register_geometry(geometry)
|
|
126
|
+
return nil unless geometry&.respond_to?(:uuid)
|
|
127
|
+
|
|
128
|
+
@geometries[geometry.uuid] ||= serialize_geometry(geometry)
|
|
129
|
+
export_id(geometry, :geometry)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def serialize_geometry(geometry)
|
|
133
|
+
data = geometry.to_h
|
|
134
|
+
data[:uuid] = export_id(geometry, :geometry)
|
|
135
|
+
data[:parameters] = geometry.parameters.dup if geometry.respond_to?(:parameters)
|
|
136
|
+
data
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def register_material(material)
|
|
140
|
+
return material.map { |entry| register_material(entry) } if material.is_a?(Array)
|
|
141
|
+
return nil unless material&.respond_to?(:uuid)
|
|
142
|
+
|
|
143
|
+
@materials[material.uuid] ||= serialize_material(material)
|
|
144
|
+
export_id(material, :material)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def serialize_material(material)
|
|
148
|
+
data = material.to_h
|
|
149
|
+
data[:uuid] = export_id(material, :material)
|
|
150
|
+
material.textures.each { |texture| register_texture(texture) } if material.respond_to?(:textures)
|
|
151
|
+
|
|
152
|
+
return data unless material.respond_to?(:texture_slots)
|
|
153
|
+
|
|
154
|
+
material.texture_slots.each do |slot|
|
|
155
|
+
data[slot] = serialize_texture_reference(material.public_send(slot))
|
|
156
|
+
end
|
|
157
|
+
data
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def serialize_texture_reference(texture)
|
|
161
|
+
return nil unless texture
|
|
162
|
+
return texture unless texture.respond_to?(:uuid)
|
|
163
|
+
|
|
164
|
+
register_texture(texture)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def register_texture(texture)
|
|
168
|
+
@textures[texture.uuid] ||= serialize_texture(texture)
|
|
169
|
+
export_id(texture, :texture)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def serialize_texture(texture)
|
|
173
|
+
texture.to_h.merge(uuid: export_id(texture, :texture))
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def export_id(resource, prefix)
|
|
177
|
+
return resource.uuid unless @deterministic_ids
|
|
178
|
+
|
|
179
|
+
@stable_ids[resource.uuid] ||= begin
|
|
180
|
+
index = @stable_id_counts[prefix]
|
|
181
|
+
@stable_id_counts[prefix] += 1
|
|
182
|
+
"#{prefix}-#{index}"
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../core/buffer_attribute"
|
|
4
|
+
require_relative "../core/buffer_geometry"
|
|
5
|
+
|
|
6
|
+
module Three
|
|
7
|
+
class BoxGeometry < BufferGeometry
|
|
8
|
+
attr_accessor :parameters
|
|
9
|
+
|
|
10
|
+
def initialize(width = 1, height = 1, depth = 1, width_segments: 1, height_segments: 1, depth_segments: 1)
|
|
11
|
+
super()
|
|
12
|
+
@type = "BoxGeometry"
|
|
13
|
+
@parameters = {
|
|
14
|
+
width: width,
|
|
15
|
+
height: height,
|
|
16
|
+
depth: depth,
|
|
17
|
+
width_segments: width_segments,
|
|
18
|
+
height_segments: height_segments,
|
|
19
|
+
depth_segments: depth_segments
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
build(width, height, depth, width_segments.floor, height_segments.floor, depth_segments.floor)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def build(width, height, depth, width_segments, height_segments, depth_segments)
|
|
28
|
+
indices = []
|
|
29
|
+
vertices = []
|
|
30
|
+
normals = []
|
|
31
|
+
uvs = []
|
|
32
|
+
state = { vertex_count: 0, group_start: 0 }
|
|
33
|
+
|
|
34
|
+
build_plane(indices, vertices, normals, uvs, state, "z", "y", "x", -1, -1, depth, height, width, depth_segments, height_segments, 0)
|
|
35
|
+
build_plane(indices, vertices, normals, uvs, state, "z", "y", "x", 1, -1, depth, height, -width, depth_segments, height_segments, 1)
|
|
36
|
+
build_plane(indices, vertices, normals, uvs, state, "x", "z", "y", 1, 1, width, depth, height, width_segments, depth_segments, 2)
|
|
37
|
+
build_plane(indices, vertices, normals, uvs, state, "x", "z", "y", 1, -1, width, depth, -height, width_segments, depth_segments, 3)
|
|
38
|
+
build_plane(indices, vertices, normals, uvs, state, "x", "y", "z", 1, -1, width, height, depth, width_segments, height_segments, 4)
|
|
39
|
+
build_plane(indices, vertices, normals, uvs, state, "x", "y", "z", -1, -1, width, height, -depth, width_segments, height_segments, 5)
|
|
40
|
+
|
|
41
|
+
set_index(indices)
|
|
42
|
+
set_attribute(:position, Float32BufferAttribute.new(vertices, 3))
|
|
43
|
+
set_attribute(:normal, Float32BufferAttribute.new(normals, 3))
|
|
44
|
+
set_attribute(:uv, Float32BufferAttribute.new(uvs, 2))
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Plane construction follows three.js BoxGeometry's MIT-licensed algorithm.
|
|
48
|
+
def build_plane(indices, vertices, normals, uvs, state, u, v, w, udir, vdir, width, height, depth, grid_x, grid_y, material_index)
|
|
49
|
+
segment_width = width.to_f / grid_x
|
|
50
|
+
segment_height = height.to_f / grid_y
|
|
51
|
+
width_half = width / 2.0
|
|
52
|
+
height_half = height / 2.0
|
|
53
|
+
depth_half = depth / 2.0
|
|
54
|
+
grid_x1 = grid_x + 1
|
|
55
|
+
grid_y1 = grid_y + 1
|
|
56
|
+
vertex_counter = 0
|
|
57
|
+
group_count = 0
|
|
58
|
+
|
|
59
|
+
grid_y1.times do |iy|
|
|
60
|
+
y = iy * segment_height - height_half
|
|
61
|
+
|
|
62
|
+
grid_x1.times do |ix|
|
|
63
|
+
x = ix * segment_width - width_half
|
|
64
|
+
vector = { "x" => 0, "y" => 0, "z" => 0 }
|
|
65
|
+
vector[u] = x * udir
|
|
66
|
+
vector[v] = y * vdir
|
|
67
|
+
vector[w] = depth_half
|
|
68
|
+
vertices.push(vector["x"], vector["y"], vector["z"])
|
|
69
|
+
|
|
70
|
+
vector[u] = 0
|
|
71
|
+
vector[v] = 0
|
|
72
|
+
vector[w] = depth.positive? ? 1 : -1
|
|
73
|
+
normals.push(vector["x"], vector["y"], vector["z"])
|
|
74
|
+
|
|
75
|
+
uvs.push(ix.to_f / grid_x, 1 - (iy.to_f / grid_y))
|
|
76
|
+
vertex_counter += 1
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
grid_y.times do |iy|
|
|
81
|
+
grid_x.times do |ix|
|
|
82
|
+
a = state[:vertex_count] + ix + grid_x1 * iy
|
|
83
|
+
b = state[:vertex_count] + ix + grid_x1 * (iy + 1)
|
|
84
|
+
c = state[:vertex_count] + (ix + 1) + grid_x1 * (iy + 1)
|
|
85
|
+
d = state[:vertex_count] + (ix + 1) + grid_x1 * iy
|
|
86
|
+
|
|
87
|
+
indices.push(a, b, d, b, c, d)
|
|
88
|
+
group_count += 6
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
add_group(state[:group_start], group_count, material_index)
|
|
93
|
+
state[:group_start] += group_count
|
|
94
|
+
state[:vertex_count] += vertex_counter
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../core/buffer_attribute"
|
|
4
|
+
require_relative "../core/buffer_geometry"
|
|
5
|
+
|
|
6
|
+
module Three
|
|
7
|
+
class PlaneGeometry < BufferGeometry
|
|
8
|
+
attr_accessor :parameters
|
|
9
|
+
|
|
10
|
+
def initialize(width = 1, height = 1, width_segments: 1, height_segments: 1)
|
|
11
|
+
super()
|
|
12
|
+
@type = "PlaneGeometry"
|
|
13
|
+
@parameters = {
|
|
14
|
+
width: width,
|
|
15
|
+
height: height,
|
|
16
|
+
width_segments: width_segments,
|
|
17
|
+
height_segments: height_segments
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
build(width, height, width_segments.floor, height_segments.floor)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
# Plane construction follows three.js PlaneGeometry's MIT-licensed algorithm.
|
|
26
|
+
def build(width, height, width_segments, height_segments)
|
|
27
|
+
width_half = width / 2.0
|
|
28
|
+
height_half = height / 2.0
|
|
29
|
+
grid_x = width_segments
|
|
30
|
+
grid_y = height_segments
|
|
31
|
+
grid_x1 = grid_x + 1
|
|
32
|
+
grid_y1 = grid_y + 1
|
|
33
|
+
segment_width = width.to_f / grid_x
|
|
34
|
+
segment_height = height.to_f / grid_y
|
|
35
|
+
|
|
36
|
+
indices = []
|
|
37
|
+
vertices = []
|
|
38
|
+
normals = []
|
|
39
|
+
uvs = []
|
|
40
|
+
|
|
41
|
+
grid_y1.times do |iy|
|
|
42
|
+
y = iy * segment_height - height_half
|
|
43
|
+
|
|
44
|
+
grid_x1.times do |ix|
|
|
45
|
+
x = ix * segment_width - width_half
|
|
46
|
+
|
|
47
|
+
vertices.push(x, -y, 0)
|
|
48
|
+
normals.push(0, 0, 1)
|
|
49
|
+
uvs.push(ix.to_f / grid_x, 1 - (iy.to_f / grid_y))
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
grid_y.times do |iy|
|
|
54
|
+
grid_x.times do |ix|
|
|
55
|
+
a = ix + grid_x1 * iy
|
|
56
|
+
b = ix + grid_x1 * (iy + 1)
|
|
57
|
+
c = (ix + 1) + grid_x1 * (iy + 1)
|
|
58
|
+
d = (ix + 1) + grid_x1 * iy
|
|
59
|
+
|
|
60
|
+
indices.push(a, b, d, b, c, d)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
set_index(indices)
|
|
65
|
+
set_attribute(:position, Float32BufferAttribute.new(vertices, 3))
|
|
66
|
+
set_attribute(:normal, Float32BufferAttribute.new(normals, 3))
|
|
67
|
+
set_attribute(:uv, Float32BufferAttribute.new(uvs, 2))
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../core/buffer_attribute"
|
|
4
|
+
require_relative "../core/buffer_geometry"
|
|
5
|
+
|
|
6
|
+
module Three
|
|
7
|
+
class SphereGeometry < BufferGeometry
|
|
8
|
+
attr_accessor :parameters
|
|
9
|
+
|
|
10
|
+
def initialize(radius = 1, width_segments: 32, height_segments: 16, phi_start: 0, phi_length: Math::PI * 2, theta_start: 0, theta_length: Math::PI)
|
|
11
|
+
super()
|
|
12
|
+
@type = "SphereGeometry"
|
|
13
|
+
@parameters = {
|
|
14
|
+
radius: radius,
|
|
15
|
+
width_segments: width_segments,
|
|
16
|
+
height_segments: height_segments,
|
|
17
|
+
phi_start: phi_start,
|
|
18
|
+
phi_length: phi_length,
|
|
19
|
+
theta_start: theta_start,
|
|
20
|
+
theta_length: theta_length
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
build(
|
|
24
|
+
radius,
|
|
25
|
+
[3, width_segments.floor].max,
|
|
26
|
+
[2, height_segments.floor].max,
|
|
27
|
+
phi_start,
|
|
28
|
+
phi_length,
|
|
29
|
+
theta_start,
|
|
30
|
+
theta_length
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
# Sphere construction follows three.js SphereGeometry's MIT-licensed algorithm.
|
|
37
|
+
def build(radius, width_segments, height_segments, phi_start, phi_length, theta_start, theta_length)
|
|
38
|
+
theta_end = [theta_start + theta_length, Math::PI].min
|
|
39
|
+
index = 0
|
|
40
|
+
grid = []
|
|
41
|
+
indices = []
|
|
42
|
+
vertices = []
|
|
43
|
+
normals = []
|
|
44
|
+
uvs = []
|
|
45
|
+
|
|
46
|
+
(height_segments + 1).times do |iy|
|
|
47
|
+
vertices_row = []
|
|
48
|
+
v = iy.to_f / height_segments
|
|
49
|
+
u_offset = uv_offset(iy, height_segments, width_segments, theta_start, theta_end)
|
|
50
|
+
|
|
51
|
+
(width_segments + 1).times do |ix|
|
|
52
|
+
u = ix.to_f / width_segments
|
|
53
|
+
theta = theta_start + v * theta_length
|
|
54
|
+
phi = phi_start + u * phi_length
|
|
55
|
+
|
|
56
|
+
x = -radius * Math.cos(phi) * Math.sin(theta)
|
|
57
|
+
y = radius * Math.cos(theta)
|
|
58
|
+
z = radius * Math.sin(phi) * Math.sin(theta)
|
|
59
|
+
|
|
60
|
+
vertices.push(x, y, z)
|
|
61
|
+
push_normal(normals, x, y, z)
|
|
62
|
+
uvs.push(u + u_offset, 1 - v)
|
|
63
|
+
vertices_row << index
|
|
64
|
+
index += 1
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
grid << vertices_row
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
height_segments.times do |iy|
|
|
71
|
+
width_segments.times do |ix|
|
|
72
|
+
a = grid[iy][ix + 1]
|
|
73
|
+
b = grid[iy][ix]
|
|
74
|
+
c = grid[iy + 1][ix]
|
|
75
|
+
d = grid[iy + 1][ix + 1]
|
|
76
|
+
|
|
77
|
+
indices.push(a, b, d) if iy != 0 || theta_start.positive?
|
|
78
|
+
indices.push(b, c, d) if iy != height_segments - 1 || theta_end < Math::PI
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
set_index(indices)
|
|
83
|
+
set_attribute(:position, Float32BufferAttribute.new(vertices, 3))
|
|
84
|
+
set_attribute(:normal, Float32BufferAttribute.new(normals, 3))
|
|
85
|
+
set_attribute(:uv, Float32BufferAttribute.new(uvs, 2))
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def uv_offset(iy, height_segments, width_segments, theta_start, theta_end)
|
|
89
|
+
if iy.zero? && theta_start.zero?
|
|
90
|
+
0.5 / width_segments
|
|
91
|
+
elsif iy == height_segments && theta_end == Math::PI
|
|
92
|
+
-0.5 / width_segments
|
|
93
|
+
else
|
|
94
|
+
0
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def push_normal(normals, x, y, z)
|
|
99
|
+
length = Math.sqrt((x * x) + (y * y) + (z * z))
|
|
100
|
+
if length.zero?
|
|
101
|
+
normals.push(0, 0, 0)
|
|
102
|
+
else
|
|
103
|
+
normals.push(x / length, y / length, z / length)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "light"
|
|
4
|
+
|
|
5
|
+
module Three
|
|
6
|
+
class DirectionalLight < Light
|
|
7
|
+
attr_reader :shadow_camera
|
|
8
|
+
|
|
9
|
+
def initialize(color = 0xffffff, intensity = 1)
|
|
10
|
+
super
|
|
11
|
+
@type = "DirectionalLight"
|
|
12
|
+
@position.copy(Object3D::DEFAULT_UP)
|
|
13
|
+
@shadow_camera = {
|
|
14
|
+
left: -5,
|
|
15
|
+
right: 5,
|
|
16
|
+
top: 5,
|
|
17
|
+
bottom: -5,
|
|
18
|
+
near: 0.5,
|
|
19
|
+
far: 500
|
|
20
|
+
}
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def set_shadow_camera(left: nil, right: nil, top: nil, bottom: nil, near: nil, far: nil)
|
|
24
|
+
@shadow_camera[:left] = left unless left.nil?
|
|
25
|
+
@shadow_camera[:right] = right unless right.nil?
|
|
26
|
+
@shadow_camera[:top] = top unless top.nil?
|
|
27
|
+
@shadow_camera[:bottom] = bottom unless bottom.nil?
|
|
28
|
+
@shadow_camera[:near] = near unless near.nil?
|
|
29
|
+
@shadow_camera[:far] = far unless far.nil?
|
|
30
|
+
mark_dirty!(:shadow)
|
|
31
|
+
self
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def to_h
|
|
35
|
+
super.merge(shadow_camera: @shadow_camera.dup)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "light"
|
|
4
|
+
|
|
5
|
+
module Three
|
|
6
|
+
class HemisphereLight < Light
|
|
7
|
+
attr_reader :ground_color
|
|
8
|
+
|
|
9
|
+
def initialize(sky_color = 0xffffff, ground_color = 0xffffff, intensity = 1)
|
|
10
|
+
super(sky_color, intensity)
|
|
11
|
+
@type = "HemisphereLight"
|
|
12
|
+
@ground_color = Color.new(ground_color)
|
|
13
|
+
bind_ground_color_changes
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def ground_color=(value)
|
|
17
|
+
@ground_color = value.is_a?(Color) ? value : Color.new(value)
|
|
18
|
+
bind_ground_color_changes
|
|
19
|
+
mark_dirty!(:light)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def to_h
|
|
23
|
+
super.merge(
|
|
24
|
+
ground_color: @ground_color.hex
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def bind_ground_color_changes
|
|
31
|
+
@ground_color.on_change { mark_dirty!(:light) }
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|