dom-sync-gl 0.0.1

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.
package/dist/index.js ADDED
@@ -0,0 +1,1695 @@
1
+ var L = Object.defineProperty;
2
+ var z = (d, e, t) => e in d ? L(d, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : d[e] = t;
3
+ var s = (d, e, t) => z(d, typeof e != "symbol" ? e + "" : e, t);
4
+ import * as a from "three";
5
+ import { OrbitControls as O } from "three/examples/jsm/controls/OrbitControls.js";
6
+ import { GLTFLoader as Y } from "three/examples/jsm/loaders/GLTFLoader.js";
7
+ const v = 60, G = 0.1, k = 1e4, y = 1, V = { x: 0, y: 0, z: 0 }, F = 16777215, D = 0.5, U = 16777215, H = 1, w = { x: 1, y: 1, z: 1 };
8
+ class C {
9
+ constructor(e) {
10
+ s(this, "rect");
11
+ /**
12
+ * 内部 THREE.PerspectiveCamera。外部から `lookAt` 等の操作は可能。
13
+ *
14
+ * **注意**: `position.z` は「DOM 1px = WebGL 1unit」を成立させるための値で、
15
+ * `resize()` のたびに `rect.height / 2 / tan(fov/2)` で**上書きされる**。
16
+ * 外から position.z を書き換えても resize で消えるので、ズームや視点調整を
17
+ * 永続化したい場合は別途 update ループで再設定するか、座標系が変わってもよい
18
+ * 設計にすること。
19
+ */
20
+ s(this, "instance");
21
+ this.rect = e;
22
+ const t = v / 2 * (Math.PI / 180), i = Math.max(1, e.width), o = Math.max(1, e.height), r = o / 2 / Math.tan(t);
23
+ this.instance = new a.PerspectiveCamera(
24
+ v,
25
+ i / o,
26
+ G,
27
+ k
28
+ ), this.instance.position.z = r;
29
+ }
30
+ resize(e) {
31
+ if (e.width <= 0 || e.height <= 0) return;
32
+ this.rect = e;
33
+ const t = Math.max(1, e.width), i = Math.max(1, e.height), o = v / 2 * (Math.PI / 180), r = i / 2 / Math.tan(o);
34
+ this.instance.aspect = t / i, this.instance.position.z = r, this.instance.updateProjectionMatrix();
35
+ }
36
+ }
37
+ class B {
38
+ constructor(e) {
39
+ s(this, "ambientLight");
40
+ s(this, "directionalLight");
41
+ s(this, "scene");
42
+ this.scene = e, this.ambientLight = new a.AmbientLight(
43
+ F,
44
+ D
45
+ ), this.scene.add(this.ambientLight), this.directionalLight = new a.DirectionalLight(
46
+ U,
47
+ H
48
+ ), this.directionalLight.position.set(
49
+ w.x,
50
+ w.y,
51
+ w.z
52
+ ), this.scene.add(this.directionalLight), this.scene.add(this.directionalLight.target);
53
+ }
54
+ }
55
+ class P {
56
+ constructor(e, t) {
57
+ s(this, "element");
58
+ s(this, "canvasRect");
59
+ s(this, "positionInfo");
60
+ s(this, "rect");
61
+ // calculateWebGLPosition の戻り値を使い回すバッファ(GC削減)
62
+ s(this, "_outPosition", { x: 0, y: 0 });
63
+ this.element = e, this.canvasRect = t, this.rect = new DOMRect(), this.positionInfo = {
64
+ pageTop: 0,
65
+ pageLeft: 0,
66
+ isFixed: !1
67
+ }, this.updatePositionInfo();
68
+ }
69
+ /**
70
+ * DOM要素の位置情報を更新(毎フレーム呼ばれる)
71
+ */
72
+ updatePositionInfo() {
73
+ this.rect = this.element.getBoundingClientRect();
74
+ const e = window.scrollY, t = window.scrollX;
75
+ this.positionInfo.isFixed ? (this.positionInfo.pageTop = this.rect.top, this.positionInfo.pageLeft = this.rect.left) : (this.positionInfo.pageTop = this.rect.top + e, this.positionInfo.pageLeft = this.rect.left + t);
76
+ }
77
+ /**
78
+ * position: fixed または sticky かどうかを再チェック(初期化・リサイズ時のみ呼ぶ)。
79
+ * fixed / sticky 要素は document 上で位置が固定されないため、毎フレーム rect.top を
80
+ * viewport 座標として扱う isFixed branch を使う。
81
+ */
82
+ refreshPositionType() {
83
+ const e = window.getComputedStyle(this.element).position;
84
+ this.positionInfo.isFixed = e === "fixed" || e === "sticky";
85
+ }
86
+ /**
87
+ * WebGL座標系での位置を計算。
88
+ * 戻り値は内部バッファを使い回すため、保持する場合は呼び出し側でコピーすること。
89
+ */
90
+ calculateWebGLPosition(e = 0, t = 0) {
91
+ let i, o;
92
+ return this.positionInfo.isFixed ? (i = this.canvasRect.left + this.canvasRect.width / 2, o = this.canvasRect.top + this.canvasRect.height / 2) : (i = this.canvasRect.left + e + this.canvasRect.width / 2, o = this.canvasRect.top + t + this.canvasRect.height / 2), this._outPosition.x = this.positionInfo.pageLeft + this.rect.width / 2 - i, this._outPosition.y = -(this.positionInfo.pageTop + this.rect.height / 2 - o), this._outPosition;
93
+ }
94
+ /**
95
+ * Canvas矩形を更新
96
+ */
97
+ setCanvasRect(e) {
98
+ this.canvasRect = e;
99
+ }
100
+ /**
101
+ * position: fixedかどうか
102
+ */
103
+ get isFixed() {
104
+ return this.positionInfo.isFixed;
105
+ }
106
+ /**
107
+ * ページトップ位置
108
+ */
109
+ get pageTop() {
110
+ return this.positionInfo.pageTop;
111
+ }
112
+ /**
113
+ * ページレフト位置
114
+ */
115
+ get pageLeft() {
116
+ return this.positionInfo.pageLeft;
117
+ }
118
+ }
119
+ const W = `
120
+ varying vec2 vUv;
121
+ void main() {
122
+ vUv = uv;
123
+ gl_Position = vec4(position, 1.0);
124
+ }
125
+ `;
126
+ class R {
127
+ constructor(e) {
128
+ s(this, "material");
129
+ /** false の時、EffectComposer はこのパスをスキップする(パススルー扱い)。 */
130
+ s(this, "enabled", !0);
131
+ this.material = e;
132
+ }
133
+ setUniform(e, t) {
134
+ this.material.uniforms[e] !== void 0 && (this.material.uniforms[e].value = t);
135
+ }
136
+ getUniform(e) {
137
+ return this.material.uniforms[e];
138
+ }
139
+ }
140
+ class j {
141
+ constructor(e, t, i) {
142
+ s(this, "renderer");
143
+ s(this, "passes", []);
144
+ s(this, "targetA");
145
+ s(this, "targetB");
146
+ s(this, "postScene");
147
+ s(this, "postCamera");
148
+ s(this, "postMesh");
149
+ s(this, "geometry");
150
+ /**
151
+ * dispose 後の API 呼び出しを no-op にするフラグ。
152
+ * dispose 済みの RT に render すると INVALID_OPERATION になるため。
153
+ */
154
+ s(this, "_disposed", !1);
155
+ this.renderer = e;
156
+ const o = e.getPixelRatio(), r = Math.max(1, Math.floor(t * o)), h = Math.max(1, Math.floor(i * o)), l = {
157
+ minFilter: a.LinearFilter,
158
+ magFilter: a.LinearFilter,
159
+ format: a.RGBAFormat,
160
+ stencilBuffer: !1
161
+ };
162
+ this.targetA = new a.WebGLRenderTarget(r, h, l), this.targetB = new a.WebGLRenderTarget(r, h, l), this.postScene = new a.Scene(), this.postCamera = new a.OrthographicCamera(-1, 1, 1, -1, 0, 1), this.geometry = new a.PlaneGeometry(2, 2), this.postMesh = new a.Mesh(this.geometry), this.postScene.add(this.postMesh);
163
+ }
164
+ addEffect(e) {
165
+ if (this._disposed)
166
+ throw new Error("[EffectComposer] dispose 済みのインスタンスでは addEffect() できません。");
167
+ const t = new a.ShaderMaterial({
168
+ uniforms: {
169
+ tDiffuse: { value: null },
170
+ ...e.uniforms
171
+ },
172
+ vertexShader: W,
173
+ fragmentShader: e.fragmentShader,
174
+ transparent: !0
175
+ }), i = new R(t);
176
+ return this.passes.push(i), i;
177
+ }
178
+ /**
179
+ * 登録済みパスを 1 つ取り除いて material を dispose する。
180
+ * `addEffect()` の戻り値か、`BaseEffect.getPass()` の値を渡す。
181
+ * 存在しない pass を渡した時は何もしない。
182
+ */
183
+ removeEffect(e) {
184
+ if (this._disposed) return !1;
185
+ const t = this.passes.indexOf(e);
186
+ return t < 0 ? !1 : (this.passes.splice(t, 1), e.material.dispose(), !0);
187
+ }
188
+ /**
189
+ * シーンをレンダリングし、登録されたエフェクトを順に適用する。
190
+ * エフェクトが0個の場合は通常レンダリング。
191
+ */
192
+ render(e, t) {
193
+ if (this._disposed) return;
194
+ let i = 0, o = -1;
195
+ const r = this.passes;
196
+ for (let n = 0, c = r.length; n < c; n++)
197
+ r[n].enabled && (i++, o = n);
198
+ if (i === 0) {
199
+ this.renderer.setRenderTarget(null), this.renderer.render(e, t);
200
+ return;
201
+ }
202
+ this.renderer.setRenderTarget(this.targetA), this.renderer.render(e, t);
203
+ let h = this.targetA, l = this.targetB;
204
+ for (let n = 0; n < r.length; n++) {
205
+ const c = r[n];
206
+ if (!c.enabled) continue;
207
+ const u = n === o;
208
+ if (c.material.uniforms.tDiffuse.value = h.texture, this.postMesh.material = c.material, this.renderer.setRenderTarget(u ? null : l), this.renderer.render(this.postScene, this.postCamera), !u) {
209
+ const f = h;
210
+ h = l, l = f;
211
+ }
212
+ }
213
+ }
214
+ resize(e, t) {
215
+ if (this._disposed) return;
216
+ const i = this.renderer.getPixelRatio(), o = Math.max(1, Math.floor(e * i)), r = Math.max(1, Math.floor(t * i));
217
+ this.targetA.setSize(o, r), this.targetB.setSize(o, r);
218
+ }
219
+ dispose() {
220
+ if (!this._disposed) {
221
+ this._disposed = !0, this.targetA.dispose(), this.targetB.dispose(), this.geometry.dispose();
222
+ for (const e of this.passes)
223
+ e.material.dispose();
224
+ this.passes = [];
225
+ }
226
+ }
227
+ }
228
+ const X = `
229
+ varying vec2 vUv;
230
+ void main() {
231
+ vUv = uv;
232
+ gl_Position = vec4(position, 1.0);
233
+ }
234
+ `;
235
+ class N {
236
+ constructor(e, t, i, o, r) {
237
+ s(this, "renderer");
238
+ s(this, "sourceMesh");
239
+ s(this, "passes", []);
240
+ s(this, "targetA");
241
+ s(this, "targetB");
242
+ // plane の内容を FBO に焼くための専用シーン
243
+ s(this, "localScene");
244
+ s(this, "localCamera");
245
+ s(this, "localMesh");
246
+ // エフェクトパス適用用シーン
247
+ s(this, "postScene");
248
+ s(this, "postCamera");
249
+ s(this, "postMesh");
250
+ s(this, "postGeo");
251
+ // main scene に表示するプロキシ
252
+ s(this, "proxyMesh");
253
+ s(this, "proxyMaterial");
254
+ s(this, "proxyGeo");
255
+ s(this, "mainScene");
256
+ /**
257
+ * 全 effect が enabled=false の時、PlaneComposer をスキップして sourceMesh を
258
+ * 直接 main scene で描画する状態(bypass)。 fullscreen pass 1 回ぶんを丸ごと
259
+ * 削減できる。effect が再度 enable された時は通常レンダリングに復帰する。
260
+ */
261
+ s(this, "_bypassed", !1);
262
+ /** dispose 済みフラグ。dispose 後の render / addEffect を no-op にする。 */
263
+ s(this, "_disposed", !1);
264
+ this.renderer = e, this.sourceMesh = t, this.mainScene = i;
265
+ const h = e.getPixelRatio(), l = Math.max(1, Math.floor(o * h)), n = Math.max(1, Math.floor(r * h)), c = {
266
+ minFilter: a.LinearFilter,
267
+ magFilter: a.LinearFilter,
268
+ format: a.RGBAFormat,
269
+ stencilBuffer: !1
270
+ };
271
+ this.targetA = new a.WebGLRenderTarget(l, n, c), this.targetB = new a.WebGLRenderTarget(l, n, c), this.localScene = new a.Scene(), this.localCamera = new a.OrthographicCamera(
272
+ -o / 2,
273
+ o / 2,
274
+ r / 2,
275
+ -r / 2,
276
+ 0.1,
277
+ 10
278
+ ), this.localCamera.position.set(0, 0, 1), this.localMesh = new a.Mesh(t.geometry, t.material), this.localMesh.position.set(0, 0, 0), this.localMesh.scale.copy(t.scale), this.localScene.add(this.localMesh), this.postScene = new a.Scene(), this.postCamera = new a.OrthographicCamera(-1, 1, 1, -1, 0, 1), this.postGeo = new a.PlaneGeometry(2, 2), this.postMesh = new a.Mesh(this.postGeo), this.postScene.add(this.postMesh), i.remove(t), this.proxyMaterial = new a.MeshBasicMaterial({
279
+ map: this.targetA.texture,
280
+ transparent: !0
281
+ }), this.proxyGeo = new a.PlaneGeometry(1, 1), this.proxyMesh = new a.Mesh(this.proxyGeo, this.proxyMaterial), this.proxyMesh.position.copy(t.position), this.proxyMesh.scale.copy(t.scale), this.proxyMesh.quaternion.copy(t.quaternion), i.add(this.proxyMesh);
282
+ }
283
+ addEffect(e) {
284
+ if (this._disposed)
285
+ throw new Error("[PlaneComposer] dispose 済みのインスタンスでは addEffect() できません。");
286
+ const t = new a.ShaderMaterial({
287
+ uniforms: {
288
+ tDiffuse: { value: null },
289
+ ...e.uniforms
290
+ },
291
+ vertexShader: X,
292
+ fragmentShader: e.fragmentShader,
293
+ transparent: !0
294
+ }), i = new R(t);
295
+ return this.passes.push(i), i;
296
+ }
297
+ /**
298
+ * 登録済みパスを 1 つ取り除いて material を dispose する。
299
+ * 存在しない pass を渡した時は何もしない。
300
+ */
301
+ removeEffect(e) {
302
+ if (this._disposed) return !1;
303
+ const t = this.passes.indexOf(e);
304
+ return t < 0 ? !1 : (this.passes.splice(t, 1), e.material.dispose(), !0);
305
+ }
306
+ /**
307
+ * bypass 状態に切り替える: sourceMesh を main scene に戻し proxyMesh を隠す。
308
+ * 全 effect が disabled の時に呼ばれ、PlaneComposer.render の fullscreen pass を
309
+ * まるごと省く (= 1 pass / 6.35M フラグメント分の節約)。
310
+ */
311
+ enterBypass() {
312
+ this._bypassed || (this.mainScene.add(this.sourceMesh), this.proxyMesh.visible = !1, this._bypassed = !0);
313
+ }
314
+ /** bypass 状態から通常レンダリングへ復帰する。 */
315
+ exitBypass() {
316
+ this._bypassed && (this.mainScene.remove(this.sourceMesh), this.proxyMesh.visible = !0, this._bypassed = !1);
317
+ }
318
+ render() {
319
+ if (this._disposed) return;
320
+ if (!this.sourceMesh.visible) {
321
+ this.proxyMesh.visible = !1;
322
+ return;
323
+ }
324
+ let e = 0;
325
+ for (const o of this.passes) o.enabled && e++;
326
+ if (e === 0) {
327
+ this.enterBypass();
328
+ return;
329
+ }
330
+ this.exitBypass(), this.proxyMesh.visible = !0, this.proxyMesh.position.copy(this.sourceMesh.position), this.proxyMesh.scale.copy(this.sourceMesh.scale), this.proxyMesh.quaternion.copy(this.sourceMesh.quaternion), this.localMesh.scale.copy(this.sourceMesh.scale), this.renderer.setRenderTarget(this.targetA), this.renderer.render(this.localScene, this.localCamera);
331
+ let t = this.targetA, i = this.targetB;
332
+ for (const o of this.passes) {
333
+ if (!o.enabled) continue;
334
+ o.material.uniforms.tDiffuse.value = t.texture, this.postMesh.material = o.material, this.renderer.setRenderTarget(i), this.renderer.render(this.postScene, this.postCamera);
335
+ const r = t;
336
+ t = i, i = r;
337
+ }
338
+ this.proxyMaterial.map = t.texture, this.renderer.setRenderTarget(null);
339
+ }
340
+ resize(e, t) {
341
+ if (this._disposed) return;
342
+ const i = this.renderer.getPixelRatio(), o = Math.max(1, Math.floor(e * i)), r = Math.max(1, Math.floor(t * i));
343
+ this.targetA.setSize(o, r), this.targetB.setSize(o, r), this.localCamera.left = -e / 2, this.localCamera.right = e / 2, this.localCamera.top = t / 2, this.localCamera.bottom = -t / 2, this.localCamera.updateProjectionMatrix();
344
+ }
345
+ dispose() {
346
+ if (!this._disposed) {
347
+ this._disposed = !0, this.mainScene.remove(this.proxyMesh), this._bypassed || this.mainScene.add(this.sourceMesh), this._bypassed = !1, this.targetA.dispose(), this.targetB.dispose(), this.postGeo.dispose(), this.proxyGeo.dispose(), this.proxyMaterial.dispose();
348
+ for (const e of this.passes)
349
+ e.material.dispose();
350
+ this.passes = [];
351
+ }
352
+ }
353
+ }
354
+ const S = new a.TextureLoader();
355
+ S.setCrossOrigin("anonymous");
356
+ const q = `
357
+ varying vec2 vUv;
358
+
359
+ void main() {
360
+ vUv = uv;
361
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
362
+ }
363
+ `, $ = `
364
+ uniform sampler2D uTexture;
365
+ uniform float uTime;
366
+ uniform vec2 uResolution;
367
+ varying vec2 vUv;
368
+
369
+ void main() {
370
+ vec4 texColor = texture2D(uTexture, vUv);
371
+ gl_FragColor = texColor;
372
+ }
373
+ `;
374
+ class K {
375
+ constructor(e, t, i, o, r = {}, h) {
376
+ s(this, "element");
377
+ s(this, "texture");
378
+ s(this, "mesh");
379
+ s(this, "geometry");
380
+ s(this, "material");
381
+ s(this, "scene");
382
+ s(this, "clock");
383
+ s(this, "positionCalculator");
384
+ s(this, "canvasRect");
385
+ s(this, "isVisible");
386
+ s(this, "updateRectEveryFrame");
387
+ s(this, "observer");
388
+ s(this, "destroyed");
389
+ s(this, "planeComposer", null);
390
+ s(this, "renderer");
391
+ s(this, "effects", []);
392
+ /**
393
+ * effect.update に渡す mouse UV のスクラッチ。
394
+ * `material.uniforms.uMouseUV.value` を直接渡すと effect 側で `.set()` 等の破壊的操作で
395
+ * plane の uniform が書き換わる事故が起きうるため、毎フレ copy したスクラッチを渡す。
396
+ */
397
+ s(this, "_effectMouseUV", new a.Vector2());
398
+ /**
399
+ * effect の `setupGUI` 呼び出し時に root GUI を渡すための provider。
400
+ * WebGLApp.createPlane() でセットされる。null の時は GUI を作らない(showGUI: false 等)。
401
+ *
402
+ * lil-gui は optional peer の dynamic import なので Promise を返す。
403
+ * @internal
404
+ */
405
+ s(this, "guiProvider", null);
406
+ /** 自前で load した texture かどうか。destroy() で dispose してよいか判定する。 */
407
+ s(this, "ownsTexture", !1);
408
+ /**
409
+ * options.crossOrigin を指定された場合のみ生成する per-instance loader。
410
+ * 指定なしの場合は sharedTextureLoader をそのまま使う。
411
+ */
412
+ s(this, "crossOrigin");
413
+ if (this.element = e, this.scene = t, this.renderer = o, this.texture = null, this.destroyed = !1, this.updateRectEveryFrame = r.updateRectEveryFrame || !1, this.crossOrigin = r.crossOrigin, this.clock = h ?? new a.Clock(), this.canvasRect = i, this.positionCalculator = e ? new P(e, i) : null, this.isVisible = !e, this.observer = null, e) {
414
+ let c = !1;
415
+ const u = r.inViewRepeat ?? !1;
416
+ this.observer = new IntersectionObserver(
417
+ (f) => {
418
+ const p = f[0];
419
+ this.isVisible = p.isIntersecting, this.mesh.visible = this.isVisible, p.isIntersecting ? r.onInView && (!c || u) && (c = !0, r.onInView(this)) : u && (c = !1, r.onOutView?.(this));
420
+ },
421
+ { rootMargin: r.inViewRootMargin ?? "100%" }
422
+ ), this.observer.observe(e);
423
+ }
424
+ const l = r.segments ?? 1;
425
+ this.geometry = new a.PlaneGeometry(1, 1, l, l);
426
+ const n = {
427
+ uTexture: { value: null },
428
+ uAlpha: { value: 1 },
429
+ uResolution: { value: new a.Vector2() },
430
+ uTime: { value: 0 },
431
+ uIsHovered: { value: !1 },
432
+ uMouseUV: { value: new a.Vector2(0, 0) },
433
+ ...r.uniforms
434
+ };
435
+ this.material = new a.ShaderMaterial({
436
+ transparent: !0,
437
+ uniforms: n,
438
+ vertexShader: r.vertexShader || q,
439
+ fragmentShader: r.fragmentShader || $
440
+ }), this.mesh = new a.Mesh(this.geometry, this.material), this.mesh.visible = this.isVisible, this.scene.add(this.mesh), this.init();
441
+ }
442
+ /**
443
+ * Phase B-read (DOM read): getBoundingClientRect 等のレイアウト読み取りのみを行う。
444
+ * Core.animate では paint 直前にまとめて呼ばれ、その直後に _tickApply が走る。
445
+ *
446
+ * **更新条件**: `updateRectEveryFrame: true` のときのみ毎フレ更新する。
447
+ * static 要素は document 座標で固定 / fixed 要素は viewport 座標で固定なので、
448
+ * 自身の CSS animation 等で動的に位置が変わるケースのみフラグを true に。
449
+ * (旧仕様では isFixed のとき自動的に毎フレ更新していたが、多くの場合不要な
450
+ * layout 強制になっていたため明示フラグ駆動に変更)
451
+ * @internal Core.animate から呼ばれる。
452
+ */
453
+ _tickRead() {
454
+ !this.isVisible || !this.positionCalculator || this.updateRectEveryFrame && this.positionCalculator.updatePositionInfo();
455
+ }
456
+ /**
457
+ * Phase B-apply: mesh.position と uniforms の書き込み。DOM には触れない。
458
+ * @internal Core.animate から呼ばれる。
459
+ */
460
+ _tickApply(e, t, i) {
461
+ this.isVisible && (this.material.uniforms.uTime.value = e, this.positionCalculator && this.setPosition(t, i));
462
+ }
463
+ /**
464
+ * Phase C (PlaneComposer render): main scene レンダリング前に
465
+ * 自前の plane をローカル FBO に焼いてエフェクトを通す。
466
+ * @internal Core.animate から呼ばれる。
467
+ */
468
+ _tickRenderComposer() {
469
+ this.isVisible && this.planeComposer?.render();
470
+ }
471
+ init() {
472
+ this.loadTexture(), this.resize();
473
+ }
474
+ loadTexture() {
475
+ const e = this.element?.getAttribute("data-texture");
476
+ if (e) {
477
+ let t;
478
+ this.crossOrigin !== void 0 ? (t = new a.TextureLoader(), t.setCrossOrigin(this.crossOrigin)) : t = S, t.load(
479
+ e,
480
+ (i) => {
481
+ if (this.destroyed) {
482
+ i.dispose();
483
+ return;
484
+ }
485
+ this.texture = i, this.ownsTexture = !0, this.material.uniforms.uTexture.value = i;
486
+ },
487
+ void 0,
488
+ (i) => {
489
+ console.error(`Failed to load texture: ${e}`, i);
490
+ }
491
+ );
492
+ } else this.material.uniforms.uTexture.value && (this.texture = this.material.uniforms.uTexture.value, this.ownsTexture = !1);
493
+ }
494
+ updateSize() {
495
+ if (this.positionCalculator) {
496
+ const e = this.positionCalculator.rect;
497
+ this.mesh.scale.set(e.width, e.height, 1), this.material.uniforms.uResolution.value.set(e.width, e.height);
498
+ } else
499
+ this.mesh.scale.set(this.canvasRect.width, this.canvasRect.height, 1), this.material.uniforms.uResolution.value.set(
500
+ this.canvasRect.width,
501
+ this.canvasRect.height
502
+ );
503
+ }
504
+ setPosition(e, t) {
505
+ if (!this.positionCalculator) return;
506
+ const { x: i, y: o } = this.positionCalculator.calculateWebGLPosition(
507
+ e,
508
+ t
509
+ );
510
+ this.mesh.position.set(i, o, 0);
511
+ }
512
+ setCanvasRect(e) {
513
+ this.canvasRect = e, this.positionCalculator && this.positionCalculator.setCanvasRect(e);
514
+ }
515
+ resize() {
516
+ if (this.positionCalculator) {
517
+ this.positionCalculator.refreshPositionType(), this.positionCalculator.updatePositionInfo();
518
+ const e = window.scrollY, t = window.scrollX;
519
+ this.updateSize(), this.setPosition(t, e);
520
+ } else
521
+ this.updateSize(), this.mesh.position.set(0, 0, 0);
522
+ if (this.planeComposer) {
523
+ const e = this.positionCalculator?.rect ?? this.canvasRect;
524
+ this.planeComposer.resize(e.width, e.height);
525
+ for (const t of this.effects)
526
+ t.resize?.(e.width, e.height);
527
+ }
528
+ }
529
+ getMesh() {
530
+ return this.mesh;
531
+ }
532
+ /**
533
+ * テクスチャを差し替える。
534
+ * 直接 `material.uniforms.uTexture.value = tex` する代わりに使うと、
535
+ * 旧テクスチャの所有権(自前 load かどうか)を考慮して安全に dispose してくれる。
536
+ *
537
+ * @param texture 新しい texture
538
+ * @param takeOwnership true なら destroy() 時にこの texture も dispose する。
539
+ * 他で使い回す texture を渡すときは false。
540
+ */
541
+ /**
542
+ * 現在の `data-texture` 属性を再読込してマテリアルに反映する。
543
+ * SPA で plane を貼ったまま画像 URL だけ差し替えたいケース用。
544
+ * 旧 texture が自前 load の場合は dispose する。属性が無ければ何もしない。
545
+ */
546
+ reloadTexture() {
547
+ this.texture && this.ownsTexture && (this.texture.dispose(), this.texture = null, this.material.uniforms.uTexture.value = null, this.ownsTexture = !1), this.loadTexture();
548
+ }
549
+ setTexture(e, t = !1) {
550
+ this.texture && this.ownsTexture && this.texture !== e && this.texture.dispose(), this.texture = e, this.ownsTexture = t, this.material.uniforms.uTexture.value = e;
551
+ }
552
+ /**
553
+ * 紐づけられたエフェクトに update を流す。Core.animate から呼ばれる。
554
+ *
555
+ * 引数 `globalMouse` は canvas 全体の UV(0〜1, Y-up)。これを **plane ローカルの UV** に変換して
556
+ * effect.update に渡す。これで FluidEffect 等が plane の中の座標として mouse を扱える。
557
+ *
558
+ * - フルスクリーン plane(element 無し): そのまま globalMouse を渡す
559
+ * - DOM 配置 plane: 自分の DOM rect と canvasRect から local UV を算出
560
+ * - マウスが plane の外にいる時は **last value を据え置き**(uv 更新せず)。
561
+ * これにより solver 側で delta が 0 になり force が立たない、自然な挙動になる。
562
+ *
563
+ * 注意: raycast には依存しない。PlaneComposer が sourceMesh を main scene から外すと
564
+ * raycast が思った通り当たらないケースがあるため。
565
+ *
566
+ * scrollX/Y は Core.animate が 1 rAF tick 上で 1 回確定したスナップショット値を渡す。
567
+ * ここで `window.scrollX/Y` を直接読むと
568
+ *(同一 frame で全コンポーネントが同じ scroll 値を共有する)を壊すため必ず引数経由。
569
+ * 後方互換のため省略可能だが、その場合は window から読む (= 古い挙動)。
570
+ */
571
+ updateEffects(e, t, i = window.scrollX, o = window.scrollY) {
572
+ if (this.effects.length === 0) return;
573
+ if (t)
574
+ if (this.positionCalculator) {
575
+ const l = this.positionCalculator, n = this.canvasRect, c = l.rect.width, u = l.rect.height;
576
+ if (n.width > 0 && n.height > 0 && c > 0 && u > 0) {
577
+ const f = l.isFixed ? l.pageLeft : l.pageLeft - i, p = l.isFixed ? l.pageTop : l.pageTop - o, T = (f - n.left) / n.width, E = (p - n.top) / n.height, I = c / n.width, _ = u / n.height, A = 1 - E - _, m = (t.x - T) / I, g = (t.y - A) / _;
578
+ m >= 0 && m <= 1 && g >= 0 && g <= 1 && this.material.uniforms.uMouseUV.value.set(m, g);
579
+ }
580
+ } else
581
+ this.material.uniforms.uMouseUV.value.copy(t);
582
+ const r = this.material.uniforms.uMouseUV.value;
583
+ this._effectMouseUV.copy(r);
584
+ const h = this.effects;
585
+ for (let l = 0, n = h.length; l < n; l++) {
586
+ const c = h[l];
587
+ c.enabled && c.update(e, this._effectMouseUV);
588
+ }
589
+ }
590
+ /** 直近の plane ローカル UV を取得(updateEffects 内で算出された値、0〜1)。 */
591
+ getMouseUV() {
592
+ return this.material.uniforms.uMouseUV.value;
593
+ }
594
+ /** マウスが現在 plane の上に乗っているかどうか。 */
595
+ isHovered() {
596
+ return this.material.uniforms.uIsHovered.value;
597
+ }
598
+ setHoverInfo(e, t) {
599
+ this.material.uniforms.uIsHovered.value = e, t && this.material.uniforms.uMouseUV.value.copy(t);
600
+ }
601
+ enableEffects() {
602
+ if (this.planeComposer) return this.planeComposer;
603
+ const e = this.positionCalculator?.rect ?? this.canvasRect;
604
+ return this.planeComposer = new N(
605
+ this.renderer,
606
+ this.mesh,
607
+ this.scene,
608
+ e.width,
609
+ e.height
610
+ ), this.planeComposer;
611
+ }
612
+ addEffect(e) {
613
+ const t = this.enableEffects();
614
+ e._setRenderer?.(this.renderer), e._register(t);
615
+ const i = this.positionCalculator?.rect ?? this.canvasRect;
616
+ return e.resize?.(i.width, i.height), this.guiProvider && e.setupGUI && this.guiProvider().then((o) => {
617
+ this.destroyed || e.setupGUI(o);
618
+ }).catch((o) => {
619
+ console.warn(
620
+ "[DomPlane] effect.setupGUI に渡す lil-gui の読み込みに失敗しました。npm install lil-gui してください。",
621
+ o
622
+ );
623
+ }), this.effects.push(e), e;
624
+ }
625
+ /**
626
+ * `addEffect()` で登録した effect を取り除き、pass material を dispose する。
627
+ * 登録されていない effect を渡した時は何もしない(戻り値 false)。
628
+ */
629
+ removeEffect(e) {
630
+ const t = this.effects.indexOf(e);
631
+ if (t < 0) return !1;
632
+ this.effects.splice(t, 1);
633
+ const i = e.getPass();
634
+ return i && this.planeComposer && this.planeComposer.removeEffect(i), e.dispose?.(), !0;
635
+ }
636
+ /**
637
+ * WebGLApp.createPlane() から呼ばれる。GUI lazy 取得関数を渡す。
638
+ * null を渡すと GUI 統合を無効化(showGUI: false 相当)。
639
+ * @internal
640
+ */
641
+ _setGuiProvider(e) {
642
+ this.guiProvider = e;
643
+ }
644
+ destroy() {
645
+ if (!this.destroyed) {
646
+ this.destroyed = !0, this.observer?.disconnect();
647
+ for (const e of this.effects)
648
+ e.dispose?.();
649
+ this.effects = [], this.planeComposer && (this.planeComposer.dispose(), this.planeComposer = null), this.scene.remove(this.mesh), this.geometry.dispose(), this.material.dispose(), this.texture && this.ownsTexture && this.texture.dispose(), this.texture = null;
650
+ }
651
+ }
652
+ }
653
+ class J {
654
+ constructor(e, t, i, o) {
655
+ s(this, "element");
656
+ s(this, "model");
657
+ s(this, "loader");
658
+ s(this, "mainScene");
659
+ s(this, "canvasRect");
660
+ s(this, "options");
661
+ s(this, "isVisible");
662
+ s(this, "positionCalculator");
663
+ s(this, "updateRectEveryFrame");
664
+ s(this, "observer");
665
+ s(this, "destroyed");
666
+ this.element = e, this.mainScene = t, this.canvasRect = i, this.model = null, this.loader = new Y(), this.options = {
667
+ scale: y,
668
+ // DEFAULT_OFFSET をそのまま参照すると、複数 Dom3DObject インスタンスが
669
+ // 同一オブジェクトを共有してしまう。誰かが `obj.options.offset.x = 5` で
670
+ // 書き換えると DEFAULT_OFFSET 自身が変わり、以後の全インスタンスに伝播する。
671
+ // インスタンスごとに copy して隔離する。
672
+ offset: { ...V },
673
+ ...o
674
+ }, this.updateRectEveryFrame = o.updateRectEveryFrame ?? !1, this.positionCalculator = e ? new P(e, i) : null, this.isVisible = !e, this.destroyed = !1, this.observer = null, e && (this.observer = new IntersectionObserver(
675
+ (r) => {
676
+ this.isVisible = r[0].isIntersecting, this.model && (this.model.visible = this.isVisible);
677
+ },
678
+ { rootMargin: "100%" }
679
+ ), this.observer.observe(e)), this.loadModel();
680
+ }
681
+ /**
682
+ * Phase B-read (DOM read): getBoundingClientRect のみ。
683
+ * Core.animate で paint 直前にまとめて呼ばれる。
684
+ * element=null の場合は何もしない(scene 原点固定で DOM 追従不要)。
685
+ *
686
+ * **更新条件**: `updateRectEveryFrame: true` のときのみ毎フレ更新。
687
+ * 自身の CSS animation で動的に位置が変わるケースのみ true に。
688
+ * (旧仕様では isFixed で自動毎フレ更新していたが、多くの場合不要な layout 強制
689
+ * になっていたため明示フラグ駆動に変更)
690
+ * @internal Core.animate から呼ばれる。
691
+ */
692
+ _tickRead() {
693
+ !this.model || !this.isVisible || !this.positionCalculator || this.updateRectEveryFrame && this.positionCalculator.updatePositionInfo();
694
+ }
695
+ /**
696
+ * Phase B-apply: model.position の書き込み。
697
+ * DOM あり/なしどちらでも `setPosition` を呼ぶ。setPosition 内部で positionCalculator の
698
+ * null を扱うため、DOM なしは offset のみ反映される(後から options.offset を書き換えても
699
+ * 毎フレ反映される一貫性を担保)。
700
+ * @internal Core.animate から呼ばれる。
701
+ */
702
+ _tickApply(e, t) {
703
+ !this.model || !this.isVisible || this.setPosition(e, t);
704
+ }
705
+ loadModel() {
706
+ const e = this.options.modelPath;
707
+ if (!e) {
708
+ console.error("Dom3DObject: modelPath が指定されていません。");
709
+ return;
710
+ }
711
+ this.loader.load(
712
+ e,
713
+ (t) => {
714
+ this.destroyed || (this.model = t.scene, this.setupModel());
715
+ },
716
+ void 0,
717
+ // GLTFLoader の onError は ProgressEvent / ErrorEvent / Error など複数型を渡してくる。
718
+ // ErrorEvent 固定で受けると `.message` が undefined のケースで static 型上の嘘になる。
719
+ (t) => {
720
+ console.error(`Failed to load model: ${e}`, t);
721
+ }
722
+ );
723
+ }
724
+ setupModel() {
725
+ if (this.model) {
726
+ if (this.positionCalculator) {
727
+ this.positionCalculator.refreshPositionType(), this.positionCalculator.updatePositionInfo();
728
+ const e = new a.Box3().setFromObject(this.model), t = new a.Vector3(), i = new a.Vector3();
729
+ e.getSize(t), e.getCenter(i);
730
+ const o = Math.max(t.x, t.y, t.z) || 1, r = this.model;
731
+ r.position.sub(i).multiplyScalar(1 / o), r.scale.multiplyScalar(1 / o);
732
+ const h = new a.Group();
733
+ h.add(r), this.model = h;
734
+ }
735
+ this.model.visible = this.isVisible, this.mainScene.add(this.model), this.applyScale(), this.setPosition(window.scrollX, window.scrollY);
736
+ }
737
+ }
738
+ applyScale() {
739
+ if (!this.model) return;
740
+ const e = this.options.scale ?? y;
741
+ if (this.positionCalculator) {
742
+ const { width: t, height: i } = this.positionCalculator.rect, h = ((this.options.fitMode ?? "maxSide") === "contain" ? Math.min(t, i) : Math.max(t, i)) * e;
743
+ this.model.scale.set(h, h, h);
744
+ } else
745
+ this.model.scale.set(e, e, e);
746
+ }
747
+ setPosition(e, t) {
748
+ if (!this.model) return;
749
+ const i = this.options.offset;
750
+ let o = i.x, r = i.y;
751
+ const h = i.z;
752
+ if (this.positionCalculator) {
753
+ const l = this.positionCalculator.calculateWebGLPosition(e, t);
754
+ o += l.x, r += l.y;
755
+ }
756
+ this.model.position.set(o, r, h);
757
+ }
758
+ setCanvasRect(e) {
759
+ this.canvasRect = e, this.positionCalculator?.setCanvasRect(e);
760
+ }
761
+ resize() {
762
+ if (!this.positionCalculator) {
763
+ this.applyScale();
764
+ return;
765
+ }
766
+ this.positionCalculator.refreshPositionType(), this.positionCalculator.updatePositionInfo(), this.applyScale(), this.setPosition(window.scrollX, window.scrollY);
767
+ }
768
+ getModel() {
769
+ return this.model;
770
+ }
771
+ destroy() {
772
+ this.destroyed || (this.destroyed = !0, this.observer?.disconnect(), this.model && (this.mainScene.remove(this.model), this.model.traverse((e) => {
773
+ if (!e.isMesh) return;
774
+ const t = e;
775
+ t.geometry.dispose();
776
+ const i = Array.isArray(t.material) ? t.material : [t.material];
777
+ for (const o of i)
778
+ o && (Q(o), o.dispose());
779
+ }), this.model = null));
780
+ }
781
+ }
782
+ function Q(d) {
783
+ const e = [
784
+ "map",
785
+ "alphaMap",
786
+ "aoMap",
787
+ "bumpMap",
788
+ "displacementMap",
789
+ "emissiveMap",
790
+ "envMap",
791
+ "lightMap",
792
+ "metalnessMap",
793
+ "normalMap",
794
+ "roughnessMap",
795
+ "specularMap",
796
+ "clearcoatMap",
797
+ "clearcoatNormalMap",
798
+ "clearcoatRoughnessMap",
799
+ "sheenColorMap",
800
+ "sheenRoughnessMap",
801
+ "transmissionMap",
802
+ "thicknessMap",
803
+ "iridescenceMap",
804
+ "iridescenceThicknessMap",
805
+ "anisotropyMap",
806
+ "matcap",
807
+ "gradientMap"
808
+ ], t = d;
809
+ for (const i of e) {
810
+ const o = t[i];
811
+ o && o.isTexture && o.dispose();
812
+ }
813
+ }
814
+ class Z {
815
+ constructor(e, t = {}) {
816
+ s(this, "container");
817
+ s(this, "_padding");
818
+ s(this, "_trackStrength");
819
+ s(this, "_strengthDecay");
820
+ s(this, "_strength", 0);
821
+ s(this, "_prevScrollY", 0);
822
+ s(this, "_prevTime", 0);
823
+ s(this, "_viewportWidth", 0);
824
+ s(this, "_viewportHeight", 0);
825
+ s(this, "_enabled", !0);
826
+ /**
827
+ * container の **元 CSS 指定** を window に対する比率として保持する。
828
+ *
829
+ * 例: container の CSS が `width: 100vw; height: 110vh` なら
830
+ * _widthRatio = 1.0, _heightRatio = 1.1
831
+ *
832
+ * `applyContainerStyles()` で container は `position: absolute; width/height = JS制御`
833
+ * に書き換えられて以降「元の CSS による intrinsic size」は測れなくなる。よって
834
+ * **constructor 進入時** に getBoundingClientRect から ratio を snapshot し、以降の
835
+ * resize ではこの ratio を window 寸法に掛けて canvas 物理サイズを決める。
836
+ *
837
+ * これにより `#canvas { width: 100vw; height: 110vh; }` のような CSS が
838
+ * window resize / orientation 変更後も「100vw × 110vh」相当を保ち続ける。
839
+ */
840
+ s(this, "_widthRatio", 1);
841
+ s(this, "_heightRatio", 1);
842
+ /**
843
+ * 現在 transform / canvas 寸法に反映している padding 値(px)。
844
+ * クランプの出力。`_padding * viewportHeight` の上限と、document の余り
845
+ * 領域の最小から決まる。
846
+ */
847
+ s(this, "_effectivePaddingPx", 0);
848
+ /** 計算用の canvas rect(scroll transform を含まない論理 rect、effective padding 基準) */
849
+ s(this, "_logicalRect", new DOMRect());
850
+ /**
851
+ * effective padding が変わって canvas drawing buffer の resize が必要な時に呼ぶ callback。
852
+ * 引数は `{ width, height }`(drawing buffer の新サイズ = container の新サイズ)。
853
+ * Core 側でこれに `renderer.setSize` / `camera.resize` / `postEffect.resize` をぶら下げる。
854
+ */
855
+ s(this, "_onResize", null);
856
+ /**
857
+ * destroy 時の復元用に、constructor 進入時の inline style を退避しておく。
858
+ * ユーザーが先に `container.style.position = 'relative'` などを当てていた場合に、
859
+ * destroy で「空文字に潰す」ではなく元の値に戻すために必要。
860
+ */
861
+ s(this, "_originalStyles");
862
+ s(this, "_warnedStrength", !1);
863
+ this.container = e, this._padding = t.padding ?? 0, this._trackStrength = t.trackStrength ?? !1, this._strengthDecay = t.strengthDecay ?? 10, this._prevScrollY = window.scrollY, this._prevTime = performance.now() / 1e3;
864
+ const i = e.style;
865
+ this._originalStyles = {
866
+ position: i.position,
867
+ left: i.left,
868
+ top: i.top,
869
+ width: i.width,
870
+ height: i.height,
871
+ overflow: i.overflow,
872
+ transform: i.transform,
873
+ pointerEvents: i.pointerEvents,
874
+ willChange: i.willChange
875
+ };
876
+ const o = e.getBoundingClientRect();
877
+ window.innerWidth > 0 && o.width > 0 && (this._widthRatio = o.width / window.innerWidth), window.innerHeight > 0 && o.height > 0 && (this._heightRatio = o.height / window.innerHeight), this.applyContainerStyles(), this.updateSize();
878
+ }
879
+ applyContainerStyles() {
880
+ this.container.style.position = "absolute", this.container.style.left = "0", this.container.style.top = "0", this.container.style.overflow = "hidden", this.container.style.pointerEvents = "none", this.container.style.willChange = "transform";
881
+ }
882
+ /**
883
+ * ビューポートサイズが変わった時に呼ぶ。effective padding は requested で初期化し、
884
+ * 次の update() 呼び出しでクランプが効く。
885
+ *
886
+ * 引数を省略した場合は、constructor で snapshot した container の CSS ratio を
887
+ * window 寸法に掛けて自動算出する。明示的に値を渡せば override 可能(scrollbar を
888
+ * 差し引いた幅にしたい等のケース)。
889
+ */
890
+ updateSize(e, t) {
891
+ this._viewportWidth = e ?? window.innerWidth * this._widthRatio, this._viewportHeight = t ?? window.innerHeight * this._heightRatio;
892
+ const i = this._viewportHeight * this._padding;
893
+ this._effectivePaddingPx = i;
894
+ const o = this._viewportHeight + 2 * i;
895
+ this.container.style.width = `${this._viewportWidth}px`, this.container.style.height = `${o}px`, this._logicalRect = new DOMRect(
896
+ 0,
897
+ -i,
898
+ this._viewportWidth,
899
+ o
900
+ ), this._onResize?.({ width: this._viewportWidth, height: o }), this.applyTransform(window.scrollX, window.scrollY);
901
+ }
902
+ /**
903
+ * 毎 rAF で呼ぶ。**plane の sceneY 計算と同一の scrollX/Y を渡すこと**。
904
+ * これが cancel の不変条件。
905
+ *
906
+ * padding clamp:
907
+ * maxPaddingY = body.scrollHeight - scrollY - viewportHeight
908
+ * effective = clamp(requested, 0, maxPaddingY)
909
+ * effective が変わったら canvas drawing buffer も同期して resize する。
910
+ */
911
+ update(e, t) {
912
+ if (!this._enabled) return;
913
+ const i = this._viewportHeight * this._padding, o = Math.max(
914
+ 0,
915
+ document.body.scrollHeight - t - this._viewportHeight
916
+ ), r = Math.min(i, o);
917
+ if (r !== this._effectivePaddingPx) {
918
+ this._effectivePaddingPx = r;
919
+ const h = this._viewportHeight + 2 * r;
920
+ this.container.style.height = `${h}px`, this._logicalRect = new DOMRect(
921
+ 0,
922
+ -r,
923
+ this._viewportWidth,
924
+ h
925
+ ), this._onResize?.({ width: this._viewportWidth, height: h });
926
+ }
927
+ this.applyTransform(e, t), this._trackStrength && this.updateStrength(t);
928
+ }
929
+ applyTransform(e, t) {
930
+ this.container.style.transform = `translate3d(${e}px, ${t - this._effectivePaddingPx}px, 0)`;
931
+ }
932
+ /** スクロール速度ベースの strength 値を更新。 */
933
+ updateStrength(e) {
934
+ const t = e - this._prevScrollY, i = performance.now() / 1e3, o = i - this._prevTime;
935
+ if (o > 0) {
936
+ const r = Math.abs(t) * 10 / this._viewportHeight;
937
+ this._strength *= Math.exp(-o * this._strengthDecay), this._strength += Math.min(r, 5);
938
+ }
939
+ this._prevScrollY = e, this._prevTime = i;
940
+ }
941
+ /**
942
+ * canvas drawing buffer の resize が必要な時に呼ばれる callback を登録する。
943
+ * @internal Core 側で renderer.setSize / camera.resize / postEffect.resize を繋ぐ。
944
+ */
945
+ setResizeCallback(e) {
946
+ this._onResize = e;
947
+ }
948
+ /**
949
+ * DomPositionCalculator 用の **論理** rect。
950
+ *
951
+ * **注意**: これは「ブラウザ上での canvas の getBoundingClientRect」ではない。
952
+ * `top = -effectivePadding`, `height = viewport + 2 * effectivePadding` で、
953
+ * scroll に追従して動く container 内のローカル座標系を表す。effective padding は
954
+ * 末尾近くで自動的に縮む。
955
+ */
956
+ get logicalRect() {
957
+ return this._logicalRect;
958
+ }
959
+ /** 現在の effective padding(px)。テスト・デバッグ用。 */
960
+ get effectivePadding() {
961
+ return this._effectivePaddingPx;
962
+ }
963
+ /**
964
+ * 現在のスクロール速度(0〜1 にクランプ)。
965
+ *
966
+ * **`trackStrength: false` で構築している場合は常に 0 を返す**。値を使いたい場合は
967
+ * options で trackStrength を true にして構築すること。DEV では誤用防止のため
968
+ * 1 度だけ console.warn を出す。
969
+ */
970
+ get strength() {
971
+ return this._trackStrength ? Math.min(1, this._strength) : 0;
972
+ }
973
+ get padding() {
974
+ return this._padding;
975
+ }
976
+ /**
977
+ * 入力受付の有効/無効。
978
+ *
979
+ * disable 中は `update()` が no-op になり container の transform は触らない(disable 直前の
980
+ * 位置で固定される)。enable に戻したフレから transform 更新が再開する。
981
+ *
982
+ * disable で「container を素の状態に戻したい」場合は `destroy()` を呼ぶこと。
983
+ */
984
+ set enabled(e) {
985
+ this._enabled = e;
986
+ }
987
+ get enabled() {
988
+ return this._enabled;
989
+ }
990
+ destroy() {
991
+ const e = this._originalStyles, t = this.container.style;
992
+ t.position = e.position, t.left = e.left, t.top = e.top, t.width = e.width, t.height = e.height, t.overflow = e.overflow, t.transform = e.transform, t.pointerEvents = e.pointerEvents, t.willChange = e.willChange;
993
+ }
994
+ }
995
+ class oe {
996
+ constructor(e, t = {}) {
997
+ s(this, "container");
998
+ s(this, "canvas");
999
+ s(this, "scene");
1000
+ s(this, "renderer");
1001
+ s(this, "camera");
1002
+ s(this, "light");
1003
+ s(this, "controls");
1004
+ s(this, "updateCallbacks");
1005
+ s(this, "resizeCallbacks");
1006
+ s(this, "rect");
1007
+ s(this, "domPlanes");
1008
+ s(this, "dom3DObjects");
1009
+ s(this, "clock");
1010
+ s(this, "scrollSync", null);
1011
+ s(this, "options");
1012
+ s(this, "rafId", 0);
1013
+ s(this, "resizeTimer", null);
1014
+ s(this, "eventAbort", new AbortController());
1015
+ /**
1016
+ * mousemove listener 専用の AbortController。`setMouseTrackingEnabled()` で動的に
1017
+ * detach 可能にするため `eventAbort` とは別に持つ。null の時は attach 済みでない。
1018
+ */
1019
+ s(this, "_mouseAbort", null);
1020
+ /**
1021
+ * canvas の viewport 上の矩形をキャッシュ。mousemove ごとに `getBoundingClientRect`
1022
+ * を呼ぶと layout 強制が走るため、resize 時 + (ScrollSync 無効時の) scroll 時に
1023
+ * invalidate する形にしてフィールドアクセスで済むようにする。
1024
+ * ScrollSync 有効時は canvas viewport rect が固定 (RafScroll が同 rAF tick で
1025
+ * scrollTo するため paint 時 actual と JS rAF が一致 → 動かない) なので
1026
+ * scroll 時の invalidate は不要。
1027
+ */
1028
+ s(this, "_canvasRect", null);
1029
+ s(this, "mouse");
1030
+ s(this, "prevMouse");
1031
+ /**
1032
+ * マウスが canvas 矩形の内側に居るか。mousemove イベントごとに更新し、
1033
+ * rAF tick 内の raycaster はこのフラグで実行を決める。
1034
+ * (uMouseUV の更新を完全に rAF 駆動にすることで paint と同期させ、
1035
+ * 「mousemove 非同期発火による uniform のちらつき」を消す)
1036
+ */
1037
+ s(this, "_mouseInside", !1);
1038
+ /** raycaster で使う NDC バッファ (毎フレ allocate を避ける) */
1039
+ s(this, "_ndcBuf", new a.Vector2());
1040
+ /** getMouseDelta の戻り値スクラッチ (毎フレ clone を避ける) */
1041
+ s(this, "_mouseDeltaBuf", new a.Vector2());
1042
+ s(this, "raycaster");
1043
+ s(this, "hoveredPlane");
1044
+ s(this, "domPlaneMeshes", []);
1045
+ s(this, "postEffect", null);
1046
+ s(this, "internalComposer", null);
1047
+ s(this, "effects", []);
1048
+ s(this, "stats", null);
1049
+ /**
1050
+ * lil-gui の root インスタンス。
1051
+ * showGUI が false なら null のまま。最初の `setupGUI` を持つ effect が
1052
+ * `addEffect()` 経由で登録された瞬間に生成する(lazy + dynamic import)。
1053
+ */
1054
+ s(this, "gui", null);
1055
+ /**
1056
+ * lil-gui の dynamic import promise。複数 effect の addEffect が同時に来た時、
1057
+ * 二重 load を防ぐ。一度 resolve したら次回以降は `this.gui` を直接返す。
1058
+ */
1059
+ s(this, "_guiLoadPromise", null);
1060
+ /**
1061
+ * destroy 済みフラグ。animate() 実行中に user callback から destroy() が
1062
+ * 呼ばれると、renderer.dispose() 後の続きで renderer.render() を呼んで
1063
+ * WebGL エラーになる。各段階の冒頭で見て早期 return する。
1064
+ */
1065
+ s(this, "destroyed", !1);
1066
+ s(this, "invalidateCanvasRect", () => {
1067
+ this._canvasRect = null;
1068
+ });
1069
+ /**
1070
+ * ScrollSync の動的 padding clamp で canvas 寸法が変わった時に呼ばれる。
1071
+ *
1072
+ * window resize(onResize)と違い、DOM の位置や element 側の bbox は変わっていない。
1073
+ * 必要なのは「canvas drawing buffer のサイズに依存するもの」だけ:
1074
+ * - renderer.setSize(drawing buffer + canvas.style.{width,height})
1075
+ * - camera の aspect / distance(rect.height に応じて変わる)
1076
+ * - postEffect の RT サイズ
1077
+ * - 各 effect の RT サイズ
1078
+ *
1079
+ * DomPlane の DOM 連動 plane は positionCalculator の bbox に依存していて canvas 高さに
1080
+ * 依存しないので触らない(calculateWebGLPosition の数式が padding を相殺するため不変)。
1081
+ * フルスクリーン plane(element=null)は canvasRect.width/height をスケールに使うので、
1082
+ * その分だけ canvasRect 参照を更新する。
1083
+ */
1084
+ s(this, "_onScrollSyncResize", () => {
1085
+ if (this.destroyed || !this.scrollSync || (this.rect = this.scrollSync.logicalRect, this.rect.width <= 0 || this.rect.height <= 0)) return;
1086
+ this.camera.resize(this.rect), this.renderer.setSize(this.rect.width, this.rect.height);
1087
+ for (let t = 0, i = this.domPlanes.length; t < i; t++) {
1088
+ const o = this.domPlanes[t];
1089
+ o.setCanvasRect(this.rect), o.element || o.resize();
1090
+ }
1091
+ for (let t = 0, i = this.dom3DObjects.length; t < i; t++)
1092
+ this.dom3DObjects[t].setCanvasRect(this.rect);
1093
+ this.postEffect?.resize(this.rect.width, this.rect.height);
1094
+ const e = this.effects;
1095
+ for (let t = 0, i = e.length; t < i; t++)
1096
+ e[t].resize?.(this.rect.width, this.rect.height);
1097
+ });
1098
+ s(this, "animate", () => {
1099
+ if (this.destroyed) return;
1100
+ this.rafId = requestAnimationFrame(this.animate), this.stats?.begin(), this.controls && this.controls.update();
1101
+ const e = this.domPlanes, t = this.dom3DObjects, i = window.scrollX, o = window.scrollY;
1102
+ this._updateHoverFromMouse();
1103
+ const r = this.updateCallbacks;
1104
+ for (let n = 0, c = r.length; n < c; n++)
1105
+ r[n]();
1106
+ const h = this.clock.getElapsedTime(), l = this.effects;
1107
+ for (let n = 0, c = l.length; n < c; n++) {
1108
+ const u = l[n];
1109
+ u.enabled && u.update(h, this.mouse);
1110
+ }
1111
+ this.scrollSync?.update(i, o);
1112
+ for (let n = 0, c = e.length; n < c; n++)
1113
+ e[n]._tickRead();
1114
+ for (let n = 0, c = t.length; n < c; n++)
1115
+ t[n]._tickRead();
1116
+ for (let n = 0, c = e.length; n < c; n++)
1117
+ e[n].updateEffects(h, this.mouse, i, o);
1118
+ for (let n = 0, c = e.length; n < c; n++)
1119
+ e[n]._tickApply(h, i, o);
1120
+ for (let n = 0, c = t.length; n < c; n++)
1121
+ t[n]._tickApply(i, o);
1122
+ for (let n = 0, c = e.length; n < c; n++)
1123
+ e[n]._tickRenderComposer();
1124
+ if (this.destroyed) {
1125
+ this.stats?.end();
1126
+ return;
1127
+ }
1128
+ this.postEffect ? this.postEffect.render(this.scene, this.camera.instance) : this.renderer.render(this.scene, this.camera.instance), this.prevMouse.copy(this.mouse), this.stats?.end();
1129
+ });
1130
+ const i = typeof e == "string" ? document.querySelector(e) : e;
1131
+ if (!i)
1132
+ throw new Error(`Container not found: ${e}`);
1133
+ if (this.container = i, this.canvas = document.createElement("canvas"), this.container.appendChild(this.canvas), this.rect = this.container.getBoundingClientRect(), this.scene = new a.Scene(), this.renderer = new a.WebGLRenderer({
1134
+ canvas: this.canvas,
1135
+ antialias: !0,
1136
+ alpha: !0
1137
+ }), this.camera = new C(this.rect), this.light = new B(this.scene), this.controls = null, this.updateCallbacks = [], this.resizeCallbacks = [], this.domPlanes = [], this.dom3DObjects = [], this.clock = new a.Clock(), this.options = { enableMouseTracking: !0, showGUI: !0, ...t }, this.mouse = new a.Vector2(0.5, 0.5), this.prevMouse = new a.Vector2(0.5, 0.5), this.raycaster = new a.Raycaster(), this.hoveredPlane = null, t.scrollSync) {
1138
+ const o = typeof t.scrollSync == "object" ? t.scrollSync : {};
1139
+ this.scrollSync = new Z(this.container, o), this.rect = this.scrollSync.logicalRect, this.scrollSync.setResizeCallback(() => this._onScrollSyncResize());
1140
+ }
1141
+ this.init(), t.showStats && import("stats.js").then(({ default: o }) => {
1142
+ this.destroyed || (this.stats = new o(), this.stats.showPanel(0), (t.statsParent ?? document.body).appendChild(this.stats.dom));
1143
+ }).catch((o) => {
1144
+ console.warn(
1145
+ "[WebGLApp] showStats: true ですが stats.js が読み込めませんでした。npm install stats.js してください。",
1146
+ o
1147
+ );
1148
+ }), this.setupEventListeners(), this.animate();
1149
+ }
1150
+ init() {
1151
+ this.scene.background = null, this.renderer.setSize(this.rect.width, this.rect.height), this.renderer.setPixelRatio(
1152
+ Math.min(window.devicePixelRatio, this.options.maxPixelRatio ?? 2)
1153
+ ), this.camera.resize(this.rect), this.renderer.outputColorSpace = this.options.outputColorSpace ?? a.SRGBColorSpace;
1154
+ }
1155
+ // sceneのgetterを追加
1156
+ getScene() {
1157
+ return this.scene;
1158
+ }
1159
+ // カメラのgetterを追加
1160
+ getCamera() {
1161
+ return this.camera;
1162
+ }
1163
+ // rendererのgetterを追加
1164
+ getRenderer() {
1165
+ return this.renderer;
1166
+ }
1167
+ // lightのgetterを追加
1168
+ getLight() {
1169
+ return this.light;
1170
+ }
1171
+ // canvasサイズを追加
1172
+ getViewPort() {
1173
+ return this.rect;
1174
+ }
1175
+ // マウス座標を取得(UV座標: 0~1)
1176
+ getMouse() {
1177
+ return this.mouse;
1178
+ }
1179
+ // 前回のマウス座標を取得(UV座標: 0~1)
1180
+ getPrevMouse() {
1181
+ return this.prevMouse;
1182
+ }
1183
+ // マウス移動量を取得。内部スクラッチを使い回すので、保持したい場合は呼び出し側で clone する。
1184
+ getMouseDelta() {
1185
+ return this._mouseDeltaBuf.copy(this.mouse).sub(this.prevMouse);
1186
+ }
1187
+ // ScrollSyncを取得
1188
+ getScrollSync() {
1189
+ return this.scrollSync;
1190
+ }
1191
+ // オブジェクトを追加するメソッド
1192
+ addObject(e) {
1193
+ this.scene.add(e);
1194
+ }
1195
+ // オブジェクトを削除するメソッド
1196
+ removeObject(e) {
1197
+ this.scene.remove(e);
1198
+ }
1199
+ // DomPlaneを作成するメソッド
1200
+ createPlane(e, t) {
1201
+ if (this.destroyed)
1202
+ throw new Error("[WebGLApp] createPlane(): destroy 済みのインスタンスでは使えません。");
1203
+ let i = null;
1204
+ if (e != null && (i = typeof e == "string" ? document.querySelector(e) : e, !i))
1205
+ throw new Error(`Element not found: ${e}`);
1206
+ const o = new K(
1207
+ i,
1208
+ this.scene,
1209
+ this.rect,
1210
+ this.renderer,
1211
+ t,
1212
+ this.clock
1213
+ );
1214
+ return this.options.showGUI !== !1 && o._setGuiProvider(() => this._ensureGUIAsync()), this.domPlanes.push(o), i && this.domPlaneMeshes.push(o.getMesh()), o;
1215
+ }
1216
+ // DomPlaneを削除するメソッド
1217
+ removePlane(e) {
1218
+ if (this.destroyed) return;
1219
+ const t = this.domPlanes.indexOf(e);
1220
+ if (t > -1) {
1221
+ this.domPlanes.splice(t, 1);
1222
+ const i = this.domPlaneMeshes.indexOf(e.getMesh());
1223
+ i > -1 && this.domPlaneMeshes.splice(i, 1), e.destroy();
1224
+ }
1225
+ }
1226
+ // Dom3DObject を作成する。
1227
+ // - selector あり (string / HTMLElement): DOM 要素位置に追従。
1228
+ // - selector が null/undefined: scene 原点に固定 (viewport 中心固定の使い方)。
1229
+ // ScrollSync 有効時でも、Lenis のような virtual scroll で JS rAF と paint の
1230
+ // scrollY が同一に揃っていれば container は viewport に完璧固定され、scene 原点
1231
+ // 固定 obj も視覚的に viewport 固定として機能する。
1232
+ create3DObject(e, t) {
1233
+ if (this.destroyed)
1234
+ throw new Error("[WebGLApp] create3DObject(): destroy 済みのインスタンスでは使えません。");
1235
+ let i = null;
1236
+ if (e != null && (i = typeof e == "string" ? document.querySelector(e) : e, !i))
1237
+ throw new Error(`Element not found: ${e}`);
1238
+ const o = new J(
1239
+ i,
1240
+ this.scene,
1241
+ this.rect,
1242
+ t
1243
+ );
1244
+ return this.dom3DObjects.push(o), o;
1245
+ }
1246
+ // Dom3DObjectを削除するメソッド
1247
+ remove3DObject(e) {
1248
+ if (this.destroyed) return;
1249
+ const t = this.dom3DObjects.indexOf(e);
1250
+ t > -1 && (this.dom3DObjects.splice(t, 1), e.destroy());
1251
+ }
1252
+ // アニメーションループに更新処理を追加するメソッド(戻り値で削除可能)
1253
+ addUpdateCallback(e) {
1254
+ return this.destroyed ? () => {
1255
+ } : (this.updateCallbacks.push(e), () => {
1256
+ const t = this.updateCallbacks.indexOf(e);
1257
+ t > -1 && this.updateCallbacks.splice(t, 1);
1258
+ });
1259
+ }
1260
+ // リサイズ時の処理を追加するメソッド(戻り値で削除可能)
1261
+ addResizeCallback(e) {
1262
+ return this.destroyed ? () => {
1263
+ } : (this.resizeCallbacks.push(e), () => {
1264
+ const t = this.resizeCallbacks.indexOf(e);
1265
+ t > -1 && this.resizeCallbacks.splice(t, 1);
1266
+ });
1267
+ }
1268
+ // OrbitControlsを有効化するメソッド
1269
+ enableOrbitControls() {
1270
+ if (this.destroyed)
1271
+ throw new Error("[WebGLApp] enableOrbitControls(): destroy 済みのインスタンスでは使えません。");
1272
+ return this.controls || (this.scrollSync && (this.canvas.style.pointerEvents = "auto"), this.controls = new O(this.camera.instance, this.canvas)), this.controls;
1273
+ }
1274
+ // OrbitControlsを取得するメソッド
1275
+ getControls() {
1276
+ return this.controls;
1277
+ }
1278
+ /**
1279
+ * canvas 全体にエフェクトを追加する。
1280
+ * 内部で EffectComposer を自動生成するため、別途 setPostEffect は不要。
1281
+ *
1282
+ * **注意**: `setPostEffect()` でカスタム postEffect を入れている状態でこれを呼ぶと、
1283
+ * 自動生成された EffectComposer で上書きされる(カスタム postEffect の dispose は
1284
+ * 呼ばれない=呼び出し側の責務)。両 API の併用は避けること。
1285
+ */
1286
+ addEffect(e) {
1287
+ if (this.destroyed)
1288
+ throw new Error("[WebGLApp] addEffect(): destroy 済みのインスタンスでは使えません。");
1289
+ return this.postEffect && this.postEffect !== this.internalComposer && console.warn("[WebGLApp] addEffect() を呼ぶ前に setPostEffect() でカスタム postEffect が設定されています。内部 EffectComposer で上書きします。カスタム postEffect は手動で dispose してください。"), this.internalComposer || (this.internalComposer = new j(
1290
+ this.renderer,
1291
+ this.rect.width,
1292
+ this.rect.height
1293
+ ), this.postEffect = this.internalComposer), e._setRenderer?.(this.renderer), e._register(this.internalComposer), e.resize?.(this.rect.width, this.rect.height), this.options.showGUI && e.setupGUI && this._ensureGUIAsync().then((t) => {
1294
+ this.destroyed || e.setupGUI(t);
1295
+ }).catch((t) => {
1296
+ console.warn(
1297
+ "[WebGLApp] showGUI: true ですが lil-gui が読み込めませんでした。npm install lil-gui してください。",
1298
+ t
1299
+ );
1300
+ }), this.effects.push(e), e;
1301
+ }
1302
+ /**
1303
+ * lil-gui を dynamic import で読み込み、root インスタンスを lazy 生成して返す。
1304
+ * 同時に複数 effect から呼ばれても load promise を共有して 1 インスタンスにまとめる。
1305
+ * @internal DomPlane.addEffect / WebGLApp.addEffect から呼ばれる。
1306
+ */
1307
+ _ensureGUIAsync() {
1308
+ return this.gui ? Promise.resolve(this.gui) : (this._guiLoadPromise || (this._guiLoadPromise = import("lil-gui").then(({ default: e }) => (this.gui || (this.gui = new e({ title: this.options.guiTitle ?? "Effects" })), this.gui))), this._guiLoadPromise);
1309
+ }
1310
+ /**
1311
+ * root の lil-gui インスタンスを取得 (sync)。
1312
+ *
1313
+ * **注意**: lil-gui は dynamic import で読み込むため、初回 effect 登録直後など
1314
+ * load 中の段階では `null` を返す。確実にインスタンスを得たい場合は
1315
+ * `getGUIAsync()` を使う。`showGUI: false` の場合は常に `null`。
1316
+ */
1317
+ getGUI() {
1318
+ return this.options.showGUI === !1 ? null : this.gui;
1319
+ }
1320
+ /**
1321
+ * lil-gui を必要に応じて load し、インスタンスを返す。
1322
+ * `showGUI: false` の場合は `null` を resolve する。
1323
+ */
1324
+ getGUIAsync() {
1325
+ return this.options.showGUI === !1 ? Promise.resolve(null) : this._ensureGUIAsync();
1326
+ }
1327
+ /**
1328
+ * ポストエフェクトを設定(低レベル API)。
1329
+ * 自前で `EffectLike`(render/resize/dispose)を実装したオブジェクトを差し込みたい場合のみ使用。
1330
+ * 通常は `addEffect()` を使うこと。
1331
+ *
1332
+ * **注意**: `addEffect()` で追加済みのエフェクトがある状態で呼ぶと、
1333
+ * 自動 EffectComposer を捨てて引数の postEffect に差し替える。既存 effect の
1334
+ * dispose は呼ばれない(必要なら先に `clearEffects()` を呼ぶこと)。
1335
+ */
1336
+ setPostEffect(e) {
1337
+ if (this.destroyed)
1338
+ throw new Error("[WebGLApp] setPostEffect(): destroy 済みのインスタンスでは使えません。");
1339
+ this.effects.length > 0 && (console.warn("[WebGLApp] setPostEffect() が呼ばれましたが、addEffect() で追加した effect が既に存在します。内部 EffectComposer を破棄してカスタム postEffect に差し替えます。事前に clearEffects() を呼ぶことを推奨します。"), this.clearEffects()), this.postEffect = e;
1340
+ }
1341
+ /**
1342
+ * `addEffect()` で登録した effect を 1 つ取り除く。
1343
+ *
1344
+ * - 内部 EffectComposer から該当 pass を外し、material を dispose する
1345
+ * - effect 自体の `dispose()` も呼ぶ(FluidEffect 等の RT も解放)
1346
+ * - 全 effect が空になった場合、internalComposer はそのまま残す(次の addEffect で再利用)
1347
+ *
1348
+ * 登録されていない effect を渡した時は `false` を返して何もしない。
1349
+ */
1350
+ removeEffect(e) {
1351
+ if (this.destroyed) return !1;
1352
+ const t = this.effects.indexOf(e);
1353
+ if (t < 0) return !1;
1354
+ this.effects.splice(t, 1);
1355
+ const i = e.getPass();
1356
+ return i && this.internalComposer && this.internalComposer.removeEffect(i), e.dispose?.(), !0;
1357
+ }
1358
+ /**
1359
+ * 登録されたエフェクトとポストエフェクトをすべて解除して破棄する。
1360
+ * addEffect で追加した全 effect の dispose() を呼び、内部 EffectComposer も解放する。
1361
+ */
1362
+ clearEffects() {
1363
+ if (!this.destroyed) {
1364
+ for (const e of this.effects)
1365
+ e.dispose?.();
1366
+ this.effects = [], this.postEffect?.dispose(), this.postEffect = null, this.internalComposer = null;
1367
+ }
1368
+ }
1369
+ /**
1370
+ * @deprecated `clearEffects()` を使ってください。挙動は同一です。
1371
+ */
1372
+ removePostEffect() {
1373
+ this.clearEffects();
1374
+ }
1375
+ setupEventListeners() {
1376
+ const e = this.eventAbort.signal;
1377
+ window.addEventListener(
1378
+ "resize",
1379
+ () => {
1380
+ this.resizeTimer && clearTimeout(this.resizeTimer), this.resizeTimer = setTimeout(() => this.onResize(), 100);
1381
+ },
1382
+ { signal: e }
1383
+ ), this.scrollSync || window.addEventListener("scroll", this.invalidateCanvasRect, {
1384
+ signal: e,
1385
+ passive: !0
1386
+ }), this.options.enableMouseTracking && this.setMouseTrackingEnabled(!0);
1387
+ }
1388
+ /**
1389
+ * mousemove tracking の動的な ON/OFF。
1390
+ * - true: 未 attach なら mousemove listener を追加する
1391
+ * - false: attach 済みなら detach する(hover も解除)
1392
+ *
1393
+ * `destroy()` 時は eventAbort と一緒に自動 detach される(_mouseAbort 個別 abort も呼ぶ)。
1394
+ */
1395
+ setMouseTrackingEnabled(e) {
1396
+ if (!this.destroyed)
1397
+ if (this.options.enableMouseTracking = e, e) {
1398
+ if (this._mouseAbort) return;
1399
+ this._mouseAbort = new AbortController(), window.addEventListener(
1400
+ "mousemove",
1401
+ (t) => this.onMouseMove(t),
1402
+ { signal: this._mouseAbort.signal }
1403
+ );
1404
+ } else
1405
+ this._mouseAbort?.abort(), this._mouseAbort = null, this._mouseInside = !1, this.hoveredPlane && (this.hoveredPlane.setHoverInfo(!1, null), this.hoveredPlane = null);
1406
+ }
1407
+ /** mousemove 等で頻繁に必要な canvas viewport rect を遅延 + キャッシュで返す。 */
1408
+ getCanvasRect() {
1409
+ return this._canvasRect || (this._canvasRect = this.canvas.getBoundingClientRect()), this._canvasRect;
1410
+ }
1411
+ onResize() {
1412
+ if (this._canvasRect = null, this.scrollSync ? (this.scrollSync.updateSize(), this.rect = this.scrollSync.logicalRect) : this.rect = this.container.getBoundingClientRect(), this.rect.width <= 0 || this.rect.height <= 0) return;
1413
+ this.camera.resize(this.rect), this.renderer.setSize(this.rect.width, this.rect.height), this.renderer.setPixelRatio(
1414
+ Math.min(window.devicePixelRatio, this.options.maxPixelRatio ?? 2)
1415
+ );
1416
+ const e = this.domPlanes;
1417
+ for (let r = 0, h = e.length; r < h; r++) {
1418
+ const l = e[r];
1419
+ l.setCanvasRect(this.rect), l.resize();
1420
+ }
1421
+ const t = this.dom3DObjects;
1422
+ for (let r = 0, h = t.length; r < h; r++) {
1423
+ const l = t[r];
1424
+ l.setCanvasRect(this.rect), l.resize();
1425
+ }
1426
+ this.postEffect?.resize(this.rect.width, this.rect.height);
1427
+ const i = this.effects;
1428
+ for (let r = 0, h = i.length; r < h; r++)
1429
+ i[r].resize?.(this.rect.width, this.rect.height);
1430
+ const o = this.resizeCallbacks;
1431
+ for (let r = 0, h = o.length; r < h; r++)
1432
+ o[r]();
1433
+ }
1434
+ /**
1435
+ * mousemove は **mouse 座標と canvas 内外フラグだけ** 更新する。
1436
+ * raycaster / setHoverInfo は呼ばない (= uMouseUV を直接書かない)。
1437
+ * uniform 更新は `_updateHoverFromMouse` 経由で rAF tick 内に集約することで、
1438
+ * paint と完全同期させ「mousemove 非同期発火による uMouseUV のちらつき」を防ぐ。
1439
+ */
1440
+ onMouseMove(e) {
1441
+ const t = this.getCanvasRect(), i = e.clientX >= t.left && e.clientX <= t.right && e.clientY >= t.top && e.clientY <= t.bottom;
1442
+ this._mouseInside = i, i && (this.mouse.x = (e.clientX - t.left) / t.width, this.mouse.y = 1 - (e.clientY - t.top) / t.height);
1443
+ }
1444
+ /**
1445
+ * rAF tick 内の Phase A で呼ばれる。最新の `this.mouse` を使って raycaster を投げ、
1446
+ * hover 中の plane を判定 + `setHoverInfo` (= uMouseUV / uIsHovered の更新) する。
1447
+ * mousemove イベントから切り離すことで全 plane の uniform が 1 tick = 1 確定値で
1448
+ * 揃い、paint と同期する。
1449
+ */
1450
+ _updateHoverFromMouse() {
1451
+ if (!this.options.enableMouseTracking || this.domPlaneMeshes.length === 0) return;
1452
+ if (!this._mouseInside) {
1453
+ this.hoveredPlane && (this.hoveredPlane.setHoverInfo(!1, null), this.hoveredPlane = null);
1454
+ return;
1455
+ }
1456
+ this._ndcBuf.set(this.mouse.x * 2 - 1, this.mouse.y * 2 - 1), this.raycaster.setFromCamera(this._ndcBuf, this.camera.instance);
1457
+ const e = this.raycaster.intersectObjects(this.domPlaneMeshes, !1);
1458
+ if (e.length > 0) {
1459
+ const t = e[0];
1460
+ if (t.uv) {
1461
+ const i = t.object, o = this.domPlanes.find((r) => r.getMesh() === i);
1462
+ if (o) {
1463
+ this.hoveredPlane && this.hoveredPlane !== o && this.hoveredPlane.setHoverInfo(!1, null), o.setHoverInfo(!0, t.uv), this.hoveredPlane = o;
1464
+ return;
1465
+ }
1466
+ }
1467
+ }
1468
+ this.hoveredPlane && (this.hoveredPlane.setHoverInfo(!1, null), this.hoveredPlane = null);
1469
+ }
1470
+ destroy() {
1471
+ if (!this.destroyed) {
1472
+ this.destroyed = !0, cancelAnimationFrame(this.rafId), this.resizeTimer && clearTimeout(this.resizeTimer), this.eventAbort.abort(), this._mouseAbort?.abort(), this._mouseAbort = null, this.domPlanes.forEach((e) => e.destroy()), this.dom3DObjects.forEach((e) => e.destroy()), this.domPlanes = [], this.dom3DObjects = [], this.domPlaneMeshes = [], this.updateCallbacks = [], this.resizeCallbacks = [], this.scrollSync?.destroy(), this.scrollSync = null;
1473
+ for (const e of this.effects)
1474
+ e.dispose?.();
1475
+ this.effects = [], this.postEffect?.dispose(), this.postEffect = null, this.internalComposer = null, this.controls?.dispose(), this.controls = null, this.renderer.dispose(), this.canvas.remove(), this.stats && (this.stats.dom.remove(), this.stats = null), this.gui && (this.gui.destroy(), this.gui = null);
1476
+ }
1477
+ }
1478
+ }
1479
+ const b = 0.01, ee = 50, M = 0.3, x = 1e3 / 60;
1480
+ class re {
1481
+ constructor(e = {}) {
1482
+ s(this, "_scrollY");
1483
+ /**
1484
+ * 直近 `window.scrollTo` に渡した値。scrollY が変わっていないフレで
1485
+ * scrollTo を no-op で呼ぶと scroll event が連発して page 側 listener を
1486
+ * 無駄に叩くので、差分があるときだけ呼び出すための diff キャッシュ。
1487
+ */
1488
+ s(this, "_lastAppliedY");
1489
+ s(this, "_maxScroll");
1490
+ s(this, "_rafId", 0);
1491
+ s(this, "_enabled", !0);
1492
+ s(this, "_lineHeight");
1493
+ s(this, "_friction");
1494
+ s(this, "_touchPrevY", 0);
1495
+ s(this, "_touchPrevTime", 0);
1496
+ /** 直近の touch velocity (px/ms)。touchend 後はこの値を初速に慣性が走る。 */
1497
+ s(this, "_velocityY", 0);
1498
+ s(this, "_isTouching", !1);
1499
+ /** tick() の dt 計算用。0 のとき初回 tick (dt は FRAME_MS で初期化)。 */
1500
+ s(this, "_lastTickTime", 0);
1501
+ s(this, "_eventAbort", new AbortController());
1502
+ s(this, "_resizeObserver", null);
1503
+ s(this, "_destroyed", !1);
1504
+ s(this, "onWheel", (e) => {
1505
+ if (!this._enabled) return;
1506
+ e.preventDefault(), this._velocityY = 0;
1507
+ let t = e.deltaY;
1508
+ e.deltaMode === 1 ? t *= this._lineHeight : e.deltaMode === 2 && (t *= window.innerHeight), this._scrollY = this.clamp(this._scrollY + t);
1509
+ });
1510
+ s(this, "onTouchStart", (e) => {
1511
+ this._enabled && e.touches.length !== 0 && (this._velocityY = 0, this._isTouching = !0, this._touchPrevY = e.touches[0].clientY, this._touchPrevTime = performance.now());
1512
+ });
1513
+ s(this, "onTouchMove", (e) => {
1514
+ if (!this._enabled || e.touches.length === 0) return;
1515
+ e.preventDefault();
1516
+ const t = e.touches[0].clientY, i = performance.now(), o = this._touchPrevY - t, r = i - this._touchPrevTime;
1517
+ if (this._scrollY = this.clamp(this._scrollY + o), r > 0) {
1518
+ const h = o / r;
1519
+ this._velocityY = this._velocityY * (1 - M) + h * M;
1520
+ }
1521
+ this._touchPrevY = t, this._touchPrevTime = i;
1522
+ });
1523
+ s(this, "onTouchEnd", () => {
1524
+ this._isTouching = !1, performance.now() - this._touchPrevTime > ee && (this._velocityY = 0);
1525
+ });
1526
+ s(this, "onResize", () => {
1527
+ this._maxScroll = this.calcMaxScroll(), this._scrollY = this.clamp(this._scrollY);
1528
+ });
1529
+ /**
1530
+ * 毎 rAF で `window.scrollTo` を呼ぶ。これが Lenis 方式の核心:
1531
+ * scrollY の更新タイミングを「JS rAF tick 上だけ」に集約することで、
1532
+ * paint 時に見える scrollY と JS が知る scrollY が完全一致する。
1533
+ *
1534
+ * 同値時は no-op で呼ばない (scroll event の連発で page 側 listener を
1535
+ * 叩くのを避ける)。
1536
+ *
1537
+ * touch リリース後の慣性: `_velocityY` が閾値以上ある間、毎 tick で
1538
+ * `scrollY += velocity * dt` を積みつつ `velocity *= friction^(dt/FRAME_MS)` で減衰。
1539
+ */
1540
+ s(this, "tick", (e = performance.now()) => {
1541
+ if (this._destroyed) return;
1542
+ const t = this._lastTickTime === 0 ? x : e - this._lastTickTime;
1543
+ if (this._lastTickTime = e, !this._isTouching && Math.abs(this._velocityY) > b) {
1544
+ const i = this._scrollY, o = this.clamp(this._scrollY + this._velocityY * t);
1545
+ this._scrollY = o, o === i ? this._velocityY = 0 : (this._velocityY *= Math.pow(this._friction, t / x), Math.abs(this._velocityY) < b && (this._velocityY = 0));
1546
+ }
1547
+ this._scrollY !== this._lastAppliedY && (window.scrollTo(0, this._scrollY), this._lastAppliedY = this._scrollY), this._rafId = requestAnimationFrame(this.tick);
1548
+ });
1549
+ this._lineHeight = e.lineHeight ?? 16, this._friction = e.touchFriction ?? 0.95, this._scrollY = window.scrollY, this._lastAppliedY = this._scrollY, this._maxScroll = this.calcMaxScroll(), this.setupEventListeners(), this.setupResizeObserver(), this._rafId = requestAnimationFrame(this.tick);
1550
+ }
1551
+ calcMaxScroll() {
1552
+ return Math.max(
1553
+ 0,
1554
+ document.documentElement.scrollHeight - window.innerHeight
1555
+ );
1556
+ }
1557
+ setupEventListeners() {
1558
+ const e = this._eventAbort.signal;
1559
+ window.addEventListener("wheel", this.onWheel, {
1560
+ passive: !1,
1561
+ signal: e
1562
+ }), window.addEventListener("touchstart", this.onTouchStart, {
1563
+ passive: !1,
1564
+ signal: e
1565
+ }), window.addEventListener("touchmove", this.onTouchMove, {
1566
+ passive: !1,
1567
+ signal: e
1568
+ }), window.addEventListener("touchend", this.onTouchEnd, {
1569
+ passive: !0,
1570
+ signal: e
1571
+ }), window.addEventListener("touchcancel", this.onTouchEnd, {
1572
+ passive: !0,
1573
+ signal: e
1574
+ }), window.addEventListener("resize", this.onResize, { signal: e });
1575
+ }
1576
+ /**
1577
+ * document の scrollHeight が動的に変わる場合 (画像 lazy load、SPA でのコンテンツ追加 等)
1578
+ * に maxScroll を追従させる。ResizeObserver で `<html>` を観察する。
1579
+ */
1580
+ setupResizeObserver() {
1581
+ typeof ResizeObserver > "u" || (this._resizeObserver = new ResizeObserver(() => {
1582
+ this._maxScroll = this.calcMaxScroll(), this._scrollY = this.clamp(this._scrollY);
1583
+ }), this._resizeObserver.observe(document.documentElement));
1584
+ }
1585
+ clamp(e) {
1586
+ return Math.min(this._maxScroll, Math.max(0, e));
1587
+ }
1588
+ /** 現在の virtual scrollY。 */
1589
+ get scrollY() {
1590
+ return this._scrollY;
1591
+ }
1592
+ /**
1593
+ * 入力受付の有効/無効。
1594
+ *
1595
+ * disable 中は wheel/touch ハンドラが **preventDefault する前に** early-return するため、
1596
+ * native スクロールが復活する(virtual scroll を一時停止して通常スクロールに戻したい
1597
+ * ケース用)。accumulator (`_scrollY`) も更新されず、慣性も止まる。
1598
+ *
1599
+ * 再 enable 時は `_scrollY` / `_lastAppliedY` を現在の `window.scrollY` に再同期して
1600
+ * disable 中の native scroll とのズレを吸収する(同期しないと次の wheel/touch 入力で
1601
+ * 古い `_scrollY` からの delta になり巨大ジャンプする)。
1602
+ */
1603
+ set enabled(e) {
1604
+ const t = !this._enabled;
1605
+ this._enabled = e, e || (this._velocityY = 0, this._isTouching = !1), e && t && (this._scrollY = window.scrollY, this._lastAppliedY = this._scrollY);
1606
+ }
1607
+ get enabled() {
1608
+ return this._enabled;
1609
+ }
1610
+ destroy() {
1611
+ this._destroyed || (this._destroyed = !0, cancelAnimationFrame(this._rafId), this._eventAbort.abort(), this._resizeObserver?.disconnect(), this._resizeObserver = null);
1612
+ }
1613
+ }
1614
+ class ne {
1615
+ constructor(e) {
1616
+ s(this, "scene");
1617
+ s(this, "camera");
1618
+ s(this, "rect");
1619
+ this.scene = new a.Scene(), this.camera = new C(e), this.rect = e;
1620
+ }
1621
+ resize(e) {
1622
+ this.rect = e, this.camera.resize(e);
1623
+ }
1624
+ dispose() {
1625
+ }
1626
+ }
1627
+ class ae {
1628
+ constructor() {
1629
+ s(this, "pass", null);
1630
+ /**
1631
+ * GUI の on/off チェックボックスから操作される。false にすると
1632
+ * EffectComposer がこのパスをスキップする(lil-gui バインド用に public)。
1633
+ */
1634
+ s(this, "_enabled", !0);
1635
+ }
1636
+ get enabled() {
1637
+ return this._enabled;
1638
+ }
1639
+ set enabled(e) {
1640
+ this._enabled = e, this.pass && (this.pass.enabled = e);
1641
+ }
1642
+ /**
1643
+ * addEffect() 呼び出し時に内部で呼ばれる。直接使わない。
1644
+ *
1645
+ * 1 インスタンスを `webgl.addEffect()` と `domPlane.addEffect()` の両方に
1646
+ * 渡すと、`update()` が同一フレームに 2 回走り内部状態が二重進行する
1647
+ * (例: `FluidEffect` の dye が倍速で進む)。DEV では警告を出す。
1648
+ */
1649
+ _register(e) {
1650
+ this.pass;
1651
+ const t = this.getConfig();
1652
+ this.pass = e.addEffect({
1653
+ fragmentShader: t.fragmentShader,
1654
+ uniforms: t.uniforms
1655
+ }), this.pass.enabled = this._enabled;
1656
+ }
1657
+ update(e, t) {
1658
+ }
1659
+ setUniform(e, t) {
1660
+ this.pass?.setUniform(e, t);
1661
+ }
1662
+ getUniform(e) {
1663
+ return this.pass?.getUniform(e);
1664
+ }
1665
+ getPass() {
1666
+ return this.pass;
1667
+ }
1668
+ }
1669
+ export {
1670
+ F as AMBIENT_LIGHT_COLOR,
1671
+ D as AMBIENT_LIGHT_INTENSITY,
1672
+ ae as BaseEffect,
1673
+ ne as BaseScene,
1674
+ k as CAMERA_FAR,
1675
+ v as CAMERA_FOV,
1676
+ G as CAMERA_NEAR,
1677
+ C as Camera,
1678
+ V as DEFAULT_OFFSET,
1679
+ y as DEFAULT_SCALE,
1680
+ U as DIRECTIONAL_LIGHT_COLOR,
1681
+ H as DIRECTIONAL_LIGHT_INTENSITY,
1682
+ w as DIRECTIONAL_LIGHT_POSITION,
1683
+ J as Dom3DObject,
1684
+ K as DomPlane,
1685
+ P as DomPositionCalculator,
1686
+ j as EffectComposer,
1687
+ R as EffectPass,
1688
+ B as Light,
1689
+ N as PlaneComposer,
1690
+ re as RafScroll,
1691
+ Z as ScrollSync,
1692
+ a as THREE,
1693
+ oe as WebGLApp
1694
+ };
1695
+ //# sourceMappingURL=index.js.map