@sangwonl/pocato-core 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +27 -7
- package/dist/index.js +535 -194
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -27,7 +27,10 @@ var EventEmitter = class {
|
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
// src/renderer/index.ts
|
|
30
|
-
import * as
|
|
30
|
+
import * as THREE4 from "three";
|
|
31
|
+
|
|
32
|
+
// src/renderer/face-renderer.ts
|
|
33
|
+
import * as THREE3 from "three";
|
|
31
34
|
|
|
32
35
|
// src/renderer/shader-bootstrap.ts
|
|
33
36
|
import * as THREE from "three";
|
|
@@ -361,6 +364,153 @@ function bootstrapShaders() {
|
|
|
361
364
|
ThreeShaderChunk["utils/defaultLighting"] = default_lighting_glsl_default;
|
|
362
365
|
}
|
|
363
366
|
|
|
367
|
+
// src/renderer/texture-loader.ts
|
|
368
|
+
import * as THREE2 from "three";
|
|
369
|
+
var VIDEO_EXTENSIONS = /\.(mp4|webm|mov|ogg)(\?|$)/i;
|
|
370
|
+
function isVideoSource(layer) {
|
|
371
|
+
if (layer.type) return layer.type === "video";
|
|
372
|
+
return VIDEO_EXTENSIONS.test(layer.src);
|
|
373
|
+
}
|
|
374
|
+
var transparentTexture = null;
|
|
375
|
+
function getTransparentTexture() {
|
|
376
|
+
if (!transparentTexture) {
|
|
377
|
+
const data = new Uint8Array([0, 0, 0, 0]);
|
|
378
|
+
transparentTexture = new THREE2.DataTexture(data, 1, 1, THREE2.RGBAFormat);
|
|
379
|
+
transparentTexture.needsUpdate = true;
|
|
380
|
+
}
|
|
381
|
+
return transparentTexture;
|
|
382
|
+
}
|
|
383
|
+
var pendingVideos = /* @__PURE__ */ new Set();
|
|
384
|
+
function loadLayerTexture(layer, onError) {
|
|
385
|
+
if (isVideoSource(layer)) {
|
|
386
|
+
if (layer.freeze != null) {
|
|
387
|
+
return loadFrozenVideoTexture(layer.src, layer.freeze === "random" ? -1 : layer.freeze, onError);
|
|
388
|
+
}
|
|
389
|
+
return loadVideoTexture(layer.src, onError);
|
|
390
|
+
}
|
|
391
|
+
return loadImageTexture(layer.src, onError);
|
|
392
|
+
}
|
|
393
|
+
var inflight = /* @__PURE__ */ new Map();
|
|
394
|
+
function loadImageTexture(src, onError) {
|
|
395
|
+
let pending = inflight.get(src);
|
|
396
|
+
if (!pending) {
|
|
397
|
+
pending = new Promise((resolve) => {
|
|
398
|
+
const loader = new THREE2.TextureLoader();
|
|
399
|
+
loader.load(
|
|
400
|
+
src,
|
|
401
|
+
(texture) => {
|
|
402
|
+
inflight.delete(src);
|
|
403
|
+
resolve(texture);
|
|
404
|
+
},
|
|
405
|
+
void 0,
|
|
406
|
+
() => {
|
|
407
|
+
inflight.delete(src);
|
|
408
|
+
onError?.(new Error(`Failed to load image texture: ${src}`));
|
|
409
|
+
resolve(getTransparentTexture());
|
|
410
|
+
}
|
|
411
|
+
);
|
|
412
|
+
});
|
|
413
|
+
inflight.set(src, pending);
|
|
414
|
+
}
|
|
415
|
+
return pending.then((texture) => ({ texture }));
|
|
416
|
+
}
|
|
417
|
+
function loadVideoTexture(src, onError) {
|
|
418
|
+
return new Promise((resolve) => {
|
|
419
|
+
const video = document.createElement("video");
|
|
420
|
+
video.src = src;
|
|
421
|
+
video.muted = true;
|
|
422
|
+
video.loop = true;
|
|
423
|
+
video.playsInline = true;
|
|
424
|
+
video.preload = "auto";
|
|
425
|
+
video.style.position = "fixed";
|
|
426
|
+
video.style.width = "1px";
|
|
427
|
+
video.style.height = "1px";
|
|
428
|
+
video.style.opacity = "0.01";
|
|
429
|
+
video.style.pointerEvents = "none";
|
|
430
|
+
video.style.zIndex = "-9999";
|
|
431
|
+
document.body.appendChild(video);
|
|
432
|
+
pendingVideos.add(video);
|
|
433
|
+
video.addEventListener("error", () => {
|
|
434
|
+
pendingVideos.delete(video);
|
|
435
|
+
const msg = video.error?.message ?? "Unknown video error";
|
|
436
|
+
onError?.(new Error(`Failed to load video texture: ${src} (${msg})`));
|
|
437
|
+
video.remove();
|
|
438
|
+
resolve({ texture: getTransparentTexture() });
|
|
439
|
+
}, { once: true });
|
|
440
|
+
video.play().then(() => {
|
|
441
|
+
pendingVideos.delete(video);
|
|
442
|
+
const texture = new THREE2.VideoTexture(video);
|
|
443
|
+
resolve({ texture, videoEl: video });
|
|
444
|
+
}).catch(() => {
|
|
445
|
+
pendingVideos.delete(video);
|
|
446
|
+
onError?.(new Error(`Video autoplay blocked: ${src}`));
|
|
447
|
+
video.remove();
|
|
448
|
+
resolve({ texture: getTransparentTexture() });
|
|
449
|
+
});
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
function loadFrozenVideoTexture(src, freezeTime, onError) {
|
|
453
|
+
return new Promise((resolve) => {
|
|
454
|
+
const video = document.createElement("video");
|
|
455
|
+
video.src = src;
|
|
456
|
+
video.muted = true;
|
|
457
|
+
video.playsInline = true;
|
|
458
|
+
video.preload = "auto";
|
|
459
|
+
video.style.position = "fixed";
|
|
460
|
+
video.style.width = "1px";
|
|
461
|
+
video.style.height = "1px";
|
|
462
|
+
video.style.opacity = "0.01";
|
|
463
|
+
video.style.pointerEvents = "none";
|
|
464
|
+
video.style.zIndex = "-9999";
|
|
465
|
+
document.body.appendChild(video);
|
|
466
|
+
pendingVideos.add(video);
|
|
467
|
+
video.addEventListener("error", () => {
|
|
468
|
+
pendingVideos.delete(video);
|
|
469
|
+
const msg = video.error?.message ?? "Unknown video error";
|
|
470
|
+
onError?.(new Error(`Failed to load video texture: ${src} (${msg})`));
|
|
471
|
+
video.remove();
|
|
472
|
+
resolve({ texture: getTransparentTexture() });
|
|
473
|
+
}, { once: true });
|
|
474
|
+
video.addEventListener("loadeddata", () => {
|
|
475
|
+
const time = freezeTime < 0 ? Math.random() * (video.duration || 1) : Math.min(freezeTime, video.duration || 0);
|
|
476
|
+
video.currentTime = time;
|
|
477
|
+
}, { once: true });
|
|
478
|
+
video.addEventListener("seeked", () => {
|
|
479
|
+
const canvas = document.createElement("canvas");
|
|
480
|
+
canvas.width = video.videoWidth;
|
|
481
|
+
canvas.height = video.videoHeight;
|
|
482
|
+
const ctx = canvas.getContext("2d");
|
|
483
|
+
ctx.drawImage(video, 0, 0);
|
|
484
|
+
const texture = new THREE2.CanvasTexture(canvas);
|
|
485
|
+
texture.needsUpdate = true;
|
|
486
|
+
pendingVideos.delete(video);
|
|
487
|
+
video.pause();
|
|
488
|
+
video.src = "";
|
|
489
|
+
video.remove();
|
|
490
|
+
resolve({ texture });
|
|
491
|
+
}, { once: true });
|
|
492
|
+
video.load();
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
function disposeLoadedLayer(loaded) {
|
|
496
|
+
if (loaded.videoEl) {
|
|
497
|
+
loaded.videoEl.pause();
|
|
498
|
+
loaded.videoEl.src = "";
|
|
499
|
+
loaded.videoEl.remove();
|
|
500
|
+
}
|
|
501
|
+
if (loaded.texture !== getTransparentTexture()) {
|
|
502
|
+
loaded.texture.dispose();
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
function cleanupPendingVideos() {
|
|
506
|
+
for (const video of pendingVideos) {
|
|
507
|
+
video.pause();
|
|
508
|
+
video.src = "";
|
|
509
|
+
video.remove();
|
|
510
|
+
}
|
|
511
|
+
pendingVideos.clear();
|
|
512
|
+
}
|
|
513
|
+
|
|
364
514
|
// src/shaders/common.vert.ts
|
|
365
515
|
var common_vert_default = glsl`
|
|
366
516
|
// three.js built-in attributes
|
|
@@ -409,8 +559,9 @@ uniform vec2 uMouse;
|
|
|
409
559
|
uniform vec2 uMove;
|
|
410
560
|
uniform vec2 uRotate;
|
|
411
561
|
uniform float uTime;
|
|
412
|
-
uniform sampler2D
|
|
413
|
-
uniform sampler2D
|
|
562
|
+
uniform sampler2D uLayer0;
|
|
563
|
+
uniform sampler2D uLayer1;
|
|
564
|
+
uniform int uLayerCount;
|
|
414
565
|
|
|
415
566
|
varying vec2 vUv;
|
|
416
567
|
|
|
@@ -418,12 +569,12 @@ varying vec2 vUv;
|
|
|
418
569
|
|
|
419
570
|
void main() {
|
|
420
571
|
// 기본 카드 텍스처 샘플링
|
|
421
|
-
vec4 baseColor = texture2D(
|
|
572
|
+
vec4 baseColor = texture2D(uLayer0, vUv);
|
|
422
573
|
|
|
423
574
|
// 팝업 텍스처 샘플링 (움직임 반영)
|
|
424
575
|
float popupOffset = 0.0;
|
|
425
576
|
vec2 popupUv = vUv + (uMove / uResolution) * popupOffset; // 팝업 이동 효과
|
|
426
|
-
vec4 popupColor = texture2D(
|
|
577
|
+
vec4 popupColor = texture2D(uLayer1, popupUv);
|
|
427
578
|
|
|
428
579
|
float aSoft = smoothstep(0.4, 1.0, popupColor.a);
|
|
429
580
|
vec4 pocaColor = mix(baseColor, popupColor, aSoft);
|
|
@@ -443,8 +594,9 @@ uniform vec2 uMouse;
|
|
|
443
594
|
uniform vec2 uMove;
|
|
444
595
|
uniform vec2 uRotate;
|
|
445
596
|
uniform float uTime;
|
|
446
|
-
uniform sampler2D
|
|
447
|
-
uniform sampler2D
|
|
597
|
+
uniform sampler2D uLayer0;
|
|
598
|
+
uniform sampler2D uLayer1;
|
|
599
|
+
uniform int uLayerCount;
|
|
448
600
|
|
|
449
601
|
varying vec2 vUv;
|
|
450
602
|
|
|
@@ -452,12 +604,12 @@ varying vec2 vUv;
|
|
|
452
604
|
|
|
453
605
|
void main() {
|
|
454
606
|
// 기본 카드 텍스처 샘플링
|
|
455
|
-
vec4 baseColor = texture2D(
|
|
607
|
+
vec4 baseColor = texture2D(uLayer0, vUv);
|
|
456
608
|
|
|
457
609
|
// 팝업 텍스처 샘플링 (움직임 반영)
|
|
458
610
|
vec2 popupOffset = vec2(-uRotate.x * 0.08, -uRotate.y * 0.06);
|
|
459
611
|
vec2 popupUv = vUv + popupOffset; // 팝업 이동 효과
|
|
460
|
-
vec4 popupColor = texture2D(
|
|
612
|
+
vec4 popupColor = texture2D(uLayer1, popupUv);
|
|
461
613
|
|
|
462
614
|
float aSoft = smoothstep(0.4, 1.0, popupColor.a);
|
|
463
615
|
vec4 pocaColor = mix(baseColor, popupColor, aSoft);
|
|
@@ -477,8 +629,9 @@ uniform vec2 uMouse;
|
|
|
477
629
|
uniform vec2 uMove;
|
|
478
630
|
uniform vec2 uRotate;
|
|
479
631
|
uniform float uTime;
|
|
480
|
-
uniform sampler2D
|
|
481
|
-
uniform sampler2D
|
|
632
|
+
uniform sampler2D uLayer0;
|
|
633
|
+
uniform sampler2D uLayer1;
|
|
634
|
+
uniform int uLayerCount;
|
|
482
635
|
|
|
483
636
|
varying vec2 vUv;
|
|
484
637
|
|
|
@@ -567,7 +720,7 @@ float computeSnow(in vec2 uv, int layerStart, int layerEnd) {
|
|
|
567
720
|
}
|
|
568
721
|
|
|
569
722
|
void main() {
|
|
570
|
-
vec4 baseTexture = texture2D(
|
|
723
|
+
vec4 baseTexture = texture2D(uLayer0, vUv);
|
|
571
724
|
gl_FragColor = mix(gl_FragColor, baseTexture, baseTexture.a);
|
|
572
725
|
|
|
573
726
|
float snowEffect = computeSnow(vUv, 5, 15);
|
|
@@ -575,7 +728,7 @@ void main() {
|
|
|
575
728
|
|
|
576
729
|
float popupMoveOffset = 0.0;
|
|
577
730
|
vec2 popupOffsetUv = vUv + (uMove.xy / uResolution.xy) * popupMoveOffset;
|
|
578
|
-
vec4 popupTexture = texture2D(
|
|
731
|
+
vec4 popupTexture = texture2D(uLayer1, popupOffsetUv);
|
|
579
732
|
gl_FragColor = mix(gl_FragColor, popupTexture, popupTexture.a);
|
|
580
733
|
|
|
581
734
|
snowEffect = computeSnow(vUv, 16, 20);
|
|
@@ -594,8 +747,9 @@ uniform vec2 uMouse;
|
|
|
594
747
|
uniform vec2 uMove;
|
|
595
748
|
uniform vec2 uRotate;
|
|
596
749
|
uniform float uTime;
|
|
597
|
-
uniform sampler2D
|
|
598
|
-
uniform sampler2D
|
|
750
|
+
uniform sampler2D uLayer0;
|
|
751
|
+
uniform sampler2D uLayer1;
|
|
752
|
+
uniform int uLayerCount;
|
|
599
753
|
|
|
600
754
|
varying vec2 vUv;
|
|
601
755
|
|
|
@@ -684,7 +838,7 @@ float computeSnow(in vec2 uv, int layerStart, int layerEnd) {
|
|
|
684
838
|
}
|
|
685
839
|
|
|
686
840
|
void main() {
|
|
687
|
-
vec4 baseTexture = texture2D(
|
|
841
|
+
vec4 baseTexture = texture2D(uLayer0, vUv);
|
|
688
842
|
gl_FragColor = mix(gl_FragColor, baseTexture, baseTexture.a);
|
|
689
843
|
|
|
690
844
|
float snowEffect = computeSnow(vUv, 5, 15);
|
|
@@ -693,7 +847,7 @@ void main() {
|
|
|
693
847
|
// 3D parallax offset for popup layer
|
|
694
848
|
vec2 popupOffset = vec2(-uRotate.x * 0.08, -uRotate.y * 0.06);
|
|
695
849
|
vec2 popupOffsetUv = vUv + popupOffset;
|
|
696
|
-
vec4 popupTexture = texture2D(
|
|
850
|
+
vec4 popupTexture = texture2D(uLayer1, popupOffsetUv);
|
|
697
851
|
gl_FragColor = mix(gl_FragColor, popupTexture, popupTexture.a);
|
|
698
852
|
|
|
699
853
|
snowEffect = computeSnow(vUv, 16, 20);
|
|
@@ -712,8 +866,9 @@ uniform vec2 uMouse;
|
|
|
712
866
|
uniform vec2 uMove;
|
|
713
867
|
uniform vec2 uRotate;
|
|
714
868
|
uniform float uTime;
|
|
715
|
-
uniform sampler2D
|
|
716
|
-
uniform sampler2D
|
|
869
|
+
uniform sampler2D uLayer0;
|
|
870
|
+
uniform sampler2D uLayer1;
|
|
871
|
+
uniform int uLayerCount;
|
|
717
872
|
|
|
718
873
|
varying vec2 vUv;
|
|
719
874
|
|
|
@@ -727,8 +882,8 @@ void main() {
|
|
|
727
882
|
vec2 pixel = 1.0 / uResolution.xy;
|
|
728
883
|
vec2 popUpUv = vUv + (uMove.xy / uResolution.xy) * 0.008;
|
|
729
884
|
|
|
730
|
-
vec4 baseColor = kuwahara(
|
|
731
|
-
vec4 popupColor = kuwahara(
|
|
885
|
+
vec4 baseColor = kuwahara(uLayer0, vUv, pixel, 4.0);
|
|
886
|
+
vec4 popupColor = kuwahara(uLayer1, popUpUv, pixel, 4.0);
|
|
732
887
|
|
|
733
888
|
float aSoft = smoothstep(0.4, 1.0, popupColor.a);
|
|
734
889
|
vec4 pocaColor = mix(baseColor, popupColor, aSoft);
|
|
@@ -748,8 +903,9 @@ uniform vec2 uMouse;
|
|
|
748
903
|
uniform vec2 uMove;
|
|
749
904
|
uniform vec2 uRotate;
|
|
750
905
|
uniform float uTime;
|
|
751
|
-
uniform sampler2D
|
|
752
|
-
uniform sampler2D
|
|
906
|
+
uniform sampler2D uLayer0;
|
|
907
|
+
uniform sampler2D uLayer1;
|
|
908
|
+
uniform int uLayerCount;
|
|
753
909
|
|
|
754
910
|
varying vec2 vUv;
|
|
755
911
|
|
|
@@ -766,8 +922,8 @@ void main() {
|
|
|
766
922
|
vec2 popupOffset = vec2(-uRotate.x * 0.08, -uRotate.y * 0.06);
|
|
767
923
|
vec2 popUpUv = vUv + popupOffset;
|
|
768
924
|
|
|
769
|
-
vec4 baseColor = kuwahara(
|
|
770
|
-
vec4 popupColor = kuwahara(
|
|
925
|
+
vec4 baseColor = kuwahara(uLayer0, vUv, pixel, 4.0);
|
|
926
|
+
vec4 popupColor = kuwahara(uLayer1, popUpUv, pixel, 4.0);
|
|
771
927
|
|
|
772
928
|
float aSoft = smoothstep(0.4, 1.0, popupColor.a);
|
|
773
929
|
vec4 pocaColor = mix(baseColor, popupColor, aSoft);
|
|
@@ -787,8 +943,9 @@ uniform vec2 uMouse;
|
|
|
787
943
|
uniform vec2 uMove;
|
|
788
944
|
uniform vec2 uRotate;
|
|
789
945
|
uniform float uTime;
|
|
790
|
-
uniform sampler2D
|
|
791
|
-
uniform sampler2D
|
|
946
|
+
uniform sampler2D uLayer0;
|
|
947
|
+
uniform sampler2D uLayer1;
|
|
948
|
+
uniform int uLayerCount;
|
|
792
949
|
|
|
793
950
|
varying vec2 vUv;
|
|
794
951
|
|
|
@@ -803,8 +960,8 @@ void main() {
|
|
|
803
960
|
vec2 pixel = 1.0 / uResolution.xy;
|
|
804
961
|
vec2 popUpUv = vUv + (uMove.xy / uResolution.xy) * 0.008;
|
|
805
962
|
|
|
806
|
-
vec4 baseColor = gaussianBlur(
|
|
807
|
-
vec4 popupColor = texture2D(
|
|
963
|
+
vec4 baseColor = gaussianBlur(uLayer0, vUv, pixel, 12);
|
|
964
|
+
vec4 popupColor = texture2D(uLayer1, popUpUv);
|
|
808
965
|
|
|
809
966
|
float aSoft = smoothstep(0.4, 1.0, popupColor.a);
|
|
810
967
|
vec4 pocaColor = mix(baseColor, popupColor, aSoft);
|
|
@@ -824,8 +981,9 @@ uniform vec2 uMouse;
|
|
|
824
981
|
uniform vec2 uMove;
|
|
825
982
|
uniform vec2 uRotate;
|
|
826
983
|
uniform float uTime;
|
|
827
|
-
uniform sampler2D
|
|
828
|
-
uniform sampler2D
|
|
984
|
+
uniform sampler2D uLayer0;
|
|
985
|
+
uniform sampler2D uLayer1;
|
|
986
|
+
uniform int uLayerCount;
|
|
829
987
|
|
|
830
988
|
varying vec2 vUv;
|
|
831
989
|
|
|
@@ -843,8 +1001,8 @@ void main() {
|
|
|
843
1001
|
vec2 popupOffset = vec2(-uRotate.x * 0.08, -uRotate.y * 0.06);
|
|
844
1002
|
vec2 popUpUv = vUv + popupOffset;
|
|
845
1003
|
|
|
846
|
-
vec4 baseColor = gaussianBlur(
|
|
847
|
-
vec4 popupColor = texture2D(
|
|
1004
|
+
vec4 baseColor = gaussianBlur(uLayer0, vUv, pixel, 12);
|
|
1005
|
+
vec4 popupColor = texture2D(uLayer1, popUpUv);
|
|
848
1006
|
|
|
849
1007
|
float aSoft = smoothstep(0.4, 1.0, popupColor.a);
|
|
850
1008
|
vec4 pocaColor = mix(baseColor, popupColor, aSoft);
|
|
@@ -864,8 +1022,9 @@ uniform vec2 uMouse;
|
|
|
864
1022
|
uniform vec2 uMove;
|
|
865
1023
|
uniform vec2 uRotate;
|
|
866
1024
|
uniform float uTime;
|
|
867
|
-
uniform sampler2D
|
|
868
|
-
uniform sampler2D
|
|
1025
|
+
uniform sampler2D uLayer0;
|
|
1026
|
+
uniform sampler2D uLayer1;
|
|
1027
|
+
uniform int uLayerCount;
|
|
869
1028
|
|
|
870
1029
|
varying vec2 vUv;
|
|
871
1030
|
|
|
@@ -879,8 +1038,8 @@ vec3 hsv2rgb(vec3 c) {
|
|
|
879
1038
|
}
|
|
880
1039
|
|
|
881
1040
|
void main() {
|
|
882
|
-
vec4 baseColor = texture2D(
|
|
883
|
-
vec4 popupColor = texture2D(
|
|
1041
|
+
vec4 baseColor = texture2D(uLayer0, vUv);
|
|
1042
|
+
vec4 popupColor = texture2D(uLayer1, vUv);
|
|
884
1043
|
|
|
885
1044
|
// Iridescence on base only
|
|
886
1045
|
float angle = uRotate.x * 2.0 + uRotate.y * 1.5;
|
|
@@ -911,8 +1070,9 @@ uniform vec2 uMouse;
|
|
|
911
1070
|
uniform vec2 uMove;
|
|
912
1071
|
uniform vec2 uRotate;
|
|
913
1072
|
uniform float uTime;
|
|
914
|
-
uniform sampler2D
|
|
915
|
-
uniform sampler2D
|
|
1073
|
+
uniform sampler2D uLayer0;
|
|
1074
|
+
uniform sampler2D uLayer1;
|
|
1075
|
+
uniform int uLayerCount;
|
|
916
1076
|
|
|
917
1077
|
varying vec2 vUv;
|
|
918
1078
|
|
|
@@ -926,12 +1086,12 @@ vec3 hsv2rgb(vec3 c) {
|
|
|
926
1086
|
}
|
|
927
1087
|
|
|
928
1088
|
void main() {
|
|
929
|
-
vec4 baseColor = texture2D(
|
|
1089
|
+
vec4 baseColor = texture2D(uLayer0, vUv);
|
|
930
1090
|
|
|
931
1091
|
// 3D parallax offset for popup layer
|
|
932
1092
|
vec2 popupOffset = vec2(-uRotate.x * 0.08, -uRotate.y * 0.06);
|
|
933
1093
|
vec2 popupUv = vUv + popupOffset;
|
|
934
|
-
vec4 popupColor = texture2D(
|
|
1094
|
+
vec4 popupColor = texture2D(uLayer1, popupUv);
|
|
935
1095
|
|
|
936
1096
|
// Iridescence on base only
|
|
937
1097
|
float angle = uRotate.x * 2.0 + uRotate.y * 1.5;
|
|
@@ -951,7 +1111,7 @@ void main() {
|
|
|
951
1111
|
}
|
|
952
1112
|
`;
|
|
953
1113
|
|
|
954
|
-
// src/renderer/
|
|
1114
|
+
// src/renderer/face-renderer.ts
|
|
955
1115
|
var FRAG_SHADERS = {
|
|
956
1116
|
"glare": glare_frag_default,
|
|
957
1117
|
"glare-3d": glare_3d_frag_default,
|
|
@@ -964,21 +1124,111 @@ var FRAG_SHADERS = {
|
|
|
964
1124
|
"holo": holo_frag_default,
|
|
965
1125
|
"holo-3d": holo_3d_frag_default
|
|
966
1126
|
};
|
|
1127
|
+
var MAX_LAYERS = 8;
|
|
1128
|
+
var FaceRenderer = class {
|
|
1129
|
+
constructor(shader, width, height) {
|
|
1130
|
+
this.loadedLayers = [];
|
|
1131
|
+
bootstrapShaders();
|
|
1132
|
+
this.scene = new THREE3.Scene();
|
|
1133
|
+
this.camera = new THREE3.Camera();
|
|
1134
|
+
const uniforms = this.createUniforms(width, height);
|
|
1135
|
+
const rawFrag = shader ?? FRAG_SHADERS["glare"];
|
|
1136
|
+
const fragmentShader = resolveIncludes(rawFrag);
|
|
1137
|
+
this.material = new THREE3.ShaderMaterial({
|
|
1138
|
+
vertexShader: resolveIncludes(common_vert_default),
|
|
1139
|
+
fragmentShader,
|
|
1140
|
+
uniforms,
|
|
1141
|
+
transparent: true
|
|
1142
|
+
});
|
|
1143
|
+
const geometry = new THREE3.PlaneGeometry(2, 2);
|
|
1144
|
+
this.mesh = new THREE3.Mesh(geometry, this.material);
|
|
1145
|
+
this.scene.add(this.mesh);
|
|
1146
|
+
}
|
|
1147
|
+
createUniforms(width, height) {
|
|
1148
|
+
const uniforms = {
|
|
1149
|
+
uTime: { value: 0 },
|
|
1150
|
+
uResolution: { value: new THREE3.Vector2(width, height) },
|
|
1151
|
+
uMouse: { value: new THREE3.Vector2(0, 0) },
|
|
1152
|
+
uMove: { value: new THREE3.Vector2(0, 0) },
|
|
1153
|
+
uRotate: { value: new THREE3.Vector2(0, 0) },
|
|
1154
|
+
uCardOpacity: { value: 1 },
|
|
1155
|
+
uLayerCount: { value: 0 }
|
|
1156
|
+
};
|
|
1157
|
+
const transparent = getTransparentTexture();
|
|
1158
|
+
for (let i = 0; i < MAX_LAYERS; i++) {
|
|
1159
|
+
uniforms[`uLayer${i}`] = { value: transparent };
|
|
1160
|
+
}
|
|
1161
|
+
return uniforms;
|
|
1162
|
+
}
|
|
1163
|
+
async loadLayers(layers, onError) {
|
|
1164
|
+
this.disposeLayers();
|
|
1165
|
+
const count = Math.min(layers.length, MAX_LAYERS);
|
|
1166
|
+
this.material.uniforms.uLayerCount.value = count;
|
|
1167
|
+
const promises = layers.slice(0, MAX_LAYERS).map(
|
|
1168
|
+
(layer) => loadLayerTexture(layer, onError)
|
|
1169
|
+
);
|
|
1170
|
+
const results = await Promise.all(promises);
|
|
1171
|
+
this.loadedLayers = results;
|
|
1172
|
+
for (let i = 0; i < results.length; i++) {
|
|
1173
|
+
this.material.uniforms[`uLayer${i}`].value = results[i].texture;
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
updateUniforms(updates) {
|
|
1177
|
+
const u = this.material.uniforms;
|
|
1178
|
+
if (updates.rotate) u.uRotate.value.set(
|
|
1179
|
+
updates.rotate.x * (Math.PI / 180),
|
|
1180
|
+
updates.rotate.y * (Math.PI / 180)
|
|
1181
|
+
);
|
|
1182
|
+
if (updates.mouse) u.uMouse.value.set(updates.mouse.x, updates.mouse.y);
|
|
1183
|
+
if (updates.move) u.uMove.value.set(updates.move.x, updates.move.y);
|
|
1184
|
+
if (updates.opacity !== void 0) u.uCardOpacity.value = updates.opacity;
|
|
1185
|
+
if (updates.time !== void 0) u.uTime.value = updates.time;
|
|
1186
|
+
}
|
|
1187
|
+
updateResolution(width, height) {
|
|
1188
|
+
this.material.uniforms.uResolution.value.set(width, height);
|
|
1189
|
+
}
|
|
1190
|
+
updateShader(fragmentShader) {
|
|
1191
|
+
this.material.fragmentShader = resolveIncludes(fragmentShader);
|
|
1192
|
+
this.material.needsUpdate = true;
|
|
1193
|
+
}
|
|
1194
|
+
disposeLayers() {
|
|
1195
|
+
for (const loaded of this.loadedLayers) {
|
|
1196
|
+
disposeLoadedLayer(loaded);
|
|
1197
|
+
}
|
|
1198
|
+
this.loadedLayers = [];
|
|
1199
|
+
const transparent = getTransparentTexture();
|
|
1200
|
+
for (let i = 0; i < MAX_LAYERS; i++) {
|
|
1201
|
+
this.material.uniforms[`uLayer${i}`].value = transparent;
|
|
1202
|
+
}
|
|
1203
|
+
this.material.uniforms.uLayerCount.value = 0;
|
|
1204
|
+
}
|
|
1205
|
+
destroy() {
|
|
1206
|
+
this.disposeLayers();
|
|
1207
|
+
this.mesh.geometry.dispose();
|
|
1208
|
+
this.material.dispose();
|
|
1209
|
+
}
|
|
1210
|
+
};
|
|
1211
|
+
|
|
1212
|
+
// src/renderer/index.ts
|
|
967
1213
|
var Renderer = class {
|
|
968
1214
|
constructor(container, options, onError, onReady) {
|
|
969
1215
|
this.container = container;
|
|
970
1216
|
this.options = options;
|
|
971
1217
|
this.onError = onError;
|
|
972
1218
|
this.onReady = onReady;
|
|
973
|
-
this.
|
|
974
|
-
this.
|
|
975
|
-
this.
|
|
976
|
-
this.
|
|
977
|
-
this.mesh = null;
|
|
978
|
-
this.clock = new THREE2.Clock();
|
|
1219
|
+
this.backFace = null;
|
|
1220
|
+
this.frontRenderer = null;
|
|
1221
|
+
this.backRenderer = null;
|
|
1222
|
+
this.clock = new THREE4.Clock();
|
|
979
1223
|
this.rafId = null;
|
|
980
|
-
this.
|
|
1224
|
+
this.backCanvas = null;
|
|
981
1225
|
this.resizeObserver = null;
|
|
1226
|
+
this.intersectionObserver = null;
|
|
1227
|
+
this.visible = false;
|
|
1228
|
+
this.destroyed = false;
|
|
1229
|
+
if (!options.front?.layers?.length) {
|
|
1230
|
+
console.warn("[pocato] front.layers must have at least 1 element");
|
|
1231
|
+
}
|
|
982
1232
|
this.injectStyles();
|
|
983
1233
|
this.cardEl = document.createElement("div");
|
|
984
1234
|
this.cardEl.className = "pocato-card pocato-loading";
|
|
@@ -986,29 +1236,133 @@ var Renderer = class {
|
|
|
986
1236
|
this.rotatorEl.className = "pocato-rotator";
|
|
987
1237
|
this.backEl = document.createElement("div");
|
|
988
1238
|
this.backEl.className = "pocato-back";
|
|
989
|
-
if (options.backImage) {
|
|
990
|
-
const backImg = document.createElement("img");
|
|
991
|
-
backImg.src = options.backImage;
|
|
992
|
-
backImg.className = "pocato-back-img";
|
|
993
|
-
this.backEl.appendChild(backImg);
|
|
994
|
-
}
|
|
995
1239
|
this.frontEl = document.createElement("div");
|
|
996
1240
|
this.frontEl.className = "pocato-front";
|
|
997
|
-
this.
|
|
998
|
-
this.
|
|
1241
|
+
this.frontCanvas = document.createElement("canvas");
|
|
1242
|
+
this.frontCanvas.className = "pocato-canvas";
|
|
999
1243
|
this.frontContentEl = document.createElement("div");
|
|
1000
1244
|
this.frontContentEl.className = "pocato-content pocato-front-content";
|
|
1001
1245
|
this.backContentEl = document.createElement("div");
|
|
1002
1246
|
this.backContentEl.className = "pocato-content pocato-back-content";
|
|
1003
|
-
this.frontEl.appendChild(this.
|
|
1247
|
+
this.frontEl.appendChild(this.frontCanvas);
|
|
1004
1248
|
this.frontEl.appendChild(this.frontContentEl);
|
|
1005
1249
|
this.backEl.appendChild(this.backContentEl);
|
|
1006
1250
|
this.rotatorEl.appendChild(this.backEl);
|
|
1007
1251
|
this.rotatorEl.appendChild(this.frontEl);
|
|
1008
1252
|
this.cardEl.appendChild(this.rotatorEl);
|
|
1009
1253
|
this.container.appendChild(this.cardEl);
|
|
1010
|
-
|
|
1011
|
-
|
|
1254
|
+
const { width, height } = this.container.getBoundingClientRect();
|
|
1255
|
+
const dpr = window.devicePixelRatio || 1;
|
|
1256
|
+
const pixelWidth = Math.floor(width * dpr);
|
|
1257
|
+
const pixelHeight = Math.floor(height * dpr);
|
|
1258
|
+
this.frontCanvas.width = pixelWidth;
|
|
1259
|
+
this.frontCanvas.height = pixelHeight;
|
|
1260
|
+
this.frontFace = new FaceRenderer(
|
|
1261
|
+
options.front.shader,
|
|
1262
|
+
pixelWidth,
|
|
1263
|
+
pixelHeight
|
|
1264
|
+
);
|
|
1265
|
+
this.backFace = new FaceRenderer(
|
|
1266
|
+
this.options.back?.shader,
|
|
1267
|
+
pixelWidth,
|
|
1268
|
+
pixelHeight
|
|
1269
|
+
);
|
|
1270
|
+
this.backCanvas = document.createElement("canvas");
|
|
1271
|
+
this.backCanvas.className = "pocato-canvas";
|
|
1272
|
+
this.backCanvas.width = pixelWidth;
|
|
1273
|
+
this.backCanvas.height = pixelHeight;
|
|
1274
|
+
this.backEl.insertBefore(this.backCanvas, this.backEl.firstChild);
|
|
1275
|
+
this.loadAllLayers();
|
|
1276
|
+
this.setupResizeObserver();
|
|
1277
|
+
this.setupIntersectionObserver();
|
|
1278
|
+
requestAnimationFrame(() => {
|
|
1279
|
+
this.cardEl.classList.remove("pocato-loading");
|
|
1280
|
+
this.cardEl.classList.add("pocato-ready");
|
|
1281
|
+
});
|
|
1282
|
+
}
|
|
1283
|
+
createFrontRenderer() {
|
|
1284
|
+
if (this.frontRenderer) return;
|
|
1285
|
+
const newCanvas = document.createElement("canvas");
|
|
1286
|
+
newCanvas.className = "pocato-canvas";
|
|
1287
|
+
this.frontEl.replaceChild(newCanvas, this.frontCanvas);
|
|
1288
|
+
this.frontCanvas = newCanvas;
|
|
1289
|
+
this.frontRenderer = new THREE4.WebGLRenderer({
|
|
1290
|
+
canvas: this.frontCanvas,
|
|
1291
|
+
alpha: true
|
|
1292
|
+
});
|
|
1293
|
+
this.frontRenderer.setPixelRatio(window.devicePixelRatio);
|
|
1294
|
+
const { width, height } = this.container.getBoundingClientRect();
|
|
1295
|
+
this.frontRenderer.setSize(width, height);
|
|
1296
|
+
}
|
|
1297
|
+
createBackRenderer() {
|
|
1298
|
+
if (this.backRenderer || !this.backCanvas || !this.backFace) return;
|
|
1299
|
+
const newCanvas = document.createElement("canvas");
|
|
1300
|
+
newCanvas.className = "pocato-canvas";
|
|
1301
|
+
this.backEl.replaceChild(newCanvas, this.backCanvas);
|
|
1302
|
+
this.backCanvas = newCanvas;
|
|
1303
|
+
this.backRenderer = new THREE4.WebGLRenderer({
|
|
1304
|
+
canvas: this.backCanvas,
|
|
1305
|
+
alpha: true
|
|
1306
|
+
});
|
|
1307
|
+
this.backRenderer.setPixelRatio(window.devicePixelRatio);
|
|
1308
|
+
const { width, height } = this.container.getBoundingClientRect();
|
|
1309
|
+
this.backRenderer.setSize(width, height);
|
|
1310
|
+
}
|
|
1311
|
+
disposeFrontRenderer() {
|
|
1312
|
+
if (!this.frontRenderer) return;
|
|
1313
|
+
this.frontRenderer.render(this.frontFace.scene, this.frontFace.camera);
|
|
1314
|
+
this.frontRenderer.dispose();
|
|
1315
|
+
this.frontRenderer = null;
|
|
1316
|
+
}
|
|
1317
|
+
disposeBackRenderer() {
|
|
1318
|
+
if (!this.backRenderer) return;
|
|
1319
|
+
if (this.backFace) {
|
|
1320
|
+
this.backRenderer.render(this.backFace.scene, this.backFace.camera);
|
|
1321
|
+
}
|
|
1322
|
+
this.backRenderer.dispose();
|
|
1323
|
+
this.backRenderer = null;
|
|
1324
|
+
}
|
|
1325
|
+
activate() {
|
|
1326
|
+
if (this.visible) return;
|
|
1327
|
+
this.visible = true;
|
|
1328
|
+
this.createFrontRenderer();
|
|
1329
|
+
if (this.backFace) this.createBackRenderer();
|
|
1330
|
+
this.clock.getDelta();
|
|
1331
|
+
this.startRenderLoop();
|
|
1332
|
+
}
|
|
1333
|
+
deactivate() {
|
|
1334
|
+
if (!this.visible) return;
|
|
1335
|
+
this.visible = false;
|
|
1336
|
+
this.stopRenderLoop();
|
|
1337
|
+
this.disposeFrontRenderer();
|
|
1338
|
+
this.disposeBackRenderer();
|
|
1339
|
+
}
|
|
1340
|
+
setupIntersectionObserver() {
|
|
1341
|
+
this.intersectionObserver = new IntersectionObserver(
|
|
1342
|
+
(entries) => {
|
|
1343
|
+
const entry = entries[0];
|
|
1344
|
+
if (!entry || this.destroyed) return;
|
|
1345
|
+
if (entry.isIntersecting) {
|
|
1346
|
+
this.activate();
|
|
1347
|
+
} else {
|
|
1348
|
+
this.deactivate();
|
|
1349
|
+
}
|
|
1350
|
+
},
|
|
1351
|
+
{ rootMargin: "100px" }
|
|
1352
|
+
// activate slightly before entering viewport
|
|
1353
|
+
);
|
|
1354
|
+
this.intersectionObserver.observe(this.cardEl);
|
|
1355
|
+
}
|
|
1356
|
+
async loadAllLayers() {
|
|
1357
|
+
const promises = [];
|
|
1358
|
+
if (this.options.front?.layers?.length) {
|
|
1359
|
+
promises.push(this.frontFace.loadLayers(this.options.front.layers, this.onError));
|
|
1360
|
+
}
|
|
1361
|
+
if (this.backFace && this.options.back?.layers?.length) {
|
|
1362
|
+
promises.push(this.backFace.loadLayers(this.options.back.layers, this.onError));
|
|
1363
|
+
}
|
|
1364
|
+
await Promise.all(promises);
|
|
1365
|
+
this.onReady?.();
|
|
1012
1366
|
}
|
|
1013
1367
|
injectStyles() {
|
|
1014
1368
|
const id = "pocato-styles";
|
|
@@ -1067,12 +1421,6 @@ var Renderer = class {
|
|
|
1067
1421
|
overflow: hidden;
|
|
1068
1422
|
background-color: #1a1a2e;
|
|
1069
1423
|
}
|
|
1070
|
-
.pocato-back-img {
|
|
1071
|
-
width: 100%;
|
|
1072
|
-
height: 100%;
|
|
1073
|
-
object-fit: cover;
|
|
1074
|
-
pointer-events: none;
|
|
1075
|
-
}
|
|
1076
1424
|
.pocato-canvas {
|
|
1077
1425
|
width: 100%;
|
|
1078
1426
|
height: 100%;
|
|
@@ -1092,134 +1440,83 @@ var Renderer = class {
|
|
|
1092
1440
|
`;
|
|
1093
1441
|
document.head.appendChild(style);
|
|
1094
1442
|
}
|
|
1095
|
-
init() {
|
|
1096
|
-
const { width, height } = this.container.getBoundingClientRect();
|
|
1097
|
-
this.scene = new THREE2.Scene();
|
|
1098
|
-
this.camera = new THREE2.Camera();
|
|
1099
|
-
this.webglRenderer = new THREE2.WebGLRenderer({
|
|
1100
|
-
canvas: this.canvas,
|
|
1101
|
-
alpha: true
|
|
1102
|
-
});
|
|
1103
|
-
this.webglRenderer.setPixelRatio(window.devicePixelRatio);
|
|
1104
|
-
this.webglRenderer.setSize(width, height);
|
|
1105
|
-
const pixelWidth = this.canvas.width;
|
|
1106
|
-
const pixelHeight = this.canvas.height;
|
|
1107
|
-
const uniforms = this.createUniforms(pixelWidth, pixelHeight);
|
|
1108
|
-
const rawFragmentShader = this.options.customShader ?? FRAG_SHADERS[this.options.type];
|
|
1109
|
-
const fragmentShader = resolveIncludes(rawFragmentShader);
|
|
1110
|
-
this.material = new THREE2.ShaderMaterial({
|
|
1111
|
-
vertexShader: resolveIncludes(common_vert_default),
|
|
1112
|
-
fragmentShader,
|
|
1113
|
-
uniforms,
|
|
1114
|
-
transparent: true
|
|
1115
|
-
});
|
|
1116
|
-
const geometry = new THREE2.PlaneGeometry(2, 2);
|
|
1117
|
-
this.mesh = new THREE2.Mesh(geometry, this.material);
|
|
1118
|
-
this.scene.add(this.mesh);
|
|
1119
|
-
this.loadTextures();
|
|
1120
|
-
this.setupResizeObserver();
|
|
1121
|
-
this.startRenderLoop();
|
|
1122
|
-
requestAnimationFrame(() => {
|
|
1123
|
-
this.cardEl.classList.remove("pocato-loading");
|
|
1124
|
-
this.cardEl.classList.add("pocato-ready");
|
|
1125
|
-
});
|
|
1126
|
-
}
|
|
1127
|
-
createUniforms(width, height) {
|
|
1128
|
-
return {
|
|
1129
|
-
uTime: { value: 0 },
|
|
1130
|
-
uResolution: { value: new THREE2.Vector2(width, height) },
|
|
1131
|
-
uMouse: { value: new THREE2.Vector2(0, 0) },
|
|
1132
|
-
uMove: { value: new THREE2.Vector2(0, 0) },
|
|
1133
|
-
uRotate: { value: new THREE2.Vector2(0, 0) },
|
|
1134
|
-
uCardOpacity: { value: 1 },
|
|
1135
|
-
uImgBase: { value: null },
|
|
1136
|
-
uImgPopup: { value: null },
|
|
1137
|
-
uImgMask: { value: null }
|
|
1138
|
-
};
|
|
1139
|
-
}
|
|
1140
|
-
loadTextures() {
|
|
1141
|
-
const loader = new THREE2.TextureLoader();
|
|
1142
|
-
const load = (url, uniform) => {
|
|
1143
|
-
if (!url) return;
|
|
1144
|
-
loader.load(
|
|
1145
|
-
url,
|
|
1146
|
-
(texture) => {
|
|
1147
|
-
this.textures.push(texture);
|
|
1148
|
-
if (this.material) {
|
|
1149
|
-
this.material.uniforms[uniform].value = texture;
|
|
1150
|
-
}
|
|
1151
|
-
},
|
|
1152
|
-
void 0,
|
|
1153
|
-
() => {
|
|
1154
|
-
this.onError?.(new Error(`Failed to load texture: ${url}`));
|
|
1155
|
-
}
|
|
1156
|
-
);
|
|
1157
|
-
};
|
|
1158
|
-
load(this.options.baseImage, "uImgBase");
|
|
1159
|
-
load(this.options.popupImage, "uImgPopup");
|
|
1160
|
-
load(this.options.maskImage, "uImgMask");
|
|
1161
|
-
this.onReady?.();
|
|
1162
|
-
}
|
|
1163
1443
|
setupResizeObserver() {
|
|
1164
1444
|
this.resizeObserver = new ResizeObserver((entries) => {
|
|
1165
1445
|
const entry = entries[0];
|
|
1166
1446
|
if (!entry) return;
|
|
1167
1447
|
const { width, height } = entry.contentRect;
|
|
1168
1448
|
if (width === 0 || height === 0) return;
|
|
1169
|
-
this.
|
|
1170
|
-
|
|
1171
|
-
|
|
1449
|
+
if (this.frontRenderer) {
|
|
1450
|
+
this.frontRenderer.setSize(width, height);
|
|
1451
|
+
}
|
|
1452
|
+
this.frontFace.updateResolution(this.frontCanvas.width, this.frontCanvas.height);
|
|
1453
|
+
if (this.backFace && this.backCanvas) {
|
|
1454
|
+
if (this.backRenderer) {
|
|
1455
|
+
this.backRenderer.setSize(width, height);
|
|
1456
|
+
}
|
|
1457
|
+
this.backFace.updateResolution(this.backCanvas.width, this.backCanvas.height);
|
|
1172
1458
|
}
|
|
1173
1459
|
});
|
|
1174
1460
|
this.resizeObserver.observe(this.container);
|
|
1175
1461
|
}
|
|
1176
1462
|
startRenderLoop() {
|
|
1463
|
+
if (this.rafId !== null) return;
|
|
1177
1464
|
const animate = () => {
|
|
1465
|
+
if (!this.visible || this.destroyed) return;
|
|
1178
1466
|
this.rafId = requestAnimationFrame(animate);
|
|
1179
|
-
if (!this.material || !this.scene || !this.camera || !this.webglRenderer) return;
|
|
1180
1467
|
const delta = this.clock.getDelta();
|
|
1181
|
-
this.material.uniforms.uTime.value += delta;
|
|
1182
|
-
|
|
1468
|
+
this.frontFace.material.uniforms.uTime.value += delta;
|
|
1469
|
+
if (this.frontRenderer) {
|
|
1470
|
+
this.frontRenderer.render(this.frontFace.scene, this.frontFace.camera);
|
|
1471
|
+
}
|
|
1472
|
+
if (this.backFace && this.backRenderer) {
|
|
1473
|
+
this.backFace.material.uniforms.uTime.value += delta;
|
|
1474
|
+
this.backRenderer.render(this.backFace.scene, this.backFace.camera);
|
|
1475
|
+
}
|
|
1183
1476
|
};
|
|
1184
1477
|
this.rafId = requestAnimationFrame(animate);
|
|
1185
1478
|
}
|
|
1479
|
+
stopRenderLoop() {
|
|
1480
|
+
if (this.rafId !== null) {
|
|
1481
|
+
cancelAnimationFrame(this.rafId);
|
|
1482
|
+
this.rafId = null;
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1186
1485
|
updateUniforms(updates) {
|
|
1187
|
-
if (!this.material) return;
|
|
1188
|
-
const u = this.material.uniforms;
|
|
1189
1486
|
if (updates.rotate) {
|
|
1190
1487
|
this.rotatorEl.style.setProperty("--pocato-rotate-x", `${updates.rotate.x}deg`);
|
|
1191
1488
|
this.rotatorEl.style.setProperty("--pocato-rotate-y", `${updates.rotate.y}deg`);
|
|
1192
|
-
u.uRotate.value.set(
|
|
1193
|
-
updates.rotate.x * (Math.PI / 180),
|
|
1194
|
-
updates.rotate.y * (Math.PI / 180)
|
|
1195
|
-
);
|
|
1196
1489
|
}
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1490
|
+
this.frontFace.updateUniforms({
|
|
1491
|
+
rotate: updates.rotate,
|
|
1492
|
+
mouse: updates.mouse,
|
|
1493
|
+
move: updates.move,
|
|
1494
|
+
opacity: updates.opacity
|
|
1495
|
+
});
|
|
1496
|
+
if (this.backFace) {
|
|
1497
|
+
this.backFace.updateUniforms({
|
|
1498
|
+
rotate: updates.rotate,
|
|
1499
|
+
mouse: updates.mouse,
|
|
1500
|
+
move: updates.move,
|
|
1501
|
+
opacity: updates.opacity
|
|
1502
|
+
});
|
|
1503
|
+
}
|
|
1200
1504
|
}
|
|
1201
1505
|
updateShader(fragmentShader) {
|
|
1202
|
-
|
|
1203
|
-
this.material.fragmentShader = resolveIncludes(fragmentShader);
|
|
1204
|
-
this.material.needsUpdate = true;
|
|
1506
|
+
this.frontFace.updateShader(fragmentShader);
|
|
1205
1507
|
}
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
const load = (url, uniform) => {
|
|
1209
|
-
if (!url) return;
|
|
1210
|
-
loader.load(url, (texture) => {
|
|
1211
|
-
this.textures.push(texture);
|
|
1212
|
-
if (this.material) {
|
|
1213
|
-
this.material.uniforms[uniform].value = texture;
|
|
1214
|
-
}
|
|
1215
|
-
});
|
|
1216
|
-
};
|
|
1217
|
-
if (options.baseImage) load(options.baseImage, "uImgBase");
|
|
1218
|
-
if (options.popupImage) load(options.popupImage, "uImgPopup");
|
|
1219
|
-
if (options.maskImage) load(options.maskImage, "uImgMask");
|
|
1508
|
+
updateBackShader(fragmentShader) {
|
|
1509
|
+
this.backFace?.updateShader(fragmentShader);
|
|
1220
1510
|
}
|
|
1221
|
-
|
|
1222
|
-
|
|
1511
|
+
async updateLayers(frontLayers, backLayers) {
|
|
1512
|
+
const promises = [];
|
|
1513
|
+
if (frontLayers) {
|
|
1514
|
+
promises.push(this.frontFace.loadLayers(frontLayers, this.onError));
|
|
1515
|
+
}
|
|
1516
|
+
if (backLayers && this.backFace) {
|
|
1517
|
+
promises.push(this.backFace.loadLayers(backLayers, this.onError));
|
|
1518
|
+
}
|
|
1519
|
+
await Promise.all(promises);
|
|
1223
1520
|
}
|
|
1224
1521
|
getRotatorEl() {
|
|
1225
1522
|
return this.rotatorEl;
|
|
@@ -1231,20 +1528,20 @@ var Renderer = class {
|
|
|
1231
1528
|
return this.backContentEl;
|
|
1232
1529
|
}
|
|
1233
1530
|
destroy() {
|
|
1234
|
-
|
|
1531
|
+
this.destroyed = true;
|
|
1532
|
+
this.stopRenderLoop();
|
|
1235
1533
|
this.resizeObserver?.disconnect();
|
|
1236
|
-
this.
|
|
1237
|
-
|
|
1238
|
-
this.
|
|
1239
|
-
this.
|
|
1534
|
+
this.intersectionObserver?.disconnect();
|
|
1535
|
+
cleanupPendingVideos();
|
|
1536
|
+
this.frontFace.destroy();
|
|
1537
|
+
this.frontRenderer?.dispose();
|
|
1538
|
+
if (this.backFace) {
|
|
1539
|
+
this.backFace.destroy();
|
|
1540
|
+
this.backRenderer?.dispose();
|
|
1541
|
+
}
|
|
1240
1542
|
if (this.cardEl.parentNode) {
|
|
1241
1543
|
this.cardEl.parentNode.removeChild(this.cardEl);
|
|
1242
1544
|
}
|
|
1243
|
-
this.scene = null;
|
|
1244
|
-
this.camera = null;
|
|
1245
|
-
this.webglRenderer = null;
|
|
1246
|
-
this.material = null;
|
|
1247
|
-
this.mesh = null;
|
|
1248
1545
|
}
|
|
1249
1546
|
};
|
|
1250
1547
|
|
|
@@ -1296,6 +1593,7 @@ function spring(initialValue, onChange, opts = {}) {
|
|
|
1296
1593
|
invMass: 1
|
|
1297
1594
|
};
|
|
1298
1595
|
let currentResolve = null;
|
|
1596
|
+
let currentTask = null;
|
|
1299
1597
|
const s = {
|
|
1300
1598
|
stiffness,
|
|
1301
1599
|
damping,
|
|
@@ -1305,10 +1603,16 @@ function spring(initialValue, onChange, opts = {}) {
|
|
|
1305
1603
|
},
|
|
1306
1604
|
set(value, updateOpts) {
|
|
1307
1605
|
ctx.target = value;
|
|
1606
|
+
if (currentTask) {
|
|
1607
|
+
tasks.delete(currentTask);
|
|
1608
|
+
currentTask = null;
|
|
1609
|
+
}
|
|
1308
1610
|
if (updateOpts?.hard) {
|
|
1309
1611
|
ctx.val = value;
|
|
1310
1612
|
ctx.lastVal = value;
|
|
1311
1613
|
onChange(value);
|
|
1614
|
+
currentResolve?.();
|
|
1615
|
+
currentResolve = null;
|
|
1312
1616
|
return Promise.resolve();
|
|
1313
1617
|
}
|
|
1314
1618
|
if (updateOpts?.soft) {
|
|
@@ -1317,7 +1621,7 @@ function spring(initialValue, onChange, opts = {}) {
|
|
|
1317
1621
|
}
|
|
1318
1622
|
return new Promise((resolve) => {
|
|
1319
1623
|
currentResolve = resolve;
|
|
1320
|
-
|
|
1624
|
+
const task = () => {
|
|
1321
1625
|
const invMassTarget = 1;
|
|
1322
1626
|
ctx.invMass = Math.min(ctx.invMass + 0.02, invMassTarget);
|
|
1323
1627
|
const moving = tickSpring(ctx, s.stiffness, s.damping, s.precision);
|
|
@@ -1325,9 +1629,12 @@ function spring(initialValue, onChange, opts = {}) {
|
|
|
1325
1629
|
if (!moving) {
|
|
1326
1630
|
currentResolve?.();
|
|
1327
1631
|
currentResolve = null;
|
|
1632
|
+
currentTask = null;
|
|
1328
1633
|
}
|
|
1329
1634
|
return moving;
|
|
1330
|
-
}
|
|
1635
|
+
};
|
|
1636
|
+
currentTask = task;
|
|
1637
|
+
scheduleTask(task);
|
|
1331
1638
|
});
|
|
1332
1639
|
}
|
|
1333
1640
|
};
|
|
@@ -1471,6 +1778,7 @@ var InteractionHandler = class {
|
|
|
1471
1778
|
this.dblClickTimer = null;
|
|
1472
1779
|
this.activePointerId = null;
|
|
1473
1780
|
this._flipSpeed = 1;
|
|
1781
|
+
this._flipId = 0;
|
|
1474
1782
|
this.flipped = initialFlipped;
|
|
1475
1783
|
this.springRotate = springVec2({ x: 0, y: 0 }, (v) => {
|
|
1476
1784
|
callbacks.onRotate(v);
|
|
@@ -1615,11 +1923,16 @@ var InteractionHandler = class {
|
|
|
1615
1923
|
flip(flipped) {
|
|
1616
1924
|
this.flipped = flipped ?? !this.flipped;
|
|
1617
1925
|
const targetX = this.flipped ? 180 : 0;
|
|
1926
|
+
const current = this.springRotate.get();
|
|
1927
|
+
this.springRotate.set({ x: current.x, y: current.y }, { hard: true });
|
|
1928
|
+
const flipId = ++this._flipId;
|
|
1618
1929
|
this.springRotate.stiffness = SPRING_FLIP.stiffness * this._flipSpeed;
|
|
1619
1930
|
this.springRotate.damping = SPRING_FLIP.damping;
|
|
1620
1931
|
this.springRotate.set({ x: targetX, y: 0 }).then(() => {
|
|
1621
|
-
this.
|
|
1622
|
-
|
|
1932
|
+
if (this._flipId === flipId) {
|
|
1933
|
+
this.springRotate.stiffness = SPRING_INTERACT.stiffness;
|
|
1934
|
+
this.springRotate.damping = SPRING_INTERACT.damping;
|
|
1935
|
+
}
|
|
1623
1936
|
});
|
|
1624
1937
|
this.callbacks.onFlip(this.flipped);
|
|
1625
1938
|
return this.flipped;
|
|
@@ -1644,6 +1957,9 @@ var InteractionHandler = class {
|
|
|
1644
1957
|
simulateEndInteract() {
|
|
1645
1958
|
this.endInteract();
|
|
1646
1959
|
}
|
|
1960
|
+
isInteracting() {
|
|
1961
|
+
return this.interacting;
|
|
1962
|
+
}
|
|
1647
1963
|
isFlipped() {
|
|
1648
1964
|
return this.flipped;
|
|
1649
1965
|
}
|
|
@@ -1772,12 +2088,8 @@ var PocaCard = class extends EventEmitter {
|
|
|
1772
2088
|
this.renderer = new Renderer(
|
|
1773
2089
|
container,
|
|
1774
2090
|
{
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
popupImage: options.popupImage,
|
|
1778
|
-
maskImage: options.maskImage,
|
|
1779
|
-
backImage: options.backImage,
|
|
1780
|
-
customShader: options.customShader
|
|
2091
|
+
front: options.front,
|
|
2092
|
+
back: options.back
|
|
1781
2093
|
},
|
|
1782
2094
|
(error) => this.emit("error", error),
|
|
1783
2095
|
() => this.emit("ready")
|
|
@@ -1792,11 +2104,19 @@ var PocaCard = class extends EventEmitter {
|
|
|
1792
2104
|
onMoveDelta: (delta) => this.renderer.updateUniforms({ move: delta }),
|
|
1793
2105
|
onDistFromCenter: (_dist) => {
|
|
1794
2106
|
},
|
|
1795
|
-
onFlip: (flipped) =>
|
|
2107
|
+
onFlip: (flipped) => {
|
|
2108
|
+
this.emit("flip", flipped);
|
|
2109
|
+
}
|
|
1796
2110
|
},
|
|
1797
2111
|
options.flippable ?? false,
|
|
1798
2112
|
options.initialFlipped ?? false
|
|
1799
2113
|
);
|
|
2114
|
+
this.renderer.getRotatorEl().addEventListener("pointerdown", () => {
|
|
2115
|
+
if (this.wiggleController) {
|
|
2116
|
+
this.wiggleController.stop();
|
|
2117
|
+
this.wiggleController = null;
|
|
2118
|
+
}
|
|
2119
|
+
});
|
|
1800
2120
|
if (options.flipSpeed != null) {
|
|
1801
2121
|
this.interaction.flipSpeed = options.flipSpeed;
|
|
1802
2122
|
}
|
|
@@ -1811,6 +2131,7 @@ var PocaCard = class extends EventEmitter {
|
|
|
1811
2131
|
this.interaction.flip();
|
|
1812
2132
|
}
|
|
1813
2133
|
wiggle() {
|
|
2134
|
+
if (this.interaction.isInteracting()) return;
|
|
1814
2135
|
this.wiggleController?.stop();
|
|
1815
2136
|
this.interaction.startSyntheticInteraction();
|
|
1816
2137
|
this.wiggleController = wiggle(0, 0, 100, (pos, done) => {
|
|
@@ -1831,11 +2152,14 @@ var PocaCard = class extends EventEmitter {
|
|
|
1831
2152
|
if (options.flipSpeed != null) {
|
|
1832
2153
|
this.interaction.flipSpeed = options.flipSpeed;
|
|
1833
2154
|
}
|
|
1834
|
-
if (options.
|
|
1835
|
-
this.renderer.updateShader(options.
|
|
2155
|
+
if (options.front?.shader) {
|
|
2156
|
+
this.renderer.updateShader(options.front.shader);
|
|
2157
|
+
}
|
|
2158
|
+
if (options.back?.shader) {
|
|
2159
|
+
this.renderer.updateBackShader(options.back.shader);
|
|
1836
2160
|
}
|
|
1837
|
-
if (options.
|
|
1838
|
-
this.renderer.
|
|
2161
|
+
if (options.front?.layers || options.back?.layers) {
|
|
2162
|
+
this.renderer.updateLayers(options.front?.layers, options.back?.layers);
|
|
1839
2163
|
}
|
|
1840
2164
|
}
|
|
1841
2165
|
destroy() {
|
|
@@ -1845,7 +2169,24 @@ var PocaCard = class extends EventEmitter {
|
|
|
1845
2169
|
this.removeAllListeners();
|
|
1846
2170
|
}
|
|
1847
2171
|
};
|
|
2172
|
+
|
|
2173
|
+
// src/shaders/registry.ts
|
|
2174
|
+
var builtinShaders = {
|
|
2175
|
+
glare: glare_frag_default,
|
|
2176
|
+
"glare-3d": glare_3d_frag_default,
|
|
2177
|
+
holo: holo_frag_default,
|
|
2178
|
+
"holo-3d": holo_3d_frag_default,
|
|
2179
|
+
snowfall: snowfall_frag_default,
|
|
2180
|
+
"snowfall-3d": snowfall_3d_frag_default,
|
|
2181
|
+
blur: blur_frag_default,
|
|
2182
|
+
"blur-3d": blur_3d_frag_default,
|
|
2183
|
+
brush: brush_frag_default,
|
|
2184
|
+
"brush-3d": brush_3d_frag_default
|
|
2185
|
+
};
|
|
1848
2186
|
export {
|
|
1849
|
-
|
|
2187
|
+
FRAG_SHADERS,
|
|
2188
|
+
PocaCard,
|
|
2189
|
+
builtinShaders,
|
|
2190
|
+
resolveIncludes
|
|
1850
2191
|
};
|
|
1851
2192
|
//# sourceMappingURL=index.js.map
|