Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ dist/
scripts-managed/

externals/
submodules/three.js/
submodules/

69 changes: 69 additions & 0 deletions .kilo/plans/1777059824803-jolly-circuit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Plan to Update three.js Library from r183 to r184

## Overview
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.

## Current State
- Submodules contain:
- `submodules/three.js-r183/` (current version)
- `submodules/three.js-r184/` (new version to adopt)
- Scala.js bindings are in `modules/three/src/main/scala/THREE/`
- Bindings appear to be manually written facades, not auto-generated
- Some references to r183 exist in code comments (RenderPipeline.scala, Timer.scala, Clock.scala)

## Steps

### 1. Analyze API Changes
- Compare three.js r183 vs r184 to identify breaking changes, new features, and deprecations
- Focus on areas where Scala.js bindings exist:
- Core (Object3D, Scene, Camera, etc.)
- Renderers (WebGLRenderer, etc.)
- Materials, Geometries, Textures
- Animation, Loaders, Controls

### 2. Update Scala.js Facades
- Modify `modules/three/src/main/scala/THREE/` files to match r184 API:
- Add new methods/properties
- Remove deprecated ones
- Update method signatures if changed
- Update JSDoc annotations with correct @since tags
- Update deprecated annotations (change r183 to r184 where appropriate)

### 3. Update Version References
- Change all "@since r183" to "@since r184" where appropriate
- Update deprecation messages that reference r183
- Check for any hardcoded version references in comments or strings

### 4. Verify Build
- Run `sbt compile` to ensure all bindings compile correctly
- Run tests if available to verify functionality

### 5. Update Example Usage (if needed)
- Check if any example code in `example/client/` needs updates for API changes

## Implementation Notes
- Since bindings are manually written, careful comparison of three.js source is required
- Pay special attention to:
- Changes in method signatures
- New/removed properties
- Changes in constant values
- New classes or removed classes
- The three.js-r184 submodule already contains the source, so we can reference it directly

## Files to Examine
- `modules/three/src/main/scala/THREE/` (all Scala.js facade files)
- Key files likely to need updates based on r183 references:
- `modules/three/src/main/scala/THREE/renderers/RenderPipeline.scala`
- `modules/three/src/main/scala/THREE/core/Timer.scala`
- `modules/three/src/main/scala/THREE/core/Clock.scala`
- `submodules/three.js-r184/src/` for reference implementation

## Risks
- Missing API changes could lead to runtime facades not matching actual library
- Incorrect facades could cause compilation errors or runtime JavaScript errors
- Need to ensure all three.js facade traits/classes properly extend js.Object and use @JSImport/@JSName correctly

## Estimated Effort
- Analysis: 1-2 hours
- Facade updates: 2-4 hours (depending on number of changes)
- Testing: 30 minutes - 1 hour
3 changes: 3 additions & 0 deletions example/client/bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion example/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"@ui5/webcomponents-fiori": "2.21.1",
"@ui5/webcomponents-icons": "2.21.1",
"gl-matrix": "^3.4.3",
"three": "0.184.0"
"three": "0.184.0",
"three-html-render": "^0.1.2"
},
"devDependencies": {
"@scala-js/vite-plugin-scalajs": "^1.1.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ object HomePage:
demo("CompressedTexture", Router.uiRoute("demo", "three", "compressedtexture")),
demo("DepthTexture", Router.uiRoute("demo", "three", "depthtexture")),
demo("FramebufferTexture", Router.uiRoute("demo", "three", "framebuffertexture")),
demo("DDSLoader", Router.uiRoute("demo", "three", "ddsloader"))
demo("DDSLoader", Router.uiRoute("demo", "three", "ddsloader")),
demo("HTMLTexture", Router.uiRoute("demo", "three", "htmltexture"))
)
),
div(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,9 @@ object Router:
path("framebuffertexture") {
FramebufferTextureSample()
},
path("htmltexture") {
HTMLTextureSample()
},
path("ddsloader") {
DDSLoaderSample()
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
package dev.cheleb.scalajswebgl.samples.three.materials

import com.raquo.laminar.api.L.*

import THREE.*

import org.scalajs.dom
import org.scalajs.dom.window
import scala.scalajs.js
import scala.scalajs.js.annotation.JSImport
import scala.math.sin

@js.native
@JSImport("three-html-render/polyfill", JSImport.Namespace)
object HtmlInCanvasPolyfill extends js.Object {
def installHtmlInCanvasPolyfill(): Unit = js.native
}

object HTMLTextureSample {

def apply() =

val htmlTextureDiv = div(
h1("HTMLTexture Demo"),
p(
"Demonstrating HTMLTexture: a live HTML element rendered as a texture on 3D objects. ",
"The HTML content updates in real-time and is reflected on the mesh surfaces."
),
div(
cls := "canvas-container"
)
)

// Install the polyfill if native HTML-in-Canvas API is not available
if (!js.Dynamic.global.HTMLCanvasElement.prototype.hasOwnProperty("requestPaint").asInstanceOf[Boolean]) {
HtmlInCanvasPolyfill.installHtmlInCanvasPolyfill()
}

// Create the HTML element that will be used as a texture source
val htmlSource = dom.document.createElement("div").asInstanceOf[dom.html.Div]
htmlSource.id = "draw_element"
htmlSource.style.width = "512px"
htmlSource.style.background = "linear-gradient(135deg, #667eea 0%, #764ba2 100%)"
htmlSource.style.color = "white"
htmlSource.style.fontFamily = "Arial, sans-serif"
htmlSource.style.fontSize = "24px"
htmlSource.style.setProperty("line-height", "1.5")
htmlSource.style.setProperty("text-align", "center")
htmlSource.style.padding = "30px"
htmlSource.innerHTML =
"""<div>
| <h2 style="margin:0 0 12px 0">HTMLTexture</h2>
| <p style="margin:0;font-size:18px" id="timer">Time: 0.0s</p>
| <div style="margin-top:16px;width:80%;height:16px;background:rgba(255,255,255,0.3);border-radius:8px;overflow:hidden;display:inline-block">
| <div id="fill" style="width:0%;height:100%;background:white;border-radius:8px"></div>
| </div>
| <p style="margin-top:12px;font-size:16px">Live HTML on 3D mesh!</p>
|</div>""".stripMargin

// Create scene
val scene = Scene()

// Create camera
val camera = new PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 2000)
camera.position.z = 8

// Create renderer
val renderer = WebGLRenderer(antialias = true)
renderer.setSize(window.innerWidth * 0.8, window.innerHeight * 0.8)
renderer.setClearColor("#aaaaaa", 1)

// Pre-setup: attach the HTML element to the canvas and trigger an initial paint
// before the renderer tries to use the texture. This ensures the polyfill has
// completed at least one snapshot before texElementImage2D is called.
val canvas = renderer.domElement
canvas.asInstanceOf[js.Dynamic].setAttribute("layoutsubtree", "true")
canvas.appendChild(htmlSource)
canvas.asInstanceOf[js.Dynamic].requestPaint()

// Create the HTMLTexture from the div element
val htmlTexture = new HTMLTexture(htmlSource)

// --- Object 1: Rotating cube with HTMLTexture ---
val cubeGeometry = new BoxGeometry(2.5, 2.5, 2.5)
val cubeMaterial = MeshStandardMaterial(
roughness = 0.2,
metalness = 0.3
)
cubeMaterial.map = htmlTexture
val cube = new Mesh(cubeGeometry, cubeMaterial)
cube.position.x = -3
scene.add(cube)

// --- Object 2: Sphere with HTMLTexture ---
val sphereGeometry = new SphereGeometry(1.5, 32, 32)
val sphereMaterial = MeshStandardMaterial(
roughness = 0.1,
metalness = 0.4
)
sphereMaterial.map = htmlTexture
val sphere = new Mesh(sphereGeometry, sphereMaterial)
sphere.position.x = 3
scene.add(sphere)

// --- Object 3: Plane showing the texture flat ---
val planeGeometry = new PlaneGeometry(3, 3)
val planeMaterial = MeshBasicMaterial(map = htmlTexture)
val plane = new Mesh(planeGeometry, planeMaterial)
plane.position.set(0, -3, 0)
plane.rotation.x = -0.4
scene.add(plane)

// Add lighting
val directionalLight = DirectionalLight(0xffffff, 2.0)
directionalLight.position.set(5, 5, 5)
scene.add(directionalLight)

val directionalLight2 = DirectionalLight(0xffffff, 1.0)
directionalLight2.position.set(-5, 3, -3)
scene.add(directionalLight2)

val ambientLight = AmbientLight(0xffffff, 1.5)
scene.add(ambientLight)

// Animation loop
val animate: () => Unit = () => {
val time = js.Date.now() * 0.001

// Rotate objects
cube.rotation.x = sin(time * 0.5) * 0.5
cube.rotation.y = time * 0.4

sphere.rotation.y = time * 0.3

// Update the HTML content dynamically
val timerEl = htmlSource.querySelector("#timer")
if (timerEl != null) {
val seconds = f"${time % 100}%.1f"
timerEl.textContent = s"Time: ${seconds}s"
}
val fillEl = htmlSource.querySelector("#fill").asInstanceOf[dom.html.Div]
if (fillEl != null) {
val pct = ((sin(time * 0.5) + 1) * 50).toInt
fillEl.style.width = s"$pct%"
}

renderer.render(scene, camera)
}

// Defer the animation loop start to allow the polyfill to complete
// its first requestPaint() + rAF snapshot cycle.
window.requestAnimationFrame { _ =>
window.requestAnimationFrame { _ =>
renderer.setAnimationLoop(animate)
}
}

// Handle window resize
val onWindowResize: dom.Event => Unit = { _ =>
camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix()
renderer.setSize(window.innerWidth * 0.8, window.innerHeight * 0.8)
}
window.addEventListener("resize", onWindowResize)

// Append renderer to the canvas container
htmlTextureDiv.ref.querySelector(".canvas-container").appendChild(canvas)

htmlTextureDiv
}
4 changes: 2 additions & 2 deletions modules/three/src/main/scala/THREE/core/Clock.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ import scala.annotation.nowarn
* Class for keeping track of time.
*
* @deprecated
* since r183. Use [[Timer]] instead.
* since r184. Use [[Timer]] instead.
* @see
* [[Timer]] for the recommended replacement
*/
@js.native
@JSImport("three", "Clock")
@deprecated("Use THREE.Timer instead. Clock will be removed in a future version.", "r183")
@deprecated("Use THREE.Timer instead. Clock will be removed in a future version.", "r184")
class Clock(var autoStart: Boolean = true) extends js.Object {

/** Holds the time at which the clock's start() method was last called. */
Expand Down
2 changes: 1 addition & 1 deletion modules/three/src/main/scala/THREE/core/Timer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import org.scalajs.dom
* delta values when the app is inactive (e.g. tab switched or browser
* hidden).
*
* @since r183
* @since r184
*/
@js.native
@JSImport("three", "Timer")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import scala.scalajs.js
import scala.scalajs.js.annotation.*

/**
* RenderPipeline is the new name for PostProcessing since r183. It manages a
* RenderPipeline is the new name for PostProcessing since r184. It manages a
* series of post-processing passes to be applied to a scene.
*
* @since r183
* @since r184
*/
@js.native
@JSImport("three/webgpu", "RenderPipeline")
Expand Down
Loading