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,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,6 @@
1
+ import { bootRubyExample } from "../shared/boot.mjs";
2
+
3
+ await bootRubyExample({
4
+ main: "examples/browser/textures/main",
5
+ clearColor: 0x11161a
6
+ });
@@ -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