Skip to content

Commit bb42b15

Browse files
mrdoobclaude
andauthored
Added HTMLTexture (#31233)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent e011e14 commit bb42b15

11 files changed

Lines changed: 824 additions & 1 deletion

examples/files.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@
153153
"webgl_materials_texture_anisotropy",
154154
"webgl_materials_texture_canvas",
155155
"webgl_materials_texture_filters",
156+
"webgl_materials_texture_html",
156157
"webgl_materials_texture_manualmipmap",
157158
"webgl_materials_texture_partialupdate",
158159
"webgl_materials_texture_rotation",
@@ -385,6 +386,7 @@
385386
"webgpu_materials_lightmap",
386387
"webgpu_materials_matcap",
387388
"webgpu_materials_sss",
389+
"webgpu_materials_texture_html",
388390
"webgpu_materials_texture_manualmipmap",
389391
"webgpu_materials_transmission",
390392
"webgpu_materials_toon",
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
import {
2+
Matrix4,
3+
Vector3
4+
} from 'three';
5+
6+
const _pixelToLocal = new Matrix4();
7+
const _mvp = new Matrix4();
8+
const _viewport = new Matrix4();
9+
const _size = new Vector3();
10+
11+
/**
12+
* Manages interaction for 3D objects independently of the scene graph.
13+
*
14+
* For objects with an {@link HTMLTexture}, the manager computes CSS `matrix3d`
15+
* transforms each frame so the underlying HTML elements stay aligned with
16+
* their meshes. Because the elements are children of the canvas, the browser
17+
* dispatches pointer events to them natively.
18+
*
19+
* ```js
20+
* const interactions = new InteractionManager();
21+
* interactions.connect( renderer, camera );
22+
*
23+
* // Objects live anywhere in the scene graph
24+
* scene.add( mesh );
25+
*
26+
* // Register for interaction separately
27+
* interactions.add( mesh );
28+
*
29+
* // In the animation loop
30+
* interactions.update();
31+
* ```
32+
* @three_import import { InteractionManager } from 'three/addons/interaction/InteractionManager.js';
33+
*/
34+
class InteractionManager {
35+
36+
constructor() {
37+
38+
/**
39+
* The registered interactive objects.
40+
*
41+
* @type {Array<Object3D>}
42+
*/
43+
this.objects = [];
44+
45+
/**
46+
* The canvas element.
47+
*
48+
* @type {?HTMLCanvasElement}
49+
* @default null
50+
*/
51+
this.element = null;
52+
53+
/**
54+
* The camera used for computing the element transforms.
55+
*
56+
* @type {?Camera}
57+
* @default null
58+
*/
59+
this.camera = null;
60+
61+
this._cachedCssW = - 1;
62+
this._cachedCssH = - 1;
63+
64+
}
65+
66+
/**
67+
* Adds one or more objects to the manager.
68+
*
69+
* @param {...Object3D} objects - The objects to add.
70+
* @return {this}
71+
*/
72+
add( ...objects ) {
73+
74+
for ( const object of objects ) {
75+
76+
if ( this.objects.indexOf( object ) === - 1 ) {
77+
78+
this.objects.push( object );
79+
80+
}
81+
82+
}
83+
84+
return this;
85+
86+
}
87+
88+
/**
89+
* Removes one or more objects from the manager.
90+
*
91+
* @param {...Object3D} objects - The objects to remove.
92+
* @return {this}
93+
*/
94+
remove( ...objects ) {
95+
96+
for ( const object of objects ) {
97+
98+
const index = this.objects.indexOf( object );
99+
100+
if ( index !== - 1 ) {
101+
102+
this.objects.splice( index, 1 );
103+
104+
}
105+
106+
}
107+
108+
return this;
109+
110+
}
111+
112+
/**
113+
* Stores the renderer and camera needed for computing element transforms.
114+
*
115+
* @param {(WebGPURenderer|WebGLRenderer)} renderer - The renderer.
116+
* @param {Camera} camera - The camera.
117+
*/
118+
connect( renderer, camera ) {
119+
120+
this.camera = camera;
121+
this.element = renderer.domElement;
122+
123+
}
124+
125+
/**
126+
* Updates the element transforms for all registered objects.
127+
* Call this once per frame in the animation loop.
128+
*/
129+
update() {
130+
131+
const canvas = this.element;
132+
const camera = this.camera;
133+
134+
if ( canvas === null || camera === null ) return;
135+
136+
// Viewport: NDC (-1,1) to canvas CSS pixels, Y flipped.
137+
// Using CSS pixels (clientWidth/clientHeight) so the resulting matrix
138+
// can be applied directly as a CSS transform without DPR conversion.
139+
140+
const cssW = canvas.clientWidth;
141+
const cssH = canvas.clientHeight;
142+
143+
if ( cssW !== this._cachedCssW || cssH !== this._cachedCssH ) {
144+
145+
_viewport.set(
146+
cssW / 2, 0, 0, cssW / 2,
147+
0, - cssH / 2, 0, cssH / 2,
148+
0, 0, 1, 0,
149+
0, 0, 0, 1
150+
);
151+
152+
this._cachedCssW = cssW;
153+
this._cachedCssH = cssH;
154+
155+
}
156+
157+
for ( const object of this.objects ) {
158+
159+
const texture = object.material.map;
160+
161+
if ( ! texture || ! texture.isHTMLTexture ) continue;
162+
163+
const element = texture.image;
164+
165+
if ( ! element ) continue;
166+
167+
// Position at canvas origin so the CSS matrix3d maps correctly.
168+
element.style.position = 'absolute';
169+
element.style.left = '0';
170+
element.style.top = '0';
171+
element.style.transformOrigin = '0 0';
172+
173+
const elemW = element.offsetWidth;
174+
const elemH = element.offsetHeight;
175+
176+
// Get mesh dimensions from geometry bounding box
177+
178+
const geometry = object.geometry;
179+
180+
if ( ! geometry.boundingBox ) geometry.computeBoundingBox();
181+
182+
geometry.boundingBox.getSize( _size );
183+
184+
// Map element pixel coords (0,0)-(elemW,elemH) to mesh local coords.
185+
// Front face: top-left at (-sizeX/2, sizeY/2, maxZ), bottom-right at (sizeX/2, -sizeY/2, maxZ).
186+
187+
_pixelToLocal.set(
188+
_size.x / elemW, 0, 0, - _size.x / 2,
189+
0, - _size.y / elemH, 0, _size.y / 2,
190+
0, 0, 1, geometry.boundingBox.max.z,
191+
0, 0, 0, 1
192+
);
193+
194+
// Model-View-Projection
195+
196+
_mvp.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse );
197+
_mvp.multiply( object.matrixWorld );
198+
_mvp.multiply( _pixelToLocal );
199+
200+
// Apply viewport
201+
202+
_mvp.premultiply( _viewport );
203+
204+
// The browser performs the perspective divide (by w) when applying the matrix3d.
205+
206+
element.style.transform = 'matrix3d(' + _mvp.elements.join( ',' ) + ')';
207+
208+
}
209+
210+
}
211+
212+
/**
213+
* Disconnects this manager, clearing the renderer and camera references.
214+
*/
215+
disconnect() {
216+
217+
this.camera = null;
218+
this.element = null;
219+
this._cachedCssW = - 1;
220+
this._cachedCssH = - 1;
221+
222+
}
223+
224+
}
225+
226+
export { InteractionManager };
5.93 KB
Loading
5.91 KB
Loading

0 commit comments

Comments
 (0)