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,35 @@
|
|
|
1
|
+
# Browser Textures Example
|
|
2
|
+
|
|
3
|
+
This example renders Ruby-authored textured meshes through ruby.wasm and the JavaScript three.js renderer. It focuses on `TextureLoader`, `RGBELoader`, repeat/wrap/filter settings, `MeshPhysicalMaterial`, `MeshMatcapMaterial`, `MeshToonMaterial`, standard plus physical material texture maps, matcap and toon gradient texture assignment, and an HDR environment texture.
|
|
4
|
+
|
|
5
|
+
Install browser dependencies and serve the repository root over HTTP:
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
pnpm install
|
|
9
|
+
ruby -run -e httpd . -p 8000
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Then open:
|
|
13
|
+
|
|
14
|
+
```text
|
|
15
|
+
http://localhost:8000/examples/browser/textures/
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The repository root must be served, not only this directory, because `boot.mjs` loads browser packages from `node_modules/` and `main.rb` loads the library source from `lib/`.
|
|
19
|
+
|
|
20
|
+
## Browser Smoke Test
|
|
21
|
+
|
|
22
|
+
Install the optional Node dependency and browser binary:
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
pnpm install
|
|
26
|
+
pnpm exec playwright install chromium
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Run the browser smoke test:
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
pnpm test:browser:textures
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The smoke test serves the repository root, waits for the Ruby scene to reach `Running`, samples the WebGL canvas for nonblank pixels, verifies the textured meshes, physical, matcap, and toon material parameters, RGBE environment mapping, and checks that repeat/wrap/filter settings reach the three.js texture.
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>three-rb textures</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
color-scheme: dark;
|
|
10
|
+
font-family:
|
|
11
|
+
Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
|
|
12
|
+
"Segoe UI", sans-serif;
|
|
13
|
+
background: #11161a;
|
|
14
|
+
color: #eef3f7;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
* {
|
|
18
|
+
box-sizing: border-box;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
html,
|
|
22
|
+
body {
|
|
23
|
+
width: 100%;
|
|
24
|
+
height: 100%;
|
|
25
|
+
margin: 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
body {
|
|
29
|
+
display: grid;
|
|
30
|
+
place-items: stretch;
|
|
31
|
+
overflow: hidden;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.viewport {
|
|
35
|
+
position: relative;
|
|
36
|
+
width: 100%;
|
|
37
|
+
height: 100%;
|
|
38
|
+
min-width: 320px;
|
|
39
|
+
min-height: 320px;
|
|
40
|
+
background:
|
|
41
|
+
linear-gradient(rgba(255, 255, 255, 0.035) 1px, transparent 1px),
|
|
42
|
+
linear-gradient(90deg, rgba(255, 255, 255, 0.035) 1px, transparent 1px),
|
|
43
|
+
linear-gradient(135deg, #11161a 0%, #18211d 54%, #121318 100%);
|
|
44
|
+
background-size: 34px 34px, 34px 34px, auto;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
canvas {
|
|
48
|
+
display: block;
|
|
49
|
+
width: 100%;
|
|
50
|
+
height: 100%;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.hud {
|
|
54
|
+
position: absolute;
|
|
55
|
+
top: 16px;
|
|
56
|
+
left: 16px;
|
|
57
|
+
display: flex;
|
|
58
|
+
align-items: center;
|
|
59
|
+
gap: 8px;
|
|
60
|
+
max-width: calc(100% - 32px);
|
|
61
|
+
padding: 8px 10px;
|
|
62
|
+
border: 1px solid rgba(255, 255, 255, 0.14);
|
|
63
|
+
border-radius: 6px;
|
|
64
|
+
background: rgba(9, 13, 17, 0.72);
|
|
65
|
+
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.24);
|
|
66
|
+
color: #dce7ef;
|
|
67
|
+
font-size: 13px;
|
|
68
|
+
line-height: 1.3;
|
|
69
|
+
backdrop-filter: blur(10px);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.status-dot {
|
|
73
|
+
width: 8px;
|
|
74
|
+
height: 8px;
|
|
75
|
+
flex: 0 0 auto;
|
|
76
|
+
border-radius: 999px;
|
|
77
|
+
background: #f2b84b;
|
|
78
|
+
box-shadow: 0 0 14px rgba(242, 184, 75, 0.72);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.status-dot[data-state="running"] {
|
|
82
|
+
background: #60d394;
|
|
83
|
+
box-shadow: 0 0 14px rgba(96, 211, 148, 0.72);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.status-dot[data-state="error"] {
|
|
87
|
+
background: #f15b5b;
|
|
88
|
+
box-shadow: 0 0 14px rgba(241, 91, 91, 0.72);
|
|
89
|
+
}
|
|
90
|
+
</style>
|
|
91
|
+
<script type="importmap">
|
|
92
|
+
{
|
|
93
|
+
"imports": {
|
|
94
|
+
"@bjorn3/browser_wasi_shim": "/node_modules/@bjorn3/browser_wasi_shim/dist/index.js",
|
|
95
|
+
"@ruby/wasm-wasi/browser": "/node_modules/@ruby/wasm-wasi/dist/esm/browser.js",
|
|
96
|
+
"three": "/node_modules/three/build/three.module.js",
|
|
97
|
+
"three/addons/": "/node_modules/three/examples/jsm/"
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
</script>
|
|
101
|
+
<script type="module">
|
|
102
|
+
const status = document.querySelector("#status");
|
|
103
|
+
const statusDot = document.querySelector("#status-dot");
|
|
104
|
+
|
|
105
|
+
globalThis.__threeRbSetStatus = (message, state) => {
|
|
106
|
+
if (status) status.textContent = message;
|
|
107
|
+
if (statusDot) statusDot.dataset.state = state;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
globalThis.__threeRbBootFailed = (message) => {
|
|
111
|
+
globalThis.__threeRbSetStatus(message, "error");
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
globalThis.addEventListener("error", (event) => {
|
|
115
|
+
globalThis.__threeRbBootFailed(event.message || "Browser error");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
globalThis.addEventListener("unhandledrejection", (event) => {
|
|
119
|
+
const reason = event.reason;
|
|
120
|
+
globalThis.__threeRbBootFailed(reason && reason.message ? reason.message : "Ruby boot failed");
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
globalThis.setTimeout(() => {
|
|
124
|
+
if (status && status.textContent === "Loading ruby.wasm") {
|
|
125
|
+
status.textContent = "Still loading ruby.wasm";
|
|
126
|
+
}
|
|
127
|
+
}, 15000);
|
|
128
|
+
|
|
129
|
+
globalThis.__threeRbSetStatus("Loading ruby.wasm", "loading");
|
|
130
|
+
</script>
|
|
131
|
+
<script type="module" src="./boot.mjs"></script>
|
|
132
|
+
</head>
|
|
133
|
+
<body>
|
|
134
|
+
<main class="viewport" id="viewport">
|
|
135
|
+
<canvas id="scene" data-testid="scene-canvas"></canvas>
|
|
136
|
+
<div class="hud" aria-live="polite">
|
|
137
|
+
<span class="status-dot" id="status-dot"></span>
|
|
138
|
+
<span id="status" data-testid="status">Loading ruby.wasm</span>
|
|
139
|
+
</div>
|
|
140
|
+
</main>
|
|
141
|
+
</body>
|
|
142
|
+
</html>
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "js"
|
|
4
|
+
|
|
5
|
+
begin
|
|
6
|
+
JS.global[:__threeReady].await
|
|
7
|
+
|
|
8
|
+
require_relative "../../../lib/three"
|
|
9
|
+
|
|
10
|
+
document = JS.global[:document]
|
|
11
|
+
window = JS.global[:window]
|
|
12
|
+
viewport = document.call(:querySelector, "#viewport")
|
|
13
|
+
status = document.call(:querySelector, "#status")
|
|
14
|
+
status_dot = document.call(:querySelector, "#status-dot")
|
|
15
|
+
status[:textContent] = "Starting Ruby scene"
|
|
16
|
+
|
|
17
|
+
scene = Three::Scene.new
|
|
18
|
+
camera = Three::OrthographicCamera.new(-2.5, 2.5, 1.6, -1.6, near: 0.1, far: 100)
|
|
19
|
+
camera.position.z = 5
|
|
20
|
+
|
|
21
|
+
environment_texture = Three::Loaders::RGBELoader.new.load("/examples/browser/assets/studio.hdr")
|
|
22
|
+
scene.environment = environment_texture
|
|
23
|
+
|
|
24
|
+
scene.add(Three::AmbientLight.new(0xffffff, 0.45))
|
|
25
|
+
|
|
26
|
+
key_light = Three::DirectionalLight.new(0xffffff, 1.1)
|
|
27
|
+
key_light.position.set(2.5, 3.0, 4.0)
|
|
28
|
+
scene.add(key_light)
|
|
29
|
+
|
|
30
|
+
texture = Three::Loaders::TextureLoader.new.load("/examples/browser/assets/checker.svg")
|
|
31
|
+
texture.wrap_s = Three::RepeatWrapping
|
|
32
|
+
texture.wrap_t = Three::RepeatWrapping
|
|
33
|
+
texture.mag_filter = Three::NearestFilter
|
|
34
|
+
texture.min_filter = Three::NearestMipmapNearestFilter
|
|
35
|
+
texture.offset.set(0.125, 0.25)
|
|
36
|
+
texture.repeat.set(4, 3)
|
|
37
|
+
texture.center.set(0.5, 0.5)
|
|
38
|
+
texture.rotation = 0.35
|
|
39
|
+
|
|
40
|
+
material = Three::MeshPhysicalMaterial.new(
|
|
41
|
+
color: 0xffffff,
|
|
42
|
+
roughness: 0.42,
|
|
43
|
+
metalness: 0.08,
|
|
44
|
+
anisotropy: 0.25,
|
|
45
|
+
anisotropy_rotation: 0.15,
|
|
46
|
+
clearcoat: 0.65,
|
|
47
|
+
clearcoat_roughness: 0.18,
|
|
48
|
+
ior: 1.45,
|
|
49
|
+
specular_intensity: 0.75,
|
|
50
|
+
specular_color: 0xe8f1ff,
|
|
51
|
+
map: texture,
|
|
52
|
+
roughness_map: texture,
|
|
53
|
+
metalness_map: texture,
|
|
54
|
+
anisotropy_map: texture,
|
|
55
|
+
clearcoat_map: texture
|
|
56
|
+
)
|
|
57
|
+
mesh = Three::Mesh.new(Three::BoxGeometry.new(1.8, 1.15, 0.32), material)
|
|
58
|
+
mesh.position.x = -0.75
|
|
59
|
+
mesh.rotation.x = -0.25
|
|
60
|
+
mesh.rotation.y = 0.38
|
|
61
|
+
scene.add(mesh)
|
|
62
|
+
|
|
63
|
+
matcap_material = Three::MeshMatcapMaterial.new(color: 0xffffff, matcap: texture, map: texture, flat_shading: true)
|
|
64
|
+
matcap_mesh = Three::Mesh.new(
|
|
65
|
+
Three::SphereGeometry.new(0.52, width_segments: 32, height_segments: 16),
|
|
66
|
+
matcap_material
|
|
67
|
+
)
|
|
68
|
+
matcap_mesh.position.x = 1.35
|
|
69
|
+
matcap_mesh.rotation.y = -0.28
|
|
70
|
+
scene.add(matcap_mesh)
|
|
71
|
+
|
|
72
|
+
toon_material = Three::MeshToonMaterial.new(
|
|
73
|
+
color: 0xf6c85f,
|
|
74
|
+
emissive: 0x101820,
|
|
75
|
+
map: texture,
|
|
76
|
+
gradient_map: texture,
|
|
77
|
+
flat_shading: true
|
|
78
|
+
)
|
|
79
|
+
toon_mesh = Three::Mesh.new(
|
|
80
|
+
Three::SphereGeometry.new(0.42, width_segments: 24, height_segments: 12),
|
|
81
|
+
toon_material
|
|
82
|
+
)
|
|
83
|
+
toon_mesh.position.set(0.32, -0.9, 0.18)
|
|
84
|
+
scene.add(toon_mesh)
|
|
85
|
+
|
|
86
|
+
renderer = Three::Renderers::ThreeJSRenderer.new(
|
|
87
|
+
canvas: "#scene",
|
|
88
|
+
antialias: true,
|
|
89
|
+
alpha: false,
|
|
90
|
+
preserveDrawingBuffer: true
|
|
91
|
+
)
|
|
92
|
+
renderer.set_clear_color(0x11161a, 1)
|
|
93
|
+
|
|
94
|
+
resize = proc do
|
|
95
|
+
width = [viewport[:clientWidth].to_i, 1].max
|
|
96
|
+
height = [viewport[:clientHeight].to_i, 1].max
|
|
97
|
+
view_height = 3.4
|
|
98
|
+
view_width = view_height * width.to_f / height
|
|
99
|
+
|
|
100
|
+
camera.left = -view_width / 2
|
|
101
|
+
camera.right = view_width / 2
|
|
102
|
+
camera.top = view_height / 2
|
|
103
|
+
camera.bottom = -view_height / 2
|
|
104
|
+
camera.update_projection_matrix
|
|
105
|
+
renderer.set_size(width, height)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
resize.call
|
|
109
|
+
window.call(:addEventListener, "resize", resize)
|
|
110
|
+
renderer.render(scene, camera)
|
|
111
|
+
|
|
112
|
+
JS.global[:__threeRbRenderer] = renderer.handle
|
|
113
|
+
JS.global[:__threeRbScene] = renderer.backend.materialize(scene)
|
|
114
|
+
JS.global[:__threeRbCamera] = renderer.backend.materialize(camera)
|
|
115
|
+
JS.global[:__threeRbTexturedMesh] = renderer.backend.materialize(mesh)
|
|
116
|
+
JS.global[:__threeRbTextureMaterial] = renderer.backend.materialize(material)
|
|
117
|
+
JS.global[:__threeRbMatcapMesh] = renderer.backend.materialize(matcap_mesh)
|
|
118
|
+
JS.global[:__threeRbMatcapMaterial] = renderer.backend.materialize(matcap_material)
|
|
119
|
+
JS.global[:__threeRbToonMesh] = renderer.backend.materialize(toon_mesh)
|
|
120
|
+
JS.global[:__threeRbToonMaterial] = renderer.backend.materialize(toon_material)
|
|
121
|
+
JS.global[:__threeRbTextureExampleTexture] = renderer.backend.materialize(texture)
|
|
122
|
+
JS.global[:__threeRbTextureExampleEnvironment] = renderer.backend.materialize(environment_texture)
|
|
123
|
+
JS.global[:__threeRbTextureExampleFrame] = 0
|
|
124
|
+
|
|
125
|
+
frame = 0
|
|
126
|
+
renderer.animation_loop do
|
|
127
|
+
frame += 1
|
|
128
|
+
mesh.rotation.x -= 0.006
|
|
129
|
+
mesh.rotation.y += 0.011
|
|
130
|
+
matcap_mesh.rotation.x += 0.005
|
|
131
|
+
matcap_mesh.rotation.y -= 0.009
|
|
132
|
+
toon_mesh.rotation.y += 0.012
|
|
133
|
+
JS.global[:__threeRbTextureExampleFrame] = frame
|
|
134
|
+
renderer.render(scene, camera)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
status[:textContent] = "Running"
|
|
138
|
+
status_dot[:dataset][:state] = "running"
|
|
139
|
+
rescue StandardError => error
|
|
140
|
+
JS.global.call(:__threeRbBootFailed, error.message) if JS.global[:__threeRbBootFailed]
|
|
141
|
+
raise
|
|
142
|
+
end
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import {
|
|
2
|
+
assertNoDiagnostics,
|
|
3
|
+
assertNonBlankCanvas,
|
|
4
|
+
createDiagnostics,
|
|
5
|
+
createSmokePage,
|
|
6
|
+
loadPlaywright,
|
|
7
|
+
sampleCanvas,
|
|
8
|
+
startServer,
|
|
9
|
+
waitForRunning
|
|
10
|
+
} from "../shared/smoke_test_helpers.mjs";
|
|
11
|
+
|
|
12
|
+
async function main() {
|
|
13
|
+
const { chromium } = await loadPlaywright();
|
|
14
|
+
const server = await startServer();
|
|
15
|
+
const browser = await chromium.launch({ headless: process.env.HEADLESS !== "0" });
|
|
16
|
+
const diagnostics = createDiagnostics();
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const page = await createSmokePage(browser, diagnostics);
|
|
20
|
+
|
|
21
|
+
await page.goto(`${server.url}/examples/browser/textures/`, { waitUntil: "load" });
|
|
22
|
+
await waitForRunning(page, diagnostics);
|
|
23
|
+
await page.waitForFunction(
|
|
24
|
+
() => globalThis.__threeRbTextureMaterial?.map?.source?.data?.complete === true &&
|
|
25
|
+
globalThis.__threeRbTextureExampleEnvironment?.isDataTexture === true,
|
|
26
|
+
null,
|
|
27
|
+
{ timeout: 10_000 }
|
|
28
|
+
);
|
|
29
|
+
await page.waitForTimeout(1_000);
|
|
30
|
+
|
|
31
|
+
assertNonBlankCanvas(await sampleCanvas(page));
|
|
32
|
+
|
|
33
|
+
const scene = await page.evaluate(() => ({
|
|
34
|
+
frame: globalThis.__threeRbTextureExampleFrame,
|
|
35
|
+
renderInfo: globalThis.__threeRbRenderer?.info?.render,
|
|
36
|
+
cameraType: globalThis.__threeRbCamera?.type,
|
|
37
|
+
sceneChildren: globalThis.__threeRbScene?.children?.length,
|
|
38
|
+
meshType: globalThis.__threeRbTexturedMesh?.type,
|
|
39
|
+
geometryType: globalThis.__threeRbTexturedMesh?.geometry?.type,
|
|
40
|
+
materialType: globalThis.__threeRbTexturedMesh?.material?.type,
|
|
41
|
+
materialMapType: globalThis.__threeRbTexturedMesh?.material?.map?.isTexture,
|
|
42
|
+
matcapMeshType: globalThis.__threeRbMatcapMesh?.type,
|
|
43
|
+
matcapGeometryType: globalThis.__threeRbMatcapMesh?.geometry?.type,
|
|
44
|
+
matcapMaterialType: globalThis.__threeRbMatcapMesh?.material?.type,
|
|
45
|
+
matcapMaterialColor: globalThis.__threeRbMatcapMaterial?.color?.getHex?.(),
|
|
46
|
+
matcapMaterialFlatShading: globalThis.__threeRbMatcapMaterial?.flatShading,
|
|
47
|
+
matcapTextureType: globalThis.__threeRbMatcapMaterial?.matcap?.isTexture,
|
|
48
|
+
matcapMapType: globalThis.__threeRbMatcapMaterial?.map?.isTexture,
|
|
49
|
+
matcapTextureSame: globalThis.__threeRbMatcapMaterial?.matcap === globalThis.__threeRbTextureExampleTexture,
|
|
50
|
+
matcapMapSame: globalThis.__threeRbMatcapMaterial?.map === globalThis.__threeRbTextureExampleTexture,
|
|
51
|
+
toonMeshType: globalThis.__threeRbToonMesh?.type,
|
|
52
|
+
toonGeometryType: globalThis.__threeRbToonMesh?.geometry?.type,
|
|
53
|
+
toonMaterialType: globalThis.__threeRbToonMesh?.material?.type,
|
|
54
|
+
toonMaterialColor: globalThis.__threeRbToonMaterial?.color?.getHex?.(),
|
|
55
|
+
toonMaterialEmissive: globalThis.__threeRbToonMaterial?.emissive?.getHex?.(),
|
|
56
|
+
toonMaterialFlatShading: globalThis.__threeRbToonMaterial?.flatShading,
|
|
57
|
+
toonGradientMapType: globalThis.__threeRbToonMaterial?.gradientMap?.isTexture,
|
|
58
|
+
toonMapType: globalThis.__threeRbToonMaterial?.map?.isTexture,
|
|
59
|
+
toonGradientMapSame: globalThis.__threeRbToonMaterial?.gradientMap === globalThis.__threeRbTextureExampleTexture,
|
|
60
|
+
toonMapSame: globalThis.__threeRbToonMaterial?.map === globalThis.__threeRbTextureExampleTexture,
|
|
61
|
+
materialRoughnessMapType: globalThis.__threeRbTextureMaterial?.roughnessMap?.isTexture,
|
|
62
|
+
materialMetalnessMapType: globalThis.__threeRbTextureMaterial?.metalnessMap?.isTexture,
|
|
63
|
+
materialAnisotropyMapType: globalThis.__threeRbTextureMaterial?.anisotropyMap?.isTexture,
|
|
64
|
+
materialClearcoatMapType: globalThis.__threeRbTextureMaterial?.clearcoatMap?.isTexture,
|
|
65
|
+
materialRoughness: globalThis.__threeRbTextureMaterial?.roughness,
|
|
66
|
+
materialMetalness: globalThis.__threeRbTextureMaterial?.metalness,
|
|
67
|
+
materialAnisotropy: globalThis.__threeRbTextureMaterial?.anisotropy,
|
|
68
|
+
materialAnisotropyRotation: globalThis.__threeRbTextureMaterial?.anisotropyRotation,
|
|
69
|
+
materialClearcoat: globalThis.__threeRbTextureMaterial?.clearcoat,
|
|
70
|
+
materialClearcoatRoughness: globalThis.__threeRbTextureMaterial?.clearcoatRoughness,
|
|
71
|
+
materialIor: globalThis.__threeRbTextureMaterial?.ior,
|
|
72
|
+
materialSpecularIntensity: globalThis.__threeRbTextureMaterial?.specularIntensity,
|
|
73
|
+
materialSpecularColor: globalThis.__threeRbTextureMaterial?.specularColor?.getHex?.(),
|
|
74
|
+
textureType: globalThis.__threeRbTextureExampleTexture?.isTexture,
|
|
75
|
+
textureWidth: globalThis.__threeRbTextureExampleTexture?.source?.data?.naturalWidth,
|
|
76
|
+
textureWrapS: globalThis.__threeRbTextureExampleTexture?.wrapS,
|
|
77
|
+
textureWrapT: globalThis.__threeRbTextureExampleTexture?.wrapT,
|
|
78
|
+
textureMagFilter: globalThis.__threeRbTextureExampleTexture?.magFilter,
|
|
79
|
+
textureMinFilter: globalThis.__threeRbTextureExampleTexture?.minFilter,
|
|
80
|
+
textureOffset: globalThis.__threeRbTextureExampleTexture?.offset?.toArray?.(),
|
|
81
|
+
textureRepeat: globalThis.__threeRbTextureExampleTexture?.repeat?.toArray?.(),
|
|
82
|
+
textureCenter: globalThis.__threeRbTextureExampleTexture?.center?.toArray?.(),
|
|
83
|
+
textureRotation: globalThis.__threeRbTextureExampleTexture?.rotation,
|
|
84
|
+
textureMatrixAutoUpdate: globalThis.__threeRbTextureExampleTexture?.matrixAutoUpdate,
|
|
85
|
+
environmentType: globalThis.__threeRbTextureExampleEnvironment?.isDataTexture,
|
|
86
|
+
environmentMapping: globalThis.__threeRbTextureExampleEnvironment?.mapping,
|
|
87
|
+
environmentColorSpace: globalThis.__threeRbTextureExampleEnvironment?.colorSpace,
|
|
88
|
+
environmentMagFilter: globalThis.__threeRbTextureExampleEnvironment?.magFilter,
|
|
89
|
+
environmentMinFilter: globalThis.__threeRbTextureExampleEnvironment?.minFilter,
|
|
90
|
+
sceneEnvironmentSame: globalThis.__threeRbScene?.environment === globalThis.__threeRbTextureExampleEnvironment
|
|
91
|
+
}));
|
|
92
|
+
|
|
93
|
+
if (scene.cameraType !== "OrthographicCamera") {
|
|
94
|
+
throw new Error(`expected an OrthographicCamera texture view: ${JSON.stringify(scene)}`);
|
|
95
|
+
}
|
|
96
|
+
if (scene.meshType !== "Mesh" || scene.geometryType !== "BoxGeometry") {
|
|
97
|
+
throw new Error(`expected a textured box mesh: ${JSON.stringify(scene)}`);
|
|
98
|
+
}
|
|
99
|
+
if (scene.materialType !== "MeshPhysicalMaterial" || scene.materialMapType !== true) {
|
|
100
|
+
throw new Error(`expected MeshPhysicalMaterial with a texture map: ${JSON.stringify(scene)}`);
|
|
101
|
+
}
|
|
102
|
+
if (scene.matcapMeshType !== "Mesh" || scene.matcapGeometryType !== "SphereGeometry") {
|
|
103
|
+
throw new Error(`expected a matcap sphere mesh: ${JSON.stringify(scene)}`);
|
|
104
|
+
}
|
|
105
|
+
if (
|
|
106
|
+
scene.matcapMaterialType !== "MeshMatcapMaterial" ||
|
|
107
|
+
scene.matcapMaterialColor !== 0xffffff ||
|
|
108
|
+
scene.matcapMaterialFlatShading !== true ||
|
|
109
|
+
scene.matcapTextureType !== true ||
|
|
110
|
+
scene.matcapMapType !== true ||
|
|
111
|
+
scene.matcapTextureSame !== true ||
|
|
112
|
+
scene.matcapMapSame !== true
|
|
113
|
+
) {
|
|
114
|
+
throw new Error(`expected MeshMatcapMaterial with shared matcap/map texture: ${JSON.stringify(scene)}`);
|
|
115
|
+
}
|
|
116
|
+
if (scene.toonMeshType !== "Mesh" || scene.toonGeometryType !== "SphereGeometry") {
|
|
117
|
+
throw new Error(`expected a toon sphere mesh: ${JSON.stringify(scene)}`);
|
|
118
|
+
}
|
|
119
|
+
if (
|
|
120
|
+
scene.toonMaterialType !== "MeshToonMaterial" ||
|
|
121
|
+
scene.toonMaterialColor !== 0xf6c85f ||
|
|
122
|
+
scene.toonMaterialEmissive !== 0x101820 ||
|
|
123
|
+
scene.toonMaterialFlatShading !== true ||
|
|
124
|
+
scene.toonGradientMapType !== true ||
|
|
125
|
+
scene.toonMapType !== true ||
|
|
126
|
+
scene.toonGradientMapSame !== true ||
|
|
127
|
+
scene.toonMapSame !== true
|
|
128
|
+
) {
|
|
129
|
+
throw new Error(`expected MeshToonMaterial with shared map/gradient texture: ${JSON.stringify(scene)}`);
|
|
130
|
+
}
|
|
131
|
+
if (scene.materialRoughnessMapType !== true || scene.materialMetalnessMapType !== true || scene.materialAnisotropyMapType !== true || scene.materialClearcoatMapType !== true) {
|
|
132
|
+
throw new Error(`expected MeshPhysicalMaterial with PBR and physical texture maps: ${JSON.stringify(scene)}`);
|
|
133
|
+
}
|
|
134
|
+
if (scene.materialRoughness !== 0.42 || scene.materialMetalness !== 0.08) {
|
|
135
|
+
throw new Error(`expected configured PBR material values: ${JSON.stringify(scene)}`);
|
|
136
|
+
}
|
|
137
|
+
if (scene.materialAnisotropy !== 0.25 || scene.materialAnisotropyRotation !== 0.15) {
|
|
138
|
+
throw new Error(`expected configured anisotropy material values: ${JSON.stringify(scene)}`);
|
|
139
|
+
}
|
|
140
|
+
if (scene.materialClearcoat !== 0.65 || scene.materialClearcoatRoughness !== 0.18 || scene.materialIor !== 1.45) {
|
|
141
|
+
throw new Error(`expected configured physical material values: ${JSON.stringify(scene)}`);
|
|
142
|
+
}
|
|
143
|
+
if (scene.materialSpecularIntensity !== 0.75 || scene.materialSpecularColor !== 0xe8f1ff) {
|
|
144
|
+
throw new Error(`expected configured physical specular values: ${JSON.stringify(scene)}`);
|
|
145
|
+
}
|
|
146
|
+
if (scene.textureType !== true || scene.textureWidth <= 0) {
|
|
147
|
+
throw new Error(`expected a loaded texture: ${JSON.stringify(scene)}`);
|
|
148
|
+
}
|
|
149
|
+
if (scene.textureWrapS !== 1000 || scene.textureWrapT !== 1000 || scene.textureMagFilter !== 1003 || scene.textureMinFilter !== 1004) {
|
|
150
|
+
throw new Error(`expected configured texture wrapping and filters: ${JSON.stringify(scene)}`);
|
|
151
|
+
}
|
|
152
|
+
if (!Array.isArray(scene.textureRepeat) || scene.textureRepeat[0] !== 4 || scene.textureRepeat[1] !== 3) {
|
|
153
|
+
throw new Error(`expected configured texture repeat: ${JSON.stringify(scene)}`);
|
|
154
|
+
}
|
|
155
|
+
if (!Array.isArray(scene.textureOffset) || scene.textureOffset[0] !== 0.125 || scene.textureOffset[1] !== 0.25) {
|
|
156
|
+
throw new Error(`expected configured texture offset: ${JSON.stringify(scene)}`);
|
|
157
|
+
}
|
|
158
|
+
if (!Array.isArray(scene.textureCenter) || scene.textureCenter[0] !== 0.5 || scene.textureCenter[1] !== 0.5) {
|
|
159
|
+
throw new Error(`expected configured texture center: ${JSON.stringify(scene)}`);
|
|
160
|
+
}
|
|
161
|
+
if (Math.abs(scene.textureRotation - 0.35) > 1e-12 || scene.textureMatrixAutoUpdate !== true) {
|
|
162
|
+
throw new Error(`expected configured texture transform state: ${JSON.stringify(scene)}`);
|
|
163
|
+
}
|
|
164
|
+
if (scene.environmentType !== true || scene.environmentMapping !== 303 || scene.environmentColorSpace !== "srgb-linear") {
|
|
165
|
+
throw new Error(`expected an RGBE environment texture with equirectangular mapping: ${JSON.stringify(scene)}`);
|
|
166
|
+
}
|
|
167
|
+
if (scene.environmentMagFilter !== 1006 || scene.environmentMinFilter !== 1006 || scene.sceneEnvironmentSame !== true) {
|
|
168
|
+
throw new Error(`expected configured RGBE environment texture filters and scene binding: ${JSON.stringify(scene)}`);
|
|
169
|
+
}
|
|
170
|
+
if (!scene.renderInfo || scene.renderInfo.triangles < 12) {
|
|
171
|
+
throw new Error(`renderer did not draw the textured cube triangles: ${JSON.stringify(scene)}`);
|
|
172
|
+
}
|
|
173
|
+
if (!scene.frame) {
|
|
174
|
+
throw new Error(`texture example animation did not advance: ${JSON.stringify(scene)}`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
assertNoDiagnostics(diagnostics);
|
|
178
|
+
|
|
179
|
+
console.log(`textures smoke test passed at ${server.url}/examples/browser/textures/`);
|
|
180
|
+
} finally {
|
|
181
|
+
await browser.close();
|
|
182
|
+
await new Promise((resolve) => server.instance.close(resolve));
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
main().catch((error) => {
|
|
187
|
+
console.error(error);
|
|
188
|
+
process.exitCode = 1;
|
|
189
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Three
|
|
4
|
+
class AnimationAction
|
|
5
|
+
PROPERTY_NAMES = {
|
|
6
|
+
enabled: "enabled",
|
|
7
|
+
paused: "paused",
|
|
8
|
+
time: "time",
|
|
9
|
+
time_scale: "timeScale",
|
|
10
|
+
weight: "weight",
|
|
11
|
+
loop: "loop",
|
|
12
|
+
repetitions: "repetitions",
|
|
13
|
+
clamp_when_finished: "clampWhenFinished"
|
|
14
|
+
}.freeze
|
|
15
|
+
|
|
16
|
+
attr_reader :handle, :backend
|
|
17
|
+
attr_reader(*PROPERTY_NAMES.keys)
|
|
18
|
+
|
|
19
|
+
def initialize(handle, backend:)
|
|
20
|
+
@handle = handle
|
|
21
|
+
@backend = backend
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def play
|
|
25
|
+
@backend.play_animation_action(@handle)
|
|
26
|
+
self
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def stop
|
|
30
|
+
@backend.stop_animation_action(@handle)
|
|
31
|
+
self
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def reset
|
|
35
|
+
@backend.reset_animation_action(@handle)
|
|
36
|
+
self
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def fade_in(duration)
|
|
40
|
+
@backend.fade_in_animation_action(@handle, duration)
|
|
41
|
+
self
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def fade_out(duration)
|
|
45
|
+
@backend.fade_out_animation_action(@handle, duration)
|
|
46
|
+
self
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
PROPERTY_NAMES.each do |ruby_name, js_name|
|
|
50
|
+
define_method("#{ruby_name}=") do |value|
|
|
51
|
+
instance_variable_set("@#{ruby_name}", value)
|
|
52
|
+
@backend.set_animation_action_property(@handle, js_name, value)
|
|
53
|
+
value
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../backends/threejs"
|
|
4
|
+
|
|
5
|
+
module Three
|
|
6
|
+
class AnimationClip
|
|
7
|
+
attr_reader :handle, :adapter
|
|
8
|
+
|
|
9
|
+
def initialize(handle, adapter: Backends::ThreeJS::RubyWasmAdapter.new)
|
|
10
|
+
@handle = handle
|
|
11
|
+
@adapter = adapter
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def name
|
|
15
|
+
@adapter.animation_clip_name(@handle)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def duration
|
|
19
|
+
@adapter.animation_clip_duration(@handle)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../backends/threejs"
|
|
4
|
+
require_relative "animation_action"
|
|
5
|
+
|
|
6
|
+
module Three
|
|
7
|
+
class AnimationMixer
|
|
8
|
+
attr_reader :root, :backend, :handle
|
|
9
|
+
|
|
10
|
+
def initialize(root, backend: Backends::ThreeJS.new)
|
|
11
|
+
@root = root
|
|
12
|
+
@backend = backend
|
|
13
|
+
@handle = @backend.create_animation_mixer(@backend.sync(root))
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def clip_action(clip, root: nil)
|
|
17
|
+
root_handle = root ? @backend.sync(root) : nil
|
|
18
|
+
handle = @backend.animation_mixer_clip_action(@handle, animation_clip_handle(clip), root_handle)
|
|
19
|
+
AnimationAction.new(handle, backend: @backend)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def update(delta)
|
|
23
|
+
@backend.update_animation_mixer(@handle, delta)
|
|
24
|
+
self
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def stop_all_action
|
|
28
|
+
@backend.stop_all_animation_actions(@handle)
|
|
29
|
+
self
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def uncache_root(root = @root)
|
|
33
|
+
@backend.uncache_animation_root(@handle, @backend.sync(root))
|
|
34
|
+
self
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def animation_clip_handle(clip)
|
|
40
|
+
clip.respond_to?(:handle) ? clip.handle : clip
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|