Skip to content

Commit 6575839

Browse files
authored
threejs r184 (#162)
* feat: add HTMLTexture support and update bindings for three.js r184 * feat: integrate three-html-render polyfill and enhance HTMLTextureSample * feat: add three-html-render dependency to enhance rendering capabilities
1 parent d111767 commit 6575839

11 files changed

Lines changed: 322 additions & 8 deletions

File tree

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@ dist/
1515
scripts-managed/
1616

1717
externals/
18-
submodules/three.js/
18+
submodules/
1919

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Plan to Update three.js Library from r183 to r184
2+
3+
## Overview
4+
This plan outlines the steps to update the three.js library binding in the Scala.js project from version r183 to r184. The project contains manually written Scala.js facades in `modules/three/` that need to be updated to match the three.js r184 API.
5+
6+
## Current State
7+
- Submodules contain:
8+
- `submodules/three.js-r183/` (current version)
9+
- `submodules/three.js-r184/` (new version to adopt)
10+
- Scala.js bindings are in `modules/three/src/main/scala/THREE/`
11+
- Bindings appear to be manually written facades, not auto-generated
12+
- Some references to r183 exist in code comments (RenderPipeline.scala, Timer.scala, Clock.scala)
13+
14+
## Steps
15+
16+
### 1. Analyze API Changes
17+
- Compare three.js r183 vs r184 to identify breaking changes, new features, and deprecations
18+
- Focus on areas where Scala.js bindings exist:
19+
- Core (Object3D, Scene, Camera, etc.)
20+
- Renderers (WebGLRenderer, etc.)
21+
- Materials, Geometries, Textures
22+
- Animation, Loaders, Controls
23+
24+
### 2. Update Scala.js Facades
25+
- Modify `modules/three/src/main/scala/THREE/` files to match r184 API:
26+
- Add new methods/properties
27+
- Remove deprecated ones
28+
- Update method signatures if changed
29+
- Update JSDoc annotations with correct @since tags
30+
- Update deprecated annotations (change r183 to r184 where appropriate)
31+
32+
### 3. Update Version References
33+
- Change all "@since r183" to "@since r184" where appropriate
34+
- Update deprecation messages that reference r183
35+
- Check for any hardcoded version references in comments or strings
36+
37+
### 4. Verify Build
38+
- Run `sbt compile` to ensure all bindings compile correctly
39+
- Run tests if available to verify functionality
40+
41+
### 5. Update Example Usage (if needed)
42+
- Check if any example code in `example/client/` needs updates for API changes
43+
44+
## Implementation Notes
45+
- Since bindings are manually written, careful comparison of three.js source is required
46+
- Pay special attention to:
47+
- Changes in method signatures
48+
- New/removed properties
49+
- Changes in constant values
50+
- New classes or removed classes
51+
- The three.js-r184 submodule already contains the source, so we can reference it directly
52+
53+
## Files to Examine
54+
- `modules/three/src/main/scala/THREE/` (all Scala.js facade files)
55+
- Key files likely to need updates based on r183 references:
56+
- `modules/three/src/main/scala/THREE/renderers/RenderPipeline.scala`
57+
- `modules/three/src/main/scala/THREE/core/Timer.scala`
58+
- `modules/three/src/main/scala/THREE/core/Clock.scala`
59+
- `submodules/three.js-r184/src/` for reference implementation
60+
61+
## Risks
62+
- Missing API changes could lead to runtime facades not matching actual library
63+
- Incorrect facades could cause compilation errors or runtime JavaScript errors
64+
- Need to ensure all three.js facade traits/classes properly extend js.Object and use @JSImport/@JSName correctly
65+
66+
## Estimated Effort
67+
- Analysis: 1-2 hours
68+
- Facade updates: 2-4 hours (depending on number of changes)
69+
- Testing: 30 minutes - 1 hour

example/client/bun.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

example/client/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
"@ui5/webcomponents-fiori": "2.21.1",
1616
"@ui5/webcomponents-icons": "2.21.1",
1717
"gl-matrix": "^3.4.3",
18-
"three": "0.184.0"
18+
"three": "0.184.0",
19+
"three-html-render": "^0.1.2"
1920
},
2021
"devDependencies": {
2122
"@scala-js/vite-plugin-scalajs": "^1.1.0",

example/client/src/main/scala/dev/cheleb/scalajswebgl/app/HomePage.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ object HomePage:
6262
demo("CompressedTexture", Router.uiRoute("demo", "three", "compressedtexture")),
6363
demo("DepthTexture", Router.uiRoute("demo", "three", "depthtexture")),
6464
demo("FramebufferTexture", Router.uiRoute("demo", "three", "framebuffertexture")),
65-
demo("DDSLoader", Router.uiRoute("demo", "three", "ddsloader"))
65+
demo("DDSLoader", Router.uiRoute("demo", "three", "ddsloader")),
66+
demo("HTMLTexture", Router.uiRoute("demo", "three", "htmltexture"))
6667
)
6768
),
6869
div(

example/client/src/main/scala/dev/cheleb/scalajswebgl/app/Router.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,9 @@ object Router:
191191
path("framebuffertexture") {
192192
FramebufferTextureSample()
193193
},
194+
path("htmltexture") {
195+
HTMLTextureSample()
196+
},
194197
path("ddsloader") {
195198
DDSLoaderSample()
196199
},
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
package dev.cheleb.scalajswebgl.samples.three.materials
2+
3+
import com.raquo.laminar.api.L.*
4+
5+
import THREE.*
6+
7+
import org.scalajs.dom
8+
import org.scalajs.dom.window
9+
import scala.scalajs.js
10+
import scala.scalajs.js.annotation.JSImport
11+
import scala.math.sin
12+
13+
@js.native
14+
@JSImport("three-html-render/polyfill", JSImport.Namespace)
15+
object HtmlInCanvasPolyfill extends js.Object {
16+
def installHtmlInCanvasPolyfill(): Unit = js.native
17+
}
18+
19+
object HTMLTextureSample {
20+
21+
def apply() =
22+
23+
val htmlTextureDiv = div(
24+
h1("HTMLTexture Demo"),
25+
p(
26+
"Demonstrating HTMLTexture: a live HTML element rendered as a texture on 3D objects. ",
27+
"The HTML content updates in real-time and is reflected on the mesh surfaces."
28+
),
29+
div(
30+
cls := "canvas-container"
31+
)
32+
)
33+
34+
// Install the polyfill if native HTML-in-Canvas API is not available
35+
if (!js.Dynamic.global.HTMLCanvasElement.prototype.hasOwnProperty("requestPaint").asInstanceOf[Boolean]) {
36+
HtmlInCanvasPolyfill.installHtmlInCanvasPolyfill()
37+
}
38+
39+
// Create the HTML element that will be used as a texture source
40+
val htmlSource = dom.document.createElement("div").asInstanceOf[dom.html.Div]
41+
htmlSource.id = "draw_element"
42+
htmlSource.style.width = "512px"
43+
htmlSource.style.background = "linear-gradient(135deg, #667eea 0%, #764ba2 100%)"
44+
htmlSource.style.color = "white"
45+
htmlSource.style.fontFamily = "Arial, sans-serif"
46+
htmlSource.style.fontSize = "24px"
47+
htmlSource.style.setProperty("line-height", "1.5")
48+
htmlSource.style.setProperty("text-align", "center")
49+
htmlSource.style.padding = "30px"
50+
htmlSource.innerHTML =
51+
"""<div>
52+
| <h2 style="margin:0 0 12px 0">HTMLTexture</h2>
53+
| <p style="margin:0;font-size:18px" id="timer">Time: 0.0s</p>
54+
| <div style="margin-top:16px;width:80%;height:16px;background:rgba(255,255,255,0.3);border-radius:8px;overflow:hidden;display:inline-block">
55+
| <div id="fill" style="width:0%;height:100%;background:white;border-radius:8px"></div>
56+
| </div>
57+
| <p style="margin-top:12px;font-size:16px">Live HTML on 3D mesh!</p>
58+
|</div>""".stripMargin
59+
60+
// Create scene
61+
val scene = Scene()
62+
63+
// Create camera
64+
val camera = new PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 2000)
65+
camera.position.z = 8
66+
67+
// Create renderer
68+
val renderer = WebGLRenderer(antialias = true)
69+
renderer.setSize(window.innerWidth * 0.8, window.innerHeight * 0.8)
70+
renderer.setClearColor("#aaaaaa", 1)
71+
72+
// Pre-setup: attach the HTML element to the canvas and trigger an initial paint
73+
// before the renderer tries to use the texture. This ensures the polyfill has
74+
// completed at least one snapshot before texElementImage2D is called.
75+
val canvas = renderer.domElement
76+
canvas.asInstanceOf[js.Dynamic].setAttribute("layoutsubtree", "true")
77+
canvas.appendChild(htmlSource)
78+
canvas.asInstanceOf[js.Dynamic].requestPaint()
79+
80+
// Create the HTMLTexture from the div element
81+
val htmlTexture = new HTMLTexture(htmlSource)
82+
83+
// --- Object 1: Rotating cube with HTMLTexture ---
84+
val cubeGeometry = new BoxGeometry(2.5, 2.5, 2.5)
85+
val cubeMaterial = MeshStandardMaterial(
86+
roughness = 0.2,
87+
metalness = 0.3
88+
)
89+
cubeMaterial.map = htmlTexture
90+
val cube = new Mesh(cubeGeometry, cubeMaterial)
91+
cube.position.x = -3
92+
scene.add(cube)
93+
94+
// --- Object 2: Sphere with HTMLTexture ---
95+
val sphereGeometry = new SphereGeometry(1.5, 32, 32)
96+
val sphereMaterial = MeshStandardMaterial(
97+
roughness = 0.1,
98+
metalness = 0.4
99+
)
100+
sphereMaterial.map = htmlTexture
101+
val sphere = new Mesh(sphereGeometry, sphereMaterial)
102+
sphere.position.x = 3
103+
scene.add(sphere)
104+
105+
// --- Object 3: Plane showing the texture flat ---
106+
val planeGeometry = new PlaneGeometry(3, 3)
107+
val planeMaterial = MeshBasicMaterial(map = htmlTexture)
108+
val plane = new Mesh(planeGeometry, planeMaterial)
109+
plane.position.set(0, -3, 0)
110+
plane.rotation.x = -0.4
111+
scene.add(plane)
112+
113+
// Add lighting
114+
val directionalLight = DirectionalLight(0xffffff, 2.0)
115+
directionalLight.position.set(5, 5, 5)
116+
scene.add(directionalLight)
117+
118+
val directionalLight2 = DirectionalLight(0xffffff, 1.0)
119+
directionalLight2.position.set(-5, 3, -3)
120+
scene.add(directionalLight2)
121+
122+
val ambientLight = AmbientLight(0xffffff, 1.5)
123+
scene.add(ambientLight)
124+
125+
// Animation loop
126+
val animate: () => Unit = () => {
127+
val time = js.Date.now() * 0.001
128+
129+
// Rotate objects
130+
cube.rotation.x = sin(time * 0.5) * 0.5
131+
cube.rotation.y = time * 0.4
132+
133+
sphere.rotation.y = time * 0.3
134+
135+
// Update the HTML content dynamically
136+
val timerEl = htmlSource.querySelector("#timer")
137+
if (timerEl != null) {
138+
val seconds = f"${time % 100}%.1f"
139+
timerEl.textContent = s"Time: ${seconds}s"
140+
}
141+
val fillEl = htmlSource.querySelector("#fill").asInstanceOf[dom.html.Div]
142+
if (fillEl != null) {
143+
val pct = ((sin(time * 0.5) + 1) * 50).toInt
144+
fillEl.style.width = s"$pct%"
145+
}
146+
147+
renderer.render(scene, camera)
148+
}
149+
150+
// Defer the animation loop start to allow the polyfill to complete
151+
// its first requestPaint() + rAF snapshot cycle.
152+
window.requestAnimationFrame { _ =>
153+
window.requestAnimationFrame { _ =>
154+
renderer.setAnimationLoop(animate)
155+
}
156+
}
157+
158+
// Handle window resize
159+
val onWindowResize: dom.Event => Unit = { _ =>
160+
camera.aspect = window.innerWidth / window.innerHeight
161+
camera.updateProjectionMatrix()
162+
renderer.setSize(window.innerWidth * 0.8, window.innerHeight * 0.8)
163+
}
164+
window.addEventListener("resize", onWindowResize)
165+
166+
// Append renderer to the canvas container
167+
htmlTextureDiv.ref.querySelector(".canvas-container").appendChild(canvas)
168+
169+
htmlTextureDiv
170+
}

