@wave3d/core 0.3.0 → 0.4.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.
@@ -1,5 +1,5 @@
1
1
  import { ensureStudioConfig } from "../config/model.js";
2
- import { fragmentShader, lineFragmentShader, postFragmentShader, postVertexShader, vertexShader } from "./shaders.js";
2
+ import { ditherFragmentShader, fragmentShader, halftoneCmykFragmentShader, halftoneFragmentShader, heatmapFragmentShader, innerLightFragmentShader, lineFragmentShader, paperTextureFragmentShader, postFragmentShader, postVertexShader, vertexShader } from "./shaders.js";
3
3
  import { WaveGeometry } from "./WaveGeometry.js";
4
4
  import { InteractionController, SCENE_APPLIERS, WAVE_APPLIERS, anyPointerFxActive, interactionActive, wavePointerFxActive, waveRipplesActive } from "./interaction.js";
5
5
  import { PALETTE_MAPS, buildBackgroundGradientCanvas, buildBackgroundImageCanvas, buildBackgroundMeshCanvas, buildPaletteTexture, canvasToTexture, configurePaletteTexture, drawBackgroundMediaFrame, loadPaletteImage, paletteMapCanvas, paletteSignature } from "./palette.js";
@@ -149,6 +149,12 @@ var WaveRenderer = class {
149
149
  postPass;
150
150
  /** Optional bloom pass — created lazily when bloomStrength first goes >0, removed at 0. */
151
151
  bloomPass;
152
+ ditherPass;
153
+ innerLightPass;
154
+ halftonePass;
155
+ heatmapPass;
156
+ paperTexturePass;
157
+ halftoneCmykPass;
152
158
  container;
153
159
  respectReducedMotion;
154
160
  skipIntroRamp;
@@ -372,6 +378,7 @@ var WaveRenderer = class {
372
378
  uPointerPush: { value: 0 },
373
379
  uPointerWake: { value: 0 },
374
380
  uPointerVel: { value: new THREE.Vector2(0, 0) },
381
+ uShapeFlow: { value: 0 },
375
382
  uPointerThin: { value: 0 },
376
383
  uPointerHue: { value: 0 },
377
384
  uPointerLighten: { value: 0 },
@@ -605,6 +612,7 @@ var WaveRenderer = class {
605
612
  u.uOpacity.value = sc.opacity;
606
613
  });
607
614
  const sharedRadius = (this.config.interaction?.radius ?? .3) * 2;
615
+ const sharedFlow = this.config.interaction?.ribbonFlow ?? .8;
608
616
  this.waves.forEach((wave, i) => {
609
617
  const sc = this.config.waves[i] ?? this.config.waves[this.config.waves.length - 1];
610
618
  if (!wavePointerFxActive(this.config, sc)) return;
@@ -618,6 +626,7 @@ var WaveRenderer = class {
618
626
  u.uPointerHue.value = h?.hueShift ?? 0;
619
627
  u.uPointerLighten.value = h?.lighten ?? 0;
620
628
  u.uPointerRipple.value = sc.interaction?.press?.ripple ?? 0;
629
+ u.uShapeFlow.value = sharedFlow;
621
630
  });
622
631
  this.updatePaletteTextures();
623
632
  this.syncVideoPlayback();
@@ -890,6 +899,12 @@ var WaveRenderer = class {
890
899
  u.uGrainAmount.value = this.config.grain;
891
900
  u.uBlurSamples.value = Math.round(this.config.blurSamples ?? 6);
892
901
  this.applyBloom();
902
+ this.applyInnerLight();
903
+ this.applyHalftone();
904
+ this.applyHeatmap();
905
+ this.applyHalftoneCmyk();
906
+ this.applyPaperTexture();
907
+ this.applyDither();
893
908
  }
894
909
  /** Insert / tune / remove the bloom pass. strength 0 removes it from the composer entirely, so
895
910
  * cost and pixels are identical to bloom-off; the pass (and its mip-chain render targets) is
@@ -912,6 +927,172 @@ var WaveRenderer = class {
912
927
  this.bloomPass = void 0;
913
928
  }
914
929
  }
930
+ /** Insert / tune / remove the dithering pass — a self-contained "layered" post shader (an ordered
931
+ * Bayer dither, in the spirit of paper-design/shaders). Like bloom, dither 0 removes the pass
932
+ * entirely so cost and pixels match dither-off, and it's created lazily on first enable. It is
933
+ * appended AFTER OutputPass so it runs last and quantizes display-space colour (tone-mapped +
934
+ * sRGB) — dithering the linear composer buffer would crush the steps in the shadows. */
935
+ applyDither() {
936
+ const strength = this.config.dither ?? 0;
937
+ if (strength > 0) {
938
+ if (!this.ditherPass) {
939
+ this.ditherPass = new ShaderPass({
940
+ uniforms: {
941
+ tDiffuse: { value: null },
942
+ uResolution: { value: this.renderer.getDrawingBufferSize(new THREE.Vector2()) },
943
+ uDitherStrength: { value: strength },
944
+ uDitherScale: { value: this.config.ditherScale ?? 2 },
945
+ uDitherSteps: { value: this.config.ditherSteps ?? 4 }
946
+ },
947
+ vertexShader: postVertexShader,
948
+ fragmentShader: ditherFragmentShader
949
+ });
950
+ this.composer.addPass(this.ditherPass);
951
+ }
952
+ const u = this.ditherPass.uniforms;
953
+ u.uDitherStrength.value = strength;
954
+ u.uDitherScale.value = Math.max(1, this.config.ditherScale ?? 2);
955
+ u.uDitherSteps.value = Math.max(2, Math.round(this.config.ditherSteps ?? 4));
956
+ } else if (this.ditherPass) {
957
+ this.composer.removePass(this.ditherPass);
958
+ this.ditherPass.dispose();
959
+ this.ditherPass = void 0;
960
+ }
961
+ }
962
+ /** Insert / tune / remove the innerLight pass — volumetric light streaks scattered from the bright
963
+ * wave toward a light point (innerLightX/Y in UV). Scene zone (index 1) so it scatters the raw wave
964
+ * like bloom. innerLight 0 removes the pass entirely; created lazily on first enable. */
965
+ applyInnerLight() {
966
+ const strength = this.config.innerLight ?? 0;
967
+ if (strength > 0) {
968
+ const cx = this.config.innerLightX ?? .5;
969
+ const cy = this.config.innerLightY ?? .15;
970
+ if (!this.innerLightPass) {
971
+ this.innerLightPass = new ShaderPass({
972
+ uniforms: {
973
+ tDiffuse: { value: null },
974
+ uInnerLight: { value: strength },
975
+ uInnerLightDensity: { value: this.config.innerLightDensity ?? .5 },
976
+ uInnerLightDecay: { value: this.config.innerLightDecay ?? .95 },
977
+ uInnerLightCenter: { value: new THREE.Vector2(cx, cy) }
978
+ },
979
+ vertexShader: postVertexShader,
980
+ fragmentShader: innerLightFragmentShader
981
+ });
982
+ this.composer.insertPass(this.innerLightPass, 1);
983
+ }
984
+ const u = this.innerLightPass.uniforms;
985
+ u.uInnerLight.value = strength;
986
+ u.uInnerLightDensity.value = this.config.innerLightDensity ?? .5;
987
+ u.uInnerLightDecay.value = this.config.innerLightDecay ?? .95;
988
+ u.uInnerLightCenter.value.set(cx, cy);
989
+ } else if (this.innerLightPass) {
990
+ this.composer.removePass(this.innerLightPass);
991
+ this.innerLightPass.dispose();
992
+ this.innerLightPass = void 0;
993
+ }
994
+ }
995
+ /** Insert / tune / remove the halftone pass — a rotated dot screen (dot size scales with local
996
+ * brightness) over the finished image. halftone 0 removes the pass; created lazily on enable. */
997
+ applyHalftone() {
998
+ const strength = this.config.halftone ?? 0;
999
+ if (strength > 0) {
1000
+ if (!this.halftonePass) {
1001
+ this.halftonePass = new ShaderPass({
1002
+ uniforms: {
1003
+ tDiffuse: { value: null },
1004
+ uResolution: { value: this.renderer.getDrawingBufferSize(new THREE.Vector2()) },
1005
+ uHalftone: { value: strength },
1006
+ uHalftoneCell: { value: this.config.halftoneCell ?? 6 },
1007
+ uHalftoneAngle: { value: this.config.halftoneAngle ?? .4 }
1008
+ },
1009
+ vertexShader: postVertexShader,
1010
+ fragmentShader: halftoneFragmentShader
1011
+ });
1012
+ this.composer.addPass(this.halftonePass);
1013
+ }
1014
+ const u = this.halftonePass.uniforms;
1015
+ u.uHalftone.value = strength;
1016
+ u.uHalftoneCell.value = Math.max(2, this.config.halftoneCell ?? 6);
1017
+ u.uHalftoneAngle.value = this.config.halftoneAngle ?? .4;
1018
+ } else if (this.halftonePass) {
1019
+ this.composer.removePass(this.halftonePass);
1020
+ this.halftonePass.dispose();
1021
+ this.halftonePass = void 0;
1022
+ }
1023
+ }
1024
+ /** Heatmap: recolour the final image by luminance → thermal palette. Finish zone. */
1025
+ applyHeatmap() {
1026
+ const strength = this.config.heatmap ?? 0;
1027
+ if (strength > 0) {
1028
+ if (!this.heatmapPass) {
1029
+ this.heatmapPass = new ShaderPass({
1030
+ uniforms: {
1031
+ tDiffuse: { value: null },
1032
+ uHeatmap: { value: strength }
1033
+ },
1034
+ vertexShader: postVertexShader,
1035
+ fragmentShader: heatmapFragmentShader
1036
+ });
1037
+ this.composer.addPass(this.heatmapPass);
1038
+ }
1039
+ this.heatmapPass.uniforms.uHeatmap.value = strength;
1040
+ } else if (this.heatmapPass) {
1041
+ this.composer.removePass(this.heatmapPass);
1042
+ this.heatmapPass.dispose();
1043
+ this.heatmapPass = void 0;
1044
+ }
1045
+ }
1046
+ /** Paper texture: fibrous substrate shading multiplied over the image. Finish zone. */
1047
+ applyPaperTexture() {
1048
+ const strength = this.config.paperTexture ?? 0;
1049
+ if (strength > 0) {
1050
+ if (!this.paperTexturePass) {
1051
+ this.paperTexturePass = new ShaderPass({
1052
+ uniforms: {
1053
+ tDiffuse: { value: null },
1054
+ uPaper: { value: strength },
1055
+ uPaperScale: { value: this.config.paperTextureScale ?? 2 }
1056
+ },
1057
+ vertexShader: postVertexShader,
1058
+ fragmentShader: paperTextureFragmentShader
1059
+ });
1060
+ this.composer.addPass(this.paperTexturePass);
1061
+ }
1062
+ const u = this.paperTexturePass.uniforms;
1063
+ u.uPaper.value = strength;
1064
+ u.uPaperScale.value = Math.max(.5, this.config.paperTextureScale ?? 2);
1065
+ } else if (this.paperTexturePass) {
1066
+ this.composer.removePass(this.paperTexturePass);
1067
+ this.paperTexturePass.dispose();
1068
+ this.paperTexturePass = void 0;
1069
+ }
1070
+ }
1071
+ /** CMYK halftone: four rotated dot screens (cyan/magenta/yellow/black). Finish zone. */
1072
+ applyHalftoneCmyk() {
1073
+ const strength = this.config.halftoneCmyk ?? 0;
1074
+ if (strength > 0) {
1075
+ if (!this.halftoneCmykPass) {
1076
+ this.halftoneCmykPass = new ShaderPass({
1077
+ uniforms: {
1078
+ tDiffuse: { value: null },
1079
+ uHalftoneCmyk: { value: strength },
1080
+ uHalftoneCmykCell: { value: this.config.halftoneCmykCell ?? 6 }
1081
+ },
1082
+ vertexShader: postVertexShader,
1083
+ fragmentShader: halftoneCmykFragmentShader
1084
+ });
1085
+ this.composer.addPass(this.halftoneCmykPass);
1086
+ }
1087
+ const u = this.halftoneCmykPass.uniforms;
1088
+ u.uHalftoneCmyk.value = strength;
1089
+ u.uHalftoneCmykCell.value = Math.max(2, this.config.halftoneCmykCell ?? 6);
1090
+ } else if (this.halftoneCmykPass) {
1091
+ this.composer.removePass(this.halftoneCmykPass);
1092
+ this.halftoneCmykPass.dispose();
1093
+ this.halftoneCmykPass = void 0;
1094
+ }
1095
+ }
915
1096
  onResize = () => {
916
1097
  this.resize();
917
1098
  };
@@ -944,6 +1125,8 @@ var WaveRenderer = class {
944
1125
  const dw = w * dpr;
945
1126
  const dh = h * dpr;
946
1127
  this.postPass.uniforms.uResolution.value.set(dw, dh);
1128
+ if (this.ditherPass) this.ditherPass.uniforms.uResolution.value.set(dw, dh);
1129
+ if (this.halftonePass) this.halftonePass.uniforms.uResolution.value.set(dw, dh);
947
1130
  for (const s of this.waves) s.material.uniforms.uResolution.value.set(dw, dh);
948
1131
  this.camera.left = -dw / 2;
949
1132
  this.camera.right = dw / 2;
@@ -1343,6 +1526,12 @@ var WaveRenderer = class {
1343
1526
  s.palette.dispose();
1344
1527
  }
1345
1528
  this.bloomPass?.dispose();
1529
+ this.ditherPass?.dispose();
1530
+ this.innerLightPass?.dispose();
1531
+ this.halftonePass?.dispose();
1532
+ this.heatmapPass?.dispose();
1533
+ this.paperTexturePass?.dispose();
1534
+ this.halftoneCmykPass?.dispose();
1346
1535
  this.composer.dispose();
1347
1536
  this.renderer.dispose();
1348
1537
  this.renderer.domElement.remove();