modules/three/src/main/scala/THREE/core/Clock.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ import scala.annotation.nowarn
88
* Class for keeping track of time.
99
*
1010
* @deprecated
11-
* since r183. Use [[Timer]] instead.
11+
* since r184. Use [[Timer]] instead.
1212
* @see
1313
* [[Timer]] for the recommended replacement
1414
*/
1515
@js.native
1616
@JSImport("three", "Clock")
17-
@deprecated("Use THREE.Timer instead. Clock will be removed in a future version.", "r183")
17+
@deprecated("Use THREE.Timer instead. Clock will be removed in a future version.", "r184")
1818
class Clock(var autoStart: Boolean = true) extends js.Object {
1919

2020
/** Holds the time at which the clock's start() method was last called. */

modules/three/src/main/scala/THREE/core/Timer.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import org.scalajs.dom
1616
* delta values when the app is inactive (e.g. tab switched or browser
1717
* hidden).
1818
*
19-
* @since r183
19+
* @since r184
2020
*/
2121
@js.native
2222
@JSImport("three", "Timer")

modules/three/src/main/scala/THREE/renderers/RenderPipeline.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ import scala.scalajs.js
44
import scala.scalajs.js.annotation.*
55

66
/**
7-
* RenderPipeline is the new name for PostProcessing since r183. It manages a
7+
* RenderPipeline is the new name for PostProcessing since r184. It manages a
88
* series of post-processing passes to be applied to a scene.
99
*
10-
* @since r183
10+
* @since r184
1111
*/
1212
@js.native
1313
@JSImport("three/webgpu", "RenderPipeline")

0 commit comments

Comments
 (0)