@3driseai/3drise-viewer 0.1.29 → 0.1.30

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.
Files changed (164) hide show
  1. package/dist/animationsV2/ClipsRuntime.d.ts +4 -0
  2. package/dist/animationsV2/ClipsRuntime.d.ts.map +1 -1
  3. package/dist/animationsV2/ClipsRuntime.js +66 -2
  4. package/dist/animationsV2/clipUtils.js +1 -1
  5. package/dist/animationsV2/sequenceUtils.d.ts +5 -0
  6. package/dist/animationsV2/sequenceUtils.d.ts.map +1 -1
  7. package/dist/animationsV2/sequenceUtils.js +10 -0
  8. package/dist/animationsV2/types.d.ts +6 -0
  9. package/dist/animationsV2/types.d.ts.map +1 -1
  10. package/dist/components/EffectsGenerator.d.ts.map +1 -1
  11. package/dist/components/EffectsGenerator.js +43 -38
  12. package/dist/components/GeometryShape.d.ts.map +1 -1
  13. package/dist/components/GeometryShape.js +22 -2
  14. package/dist/components/ObjectNode.d.ts.map +1 -1
  15. package/dist/components/ObjectNode.js +6 -17
  16. package/dist/components/ParticlesGenerator.d.ts.map +1 -1
  17. package/dist/components/ParticlesGenerator.js +44 -24
  18. package/dist/components/PickVolume.d.ts +17 -0
  19. package/dist/components/PickVolume.d.ts.map +1 -0
  20. package/dist/components/PickVolume.js +20 -0
  21. package/dist/components/SkyStars.d.ts.map +1 -1
  22. package/dist/components/SkyStars.js +83 -2
  23. package/dist/components/TextGenerator.d.ts.map +1 -1
  24. package/dist/components/TextGenerator.js +3 -2
  25. package/dist/components/index.d.ts +1 -8
  26. package/dist/components/index.d.ts.map +1 -1
  27. package/dist/components/index.js +1 -4
  28. package/dist/components/particles/BioluminescentWakeParticles.d.ts +5 -0
  29. package/dist/components/particles/BioluminescentWakeParticles.d.ts.map +1 -1
  30. package/dist/components/particles/BioluminescentWakeParticles.js +2 -1
  31. package/dist/components/particles/BrownianDustParticles.d.ts +5 -0
  32. package/dist/components/particles/BrownianDustParticles.d.ts.map +1 -1
  33. package/dist/components/particles/BrownianDustParticles.js +2 -0
  34. package/dist/components/particles/ChladniSandParticles.d.ts +5 -0
  35. package/dist/components/particles/ChladniSandParticles.d.ts.map +1 -1
  36. package/dist/components/particles/ChladniSandParticles.js +2 -1
  37. package/dist/components/particles/ElectronOrbitalsParticles.d.ts +5 -0
  38. package/dist/components/particles/ElectronOrbitalsParticles.d.ts.map +1 -1
  39. package/dist/components/particles/ElectronOrbitalsParticles.js +2 -1
  40. package/dist/components/particles/FaradayPowderParticles.d.ts +5 -0
  41. package/dist/components/particles/FaradayPowderParticles.d.ts.map +1 -1
  42. package/dist/components/particles/FaradayPowderParticles.js +2 -1
  43. package/dist/components/particles/GrinderSparksParticles.d.ts +5 -0
  44. package/dist/components/particles/GrinderSparksParticles.d.ts.map +1 -1
  45. package/dist/components/particles/GrinderSparksParticles.js +2 -1
  46. package/dist/components/particles/MagneticBottleParticles.d.ts +5 -0
  47. package/dist/components/particles/MagneticBottleParticles.d.ts.map +1 -1
  48. package/dist/components/particles/MagneticBottleParticles.js +2 -1
  49. package/dist/components/particles/NeonRainParticles.d.ts +5 -0
  50. package/dist/components/particles/NeonRainParticles.d.ts.map +1 -1
  51. package/dist/components/particles/NeonRainParticles.js +2 -1
  52. package/dist/components/particles/ShapeBurstParticles.d.ts +5 -0
  53. package/dist/components/particles/ShapeBurstParticles.d.ts.map +1 -1
  54. package/dist/components/particles/ShapeBurstParticles.js +2 -1
  55. package/dist/components/particles/ShapeFlowParticles.d.ts +5 -0
  56. package/dist/components/particles/ShapeFlowParticles.d.ts.map +1 -1
  57. package/dist/components/particles/ShapeFlowParticles.js +2 -1
  58. package/dist/components/particles/TidalStreamParticles.d.ts +5 -0
  59. package/dist/components/particles/TidalStreamParticles.d.ts.map +1 -1
  60. package/dist/components/particles/TidalStreamParticles.js +2 -0
  61. package/dist/components/text/PlainText.d.ts +15 -0
  62. package/dist/components/text/PlainText.d.ts.map +1 -0
  63. package/dist/components/text/PlainText.js +4 -0
  64. package/dist/components/text/TextObjectView.d.ts +3 -1
  65. package/dist/components/text/TextObjectView.d.ts.map +1 -1
  66. package/dist/components/text/TextObjectView.js +9 -3
  67. package/dist/components/text/engine/looks/index.d.ts.map +1 -1
  68. package/dist/components/text/engine/looks/index.js +2 -1
  69. package/dist/components/text/engine/looks/plain.d.ts +146 -0
  70. package/dist/components/text/engine/looks/plain.d.ts.map +1 -0
  71. package/dist/components/text/engine/looks/plain.js +104 -0
  72. package/dist/components/text/fonts/bebas-neue.outline.d.ts +3 -0
  73. package/dist/components/text/fonts/bebas-neue.outline.d.ts.map +1 -0
  74. package/dist/components/text/fonts/bebas-neue.outline.js +4 -0
  75. package/dist/components/text/fonts/bebas-neue.strokes.d.ts +3 -0
  76. package/dist/components/text/fonts/bebas-neue.strokes.d.ts.map +1 -0
  77. package/dist/components/text/fonts/bebas-neue.strokes.js +4 -0
  78. package/dist/components/text/fonts/index.d.ts +1 -1
  79. package/dist/components/text/fonts/index.d.ts.map +1 -1
  80. package/dist/components/text/fonts/index.js +15 -1
  81. package/dist/components/text/fonts/inter.outline.d.ts +3 -0
  82. package/dist/components/text/fonts/inter.outline.d.ts.map +1 -0
  83. package/dist/components/text/fonts/inter.outline.js +4 -0
  84. package/dist/components/text/fonts/inter.strokes.d.ts +3 -0
  85. package/dist/components/text/fonts/inter.strokes.d.ts.map +1 -0
  86. package/dist/components/text/fonts/inter.strokes.js +4 -0
  87. package/dist/components/text/fonts/lora.outline.d.ts +3 -0
  88. package/dist/components/text/fonts/lora.outline.d.ts.map +1 -0
  89. package/dist/components/text/fonts/lora.outline.js +4 -0
  90. package/dist/components/text/fonts/lora.strokes.d.ts +3 -0
  91. package/dist/components/text/fonts/lora.strokes.d.ts.map +1 -0
  92. package/dist/components/text/fonts/lora.strokes.js +4 -0
  93. package/dist/components/text/fonts/montserrat.outline.d.ts +3 -0
  94. package/dist/components/text/fonts/montserrat.outline.d.ts.map +1 -0
  95. package/dist/components/text/fonts/montserrat.outline.js +4 -0
  96. package/dist/components/text/fonts/montserrat.strokes.d.ts +3 -0
  97. package/dist/components/text/fonts/montserrat.strokes.d.ts.map +1 -0
  98. package/dist/components/text/fonts/montserrat.strokes.js +4 -0
  99. package/dist/components/text/fonts/oswald.outline.d.ts +3 -0
  100. package/dist/components/text/fonts/oswald.outline.d.ts.map +1 -0
  101. package/dist/components/text/fonts/oswald.outline.js +4 -0
  102. package/dist/components/text/fonts/oswald.strokes.d.ts +3 -0
  103. package/dist/components/text/fonts/oswald.strokes.d.ts.map +1 -0
  104. package/dist/components/text/fonts/oswald.strokes.js +4 -0
  105. package/dist/components/text/fonts/playfair-display.outline.d.ts +3 -0
  106. package/dist/components/text/fonts/playfair-display.outline.d.ts.map +1 -0
  107. package/dist/components/text/fonts/playfair-display.outline.js +4 -0
  108. package/dist/components/text/fonts/playfair-display.strokes.d.ts +3 -0
  109. package/dist/components/text/fonts/playfair-display.strokes.d.ts.map +1 -0
  110. package/dist/components/text/fonts/playfair-display.strokes.js +4 -0
  111. package/dist/components/text/fonts/roboto.outline.d.ts +3 -0
  112. package/dist/components/text/fonts/roboto.outline.d.ts.map +1 -0
  113. package/dist/components/text/fonts/roboto.outline.js +4 -0
  114. package/dist/components/text/fonts/roboto.strokes.d.ts +3 -0
  115. package/dist/components/text/fonts/roboto.strokes.d.ts.map +1 -0
  116. package/dist/components/text/fonts/roboto.strokes.js +4 -0
  117. package/dist/components/text/index.d.ts +2 -0
  118. package/dist/components/text/index.d.ts.map +1 -1
  119. package/dist/components/text/index.js +1 -0
  120. package/dist/components/text/useTextObject.d.ts +1 -0
  121. package/dist/components/text/useTextObject.d.ts.map +1 -1
  122. package/dist/components/text/useTextObject.js +23 -11
  123. package/dist/constants.d.ts.map +1 -1
  124. package/dist/constants.js +2 -0
  125. package/dist/index.d.ts +1 -1
  126. package/dist/index.d.ts.map +1 -1
  127. package/dist/index.js +1 -1
  128. package/dist/operations/catalogue/flock.d.ts.map +1 -1
  129. package/dist/operations/catalogue/flock.js +44 -9
  130. package/dist/operations/catalogue/inkBloom.d.ts.map +1 -1
  131. package/dist/operations/catalogue/inkBloom.js +46 -9
  132. package/dist/operations/catalogue/liquidMetal.d.ts.map +1 -1
  133. package/dist/operations/catalogue/liquidMetal.js +41 -12
  134. package/dist/operations/catalogue/particleSwarm.d.ts.map +1 -1
  135. package/dist/operations/catalogue/particleSwarm.js +29 -9
  136. package/dist/operations/catalogue/petalPeel.d.ts.map +1 -1
  137. package/dist/operations/catalogue/petalPeel.js +33 -9
  138. package/dist/operations/catalogue/portal.d.ts.map +1 -1
  139. package/dist/operations/catalogue/portal.js +33 -11
  140. package/dist/operations/catalogue/sandErosion.d.ts.map +1 -1
  141. package/dist/operations/catalogue/sandErosion.js +36 -14
  142. package/dist/operations/catalogue/teleport.d.ts.map +1 -1
  143. package/dist/operations/catalogue/teleport.js +43 -12
  144. package/dist/operations/catalogue/unweave.d.ts.map +1 -1
  145. package/dist/operations/catalogue/unweave.js +34 -15
  146. package/dist/operations/catalogue/voxelize.d.ts.map +1 -1
  147. package/dist/operations/catalogue/voxelize.js +37 -10
  148. package/dist/triggers/engine.d.ts +1 -1
  149. package/dist/triggers/engine.d.ts.map +1 -1
  150. package/dist/triggers/engine.js +3 -0
  151. package/dist/triggers/hosts/actions.d.ts.map +1 -1
  152. package/dist/triggers/hosts/actions.js +1 -1
  153. package/dist/triggers/hosts/interactions.d.ts.map +1 -1
  154. package/dist/triggers/hosts/interactions.js +1 -1
  155. package/dist/utils/pickProxy.d.ts +11 -0
  156. package/dist/utils/pickProxy.d.ts.map +1 -0
  157. package/dist/utils/pickProxy.js +36 -0
  158. package/dist/utils/shapes.d.ts +4 -0
  159. package/dist/utils/shapes.d.ts.map +1 -1
  160. package/dist/utils/shapes.js +4 -1
  161. package/dist/utils/textShape.d.ts +24 -0
  162. package/dist/utils/textShape.d.ts.map +1 -0
  163. package/dist/utils/textShape.js +102 -0
  164. package/package.json +2 -2
@@ -4,6 +4,8 @@
4
4
  // rotation (inner rings faster, like a disc). While it is on, the swarm keeps
5
5
  // turning. Reverting pulls every particle back to its exact spot and the
6
6
  // surface closes behind them.
7
+ // With Disappear off (an object effect's default) the object stays as a particle
8
+ // cloud in its own shape: every particle on its spot, twinkling and breathing.
7
9
  import { gsap } from 'gsap';
8
10
  import { defineOperation } from '../types.js';
9
11
  import { addLayer, forEachMeshMaterial, removeLayer } from './shaderLayers.js';
@@ -18,17 +20,19 @@ const FLIGHT = 0.5;
18
20
  export default defineOperation({
19
21
  value: 'particleSwarm',
20
22
  label: 'Particle swarm',
21
- description: 'Break the targets into their own coloured particles that swirl around them — reverting reassembles',
23
+ description: 'Break the targets into their own coloured particles that swirl around them (or, Disappear off, stay as a particle cloud) — reverting reassembles',
22
24
  channel: 'visual',
23
25
  multi: true,
24
26
  toggles: true,
25
- usableIn: ['interaction', 'action'],
27
+ usableIn: ['interaction', 'action', 'object'],
28
+ objectParams: { disappear: false },
26
29
  params: {
27
30
  direction: { type: 'select', label: 'Direction', default: 'up', options: DIRECTION_OPTIONS },
31
+ disappear: { type: 'boolean', label: 'Disappear', default: true, hint: 'Off: the object stays as a cloud of particles in its own shape' },
28
32
  particles: { type: 'number', label: 'Particles', default: 30000, min: 2000, max: 120000, step: 1000 },
29
33
  particleSize: { type: 'number', label: 'Particle size', default: 1, min: 0.2, max: 4, step: 0.1, unit: '×' },
30
- spread: { type: 'number', label: 'Spread', default: 1, min: 0.3, max: 3, step: 0.05, unit: '×', hint: 'Radius of the vortex' },
31
- speed: { type: 'number', label: 'Swirl speed', default: 1, min: 0, max: 4, step: 0.05, unit: '×' },
34
+ spread: { type: 'number', label: 'Spread', default: 1, min: 0.3, max: 3, step: 0.05, unit: '×', hint: 'Radius of the vortex', showWhen: { disappear: ['true'] } },
35
+ speed: { type: 'number', label: 'Swirl speed', default: 1, min: 0, max: 4, step: 0.05, unit: '×', showWhen: { disappear: ['true'] } },
32
36
  edgeColor: { type: 'color', label: 'Edge colour', default: '#9fe8ff' },
33
37
  glow: { type: 'boolean', label: 'Glow', default: true, hint: 'Additive light — off keeps the true surface colours' },
34
38
  duration: { type: 'number', label: 'Duration', default: 2.8, min: 0.3, max: 12, step: 0.1, unit: 's' },
@@ -39,16 +43,19 @@ export default defineOperation({
39
43
  const field = createFieldState(target);
40
44
  field.edgeWidth.value = 0.05;
41
45
  field.scale.value = 2.5;
42
- const flight = { value: FLIGHT }, spread = { value: 1 }, speed = { value: 1 }, size = { value: 1 };
46
+ const flight = { value: FLIGHT }, spread = { value: 1 }, speed = { value: 1 }, size = { value: 1 }, keep = { value: 0 };
43
47
  const sample = sampleSurfaceCached(target, Math.round(Number(p.particles)), 11);
44
48
  const points = createFxPoints(target, sample, {
45
49
  additive: !!p.glow,
46
- uniforms: { ...fieldUniforms(P, field), uSwmFlight: flight, uSwmSpread: spread, uSwmSpeed: speed, uSwmPtSize: size },
50
+ uniforms: { ...fieldUniforms(P, field), uSwmFlight: flight, uSwmSpread: spread, uSwmSpeed: speed, uSwmPtSize: size, uSwmKeep: keep },
47
51
  vertexPars: fieldGlsl(P) + GLSL_POINT_SIZE,
48
52
  vertexMain: /* glsl */ `
49
53
  float birth = ${P}BirthOf(${P}Field(${P}Norm(aP)));
50
54
  float a = clamp((uSwmProgress - birth) / uSwmFlight, 0.0, 1.0);
51
55
  if (a <= 0.0) { gl_Position = vec4(2.0, 2.0, 2.0, 1.0); gl_PointSize = 0.0; vColor = vec3(0.0); vAlpha = 0.0; return; }
56
+ // keep: the particle appears on its spot and stays there
57
+ float held = uSwmKeep * step(0.9999, uSwmProgress);
58
+ a = mix(a, min(a, 0.001), uSwmKeep);
52
59
  float E = uSwmExtent;
53
60
  vec3 C = u${P}Min + u${P}Size * 0.5;
54
61
  // destination: a tilted two-arm spiral galaxy around the object's centre.
@@ -71,19 +78,22 @@ export default defineOperation({
71
78
  vec3 ctrl = aP + aN * (0.25 * E) + vec3(0.0, 0.12 * E, 0.0);
72
79
  float k = a * a * (3.0 - 2.0 * a);
73
80
  vec3 local = mix(mix(aP, ctrl, k), mix(ctrl, D, k), k);
81
+ // holding as a cloud: particles jitter on their spots, the whole cloud breathes
82
+ local += held * (aN * E * 0.004 * sin(u${P}Time * (2.0 + 2.0 * aR.w) + aR.z * 40.0));
83
+ local = C + (local - C) * (1.0 + held * 0.012 * sin(u${P}Time * 0.9));
74
84
  vec4 mv = modelViewMatrix * vec4(local, 1.0);
75
85
  gl_Position = projectionMatrix * mv;
76
86
  // the colour of the spot it left, brighter in flight, a slow shimmer in the vortex
77
87
  float flightGlow = sin(3.14159 * a);
78
88
  float shimmer = 0.85 + 0.15 * sin(u${P}Time * (1.0 + 2.0 * aR.w) + aR.z * 6.2832);
79
89
  vColor = aC * (1.0 + 0.4 * flightGlow) * shimmer + u${P}EdgeColor * 0.25 * flightGlow * flightGlow;
80
- vAlpha = smoothstep(0.0, 0.08, a);
90
+ vAlpha = mix(smoothstep(0.0, 0.08, a), 0.75 + 0.25 * sin(u${P}Time * (3.0 + 4.0 * aR.x) + aR.y * 50.0), uSwmKeep);
81
91
  gl_PointSize = clamp(fxPointPx(E * 0.0045 * uSwmPtSize * (0.6 + 0.8 * aR.w), mv), 0.0, 20.0);`,
82
92
  });
83
93
  const own = {};
84
94
  // while the swarm exists it keeps turning, even after the transition finished
85
95
  const tick = () => { own.field.time.value += gsap.ticker.deltaRatio() / 60; syncRoot(target, own.field); };
86
- Object.assign(own, { field, flight, spread, speed, size, points, mats: new Set(), tick });
96
+ Object.assign(own, { field, flight, spread, speed, size, keep, points, mats: new Set(), tick });
87
97
  st = own;
88
98
  const layer = surfaceLayer(P, field, 'tint');
89
99
  forEachMeshMaterial(target, (mat, mesh) => {
@@ -100,6 +110,15 @@ export default defineOperation({
100
110
  s.spread.value = Number(p.spread);
101
111
  s.speed.value = Number(p.speed);
102
112
  s.size.value = Number(p.particleSize);
113
+ s.keep.value = p.disappear === false ? 1 : 0;
114
+ setMeshesHidden(target, false);
115
+ if (s.keep.value) {
116
+ // keep: the surface becomes its particles (0 → 1) and holds — its own ticker keeps the time running
117
+ return runProgress(target, s.field, 1, {
118
+ duration: Number(p.duration) / (1 + FLIGHT), ease: 'none', clock: false,
119
+ onComplete: () => setMeshesHidden(target, true),
120
+ });
121
+ }
103
122
  return runProgress(target, s.field, 1 + FLIGHT, {
104
123
  duration: Number(p.duration), ease: 'power1.inOut', clock: false,
105
124
  onComplete: () => setMeshesHidden(target, true),
@@ -110,8 +129,9 @@ export default defineOperation({
110
129
  if (!s)
111
130
  return;
112
131
  setMeshesHidden(target, false);
132
+ // from a hold it only has part of the way to go: keep the same speed
113
133
  return runProgress(target, s.field, 0, {
114
- duration: Number(p.duration), ease: 'power1.inOut', clock: false,
134
+ duration: Number(p.duration) * Math.min(1, Math.max(0.1, s.field.progress.value / (1 + FLIGHT))), ease: s.keep.value ? 'none' : 'power1.inOut', clock: false,
115
135
  onComplete: () => {
116
136
  gsap.ticker.remove(s.tick);
117
137
  s.mats.forEach(m => removeLayer(m, KEY));
@@ -1 +1 @@
1
- {"version":3,"file":"petalPeel.d.ts","sourceRoot":"","sources":["../../../src/operations/catalogue/petalPeel.ts"],"names":[],"mappings":";AAsBA,wBAqHG"}
1
+ {"version":3,"file":"petalPeel.d.ts","sourceRoot":"","sources":["../../../src/operations/catalogue/petalPeel.ts"],"names":[],"mappings":";AAwBA,wBA2IG"}
@@ -4,6 +4,8 @@
4
4
  // rocking tumble — and settles flat on the ground around the base. The flakes
5
5
  // are real lit, double-sided instances (one instanced mesh, casting shadows).
6
6
  // Reverting lifts them back into place and the surface closes.
7
+ // With Disappear off (an object effect's default) the object stays "lifted": every
8
+ // flake hinged up off the surface like raised scales, breathing in slow waves.
7
9
  import { CircleGeometry } from 'three';
8
10
  import { defineOperation } from '../types.js';
9
11
  import { GLSL_ROT, addLayer, forEachMeshMaterial, removeLayer } from './shaderLayers.js';
@@ -17,16 +19,18 @@ const LIFE = 0.8;
17
19
  export default defineOperation({
18
20
  value: 'petalPeel',
19
21
  label: 'Petal peel',
20
- description: 'Peel the targets into flakes that flutter down like leaves — reverting lifts them back',
22
+ description: 'Peel the targets into flakes that flutter down like leaves (or, Disappear off, stay lifted like raised scales) — reverting lifts them back',
21
23
  channel: 'visual',
22
24
  multi: true,
23
25
  toggles: true,
24
- usableIn: ['interaction', 'action'],
26
+ usableIn: ['interaction', 'action', 'object'],
27
+ objectParams: { disappear: false },
25
28
  params: {
26
29
  direction: { type: 'select', label: 'Direction', default: 'down', options: DIRECTION_OPTIONS },
30
+ disappear: { type: 'boolean', label: 'Disappear', default: true, hint: 'Off: the flakes stay lifted off the surface, like raised scales' },
27
31
  petals: { type: 'number', label: 'Petals', default: 2600, min: 300, max: 20000, step: 100 },
28
32
  petalSize: { type: 'number', label: 'Petal size', default: 1, min: 0.3, max: 4, step: 0.05, unit: '×' },
29
- drift: { type: 'number', label: 'Drift', default: 1, min: 0, max: 4, step: 0.05, unit: '×', hint: 'How far they sway and scatter' },
33
+ drift: { type: 'number', label: 'Drift', default: 1, min: 0, max: 4, step: 0.05, unit: '×', hint: 'How far they sway and scatter', showWhen: { disappear: ['true'] } },
30
34
  edgeColor: { type: 'color', label: 'Edge colour', default: '#fff1d6' },
31
35
  scale: { type: 'number', label: 'Pattern', default: 2.5, min: 0.5, max: 12, step: 0.1 },
32
36
  duration: { type: 'number', label: 'Duration', default: 4, min: 0.3, max: 14, step: 0.1, unit: 's' },
@@ -36,7 +40,7 @@ export default defineOperation({
36
40
  if (!st) {
37
41
  const field = createFieldState(target);
38
42
  field.edgeWidth.value = 0.04;
39
- const life = { value: LIFE }, drift = { value: 1 };
43
+ const life = { value: LIFE }, drift = { value: 1 }, keep = { value: 0 };
40
44
  const count = Math.round(Number(p.petals));
41
45
  const sample = sampleSurfaceCached(target, count, 47);
42
46
  // petal size from the surface area each one has to cover
@@ -54,7 +58,7 @@ export default defineOperation({
54
58
  },
55
59
  layerKey: 'petalInstances', roughness: 0.7, metalness: 0, doubleSided: true,
56
60
  layer: {
57
- uniforms: { ...fieldUniforms(P, field), uPdlLife: life, uPdlDrift: drift },
61
+ uniforms: { ...fieldUniforms(P, field), uPdlLife: life, uPdlDrift: drift, uPdlKeep: keep },
58
62
  common: fieldGlsl(P) + GLSL_ROT,
59
63
  vertexPars: /* glsl */ `
60
64
  attribute vec3 aPdlP; attribute vec3 aPdlN; attribute vec4 aPdlR;
@@ -78,12 +82,22 @@ export default defineOperation({
78
82
  mat3 lying = fxRot(vec3(0.0, 1.0, 0.0), aPdlR.y * 6.28) * mat3(1.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, 1.0, 0.0);
79
83
  float settle = smoothstep(land, land + 0.06, a);
80
84
  return mat3(mix(inAir[0], lying[0], settle), mix(inAir[1], lying[1], settle), mix(inAir[2], lying[2], settle));
81
- }`,
82
- normal: `{ objectNormal = pdlRot(pdlAge()) * objectNormal; }`,
85
+ }
86
+ // lifted (keep): hinged up off the surface, breathing in slow waves across the object
87
+ float pdlLift(float a) {
88
+ float held = uPdlKeep * step(0.9999, uPdlProgress);
89
+ float wave = sin(uPdlTime * 1.1 + dot(aPdlP, vec3(1.0, 0.6, 0.3)) / uPdlExtent * 7.0 + aPdlR.x * 0.8);
90
+ return smoothstep(0.0, 0.14, a) * (0.5 + held * 0.14 * wave);
91
+ }
92
+ mat3 pdlLifted(float a) { return pdlFrame() * fxRot(vec3(1.0, 0.0, 0.0), pdlLift(a)); }`,
93
+ normal: `{ objectNormal = (uPdlKeep > 0.5 ? pdlLifted(pdlAge()) : pdlRot(pdlAge())) * objectNormal; }`,
83
94
  vertex: /* glsl */ `
84
95
  {
85
96
  float a = pdlAge();
86
97
  if (a <= 0.0) { transformed *= 0.0; }
98
+ else if (uPdlKeep > 0.5) {
99
+ transformed = pdlLifted(a) * transformed + aPdlN * (0.012 * uPdlExtent) * smoothstep(0.0, 0.14, a);
100
+ }
87
101
  else {
88
102
  a = min(a, 1.0);
89
103
  float E = uPdlExtent;
@@ -101,7 +115,7 @@ export default defineOperation({
101
115
  }`,
102
116
  },
103
117
  });
104
- st = { field, life, drift, mesh, mats: new Set() };
118
+ st = { field, life, drift, keep, mesh, mats: new Set() };
105
119
  const own = st;
106
120
  const layer = surfaceLayer(P, field, 'tint');
107
121
  forEachMeshMaterial(target, (mat, m) => {
@@ -115,6 +129,15 @@ export default defineOperation({
115
129
  const s = st;
116
130
  setFieldParams(s.field, p, target, ctx);
117
131
  s.drift.value = Number(p.drift);
132
+ s.keep.value = p.disappear === false ? 1 : 0;
133
+ hideMeshes(target, KEY, false);
134
+ if (s.keep.value) {
135
+ // keep: the surface lifts into flakes (0 → 1) and holds
136
+ return runProgress(target, s.field, 1, {
137
+ duration: Number(p.duration) / (1 + LIFE), ease: 'none', hold: true,
138
+ onComplete: () => hideMeshes(target, KEY, true),
139
+ });
140
+ }
118
141
  return runProgress(target, s.field, 1 + LIFE, {
119
142
  duration: Number(p.duration), ease: 'none',
120
143
  onComplete: () => hideMeshes(target, KEY, true),
@@ -125,8 +148,9 @@ export default defineOperation({
125
148
  if (!s)
126
149
  return;
127
150
  hideMeshes(target, KEY, false);
151
+ // from a hold it only has part of the way to go: keep the same speed
128
152
  return runProgress(target, s.field, 0, {
129
- duration: Number(p.duration), ease: 'none',
153
+ duration: Number(p.duration) * Math.min(1, Math.max(0.1, s.field.progress.value / (1 + LIFE))), ease: 'none',
130
154
  onComplete: () => {
131
155
  s.mats.forEach(m => removeLayer(m, KEY));
132
156
  disposeFxInstances(s.mesh);
@@ -1 +1 @@
1
- {"version":3,"file":"portal.d.ts","sourceRoot":"","sources":["../../../src/operations/catalogue/portal.ts"],"names":[],"mappings":";AAoIA,wBAsFG"}
1
+ {"version":3,"file":"portal.d.ts","sourceRoot":"","sources":["../../../src/operations/catalogue/portal.ts"],"names":[],"mappings":";AA6IA,wBAiGG"}
@@ -8,6 +8,9 @@
8
8
  // photon ring and fine, faint spiral filaments inside — see-through
9
9
  // tech segmented arcs rotating both ways, tick marks and a scanning light
10
10
  // Reverting brings the object back out of the portal.
11
+ // With Disappear off (an object effect's default) the ring opens around the object's
12
+ // middle and stays, the object intact inside it with a soft band of light where the
13
+ // ring's plane crosses it.
11
14
  import { AdditiveBlending, Color, Mesh, PlaneGeometry, Quaternion, ShaderMaterial, Vector3 } from 'three';
12
15
  import { defineOperation } from '../types.js';
13
16
  import { addLayer, forEachMeshMaterial, removeLayer } from './shaderLayers.js';
@@ -21,16 +24,22 @@ const STYLES = ['electric', 'runes', 'horizon', 'tech'];
21
24
  const PAD = 1.35;
22
25
  function cutLayer(st) {
23
26
  return {
24
- uniforms: { ...fieldUniforms(P, st.field), uPorColor: st.color },
27
+ uniforms: { ...fieldUniforms(P, st.field), uPorColor: st.color, uPorKeep: st.keep },
25
28
  common: `varying vec3 vPorQ;\n${fieldGlsl(P)}`,
26
29
  vertex: `vPorQ = PorNorm((uPorRootInv * modelMatrix * vec4(transformed, 1.0)).xyz);`,
27
30
  fragment: /* glsl */ `
28
31
  {
29
32
  float s = PorAlong(vPorQ);
30
- float fs = mix(-0.04, 1.04, clamp(uPorProgress, 0.0, 1.0));
31
- if (s < fs) discard;
32
- float rim = exp(-(s - fs) / 0.012);
33
- outgoingLight = mix(outgoingLight, uPorColor * 3.0, clamp(rim, 0.0, 1.0));
33
+ if (uPorKeep > 0.5) {
34
+ // inside the ring: a soft band of light where its plane crosses the object, breathing
35
+ float band = exp(-abs(s - 0.5) / 0.03) * (0.5 + 0.3 * sin(uPorTime * 1.7)) * smoothstep(0.0, 0.08, uPorProgress);
36
+ outgoingLight = mix(outgoingLight, uPorColor * 2.5, clamp(band, 0.0, 1.0));
37
+ } else {
38
+ float fs = mix(-0.04, 1.04, clamp(uPorProgress, 0.0, 1.0));
39
+ if (s < fs) discard;
40
+ float rim = exp(-(s - fs) / 0.012);
41
+ outgoingLight = mix(outgoingLight, uPorColor * 3.0, clamp(rim, 0.0, 1.0));
42
+ }
34
43
  }`,
35
44
  };
36
45
  }
@@ -122,12 +131,14 @@ function makeRingMaterial(st, style) {
122
131
  export default defineOperation({
123
132
  value: 'portal',
124
133
  label: 'Portal',
125
- description: 'Sweep a ring of light through the targets and send them through it — reverting brings them back',
134
+ description: 'Sweep a ring of light through the targets and send them through it (or, Disappear off, keep the ring open around them) — reverting brings them back',
126
135
  channel: 'visual',
127
136
  multi: true,
128
137
  toggles: true,
129
- usableIn: ['interaction', 'action'],
138
+ usableIn: ['interaction', 'action', 'object'],
139
+ objectParams: { disappear: false },
130
140
  params: {
141
+ disappear: { type: 'boolean', label: 'Disappear', default: true, hint: 'Off: the ring opens around the object and stays' },
131
142
  ring: { type: 'select', label: 'Ring', default: 'electric', options: [{ value: 'electric', label: 'Electric arcs' }, { value: 'runes', label: 'Rune ring' }, { value: 'horizon', label: 'Event horizon' }, { value: 'tech', label: 'Tech gate' }] },
132
143
  direction: { type: 'select', label: 'Sweep', default: 'down', options: SWEEP_OPTIONS },
133
144
  color: { type: 'color', label: 'Light', default: '#7a8cff' },
@@ -142,7 +153,7 @@ export default defineOperation({
142
153
  if (!st) {
143
154
  const field = createFieldState(target);
144
155
  const own = {
145
- field, color: { value: new Color() }, style: '', mats: new Set(),
156
+ field, color: { value: new Color() }, style: '', mats: new Set(), keep: { value: 0 },
146
157
  uni: { uStyle: { value: 0 }, uThick: { value: 1 }, uGlow: { value: 1 }, uSpin: { value: 1 }, uAlpha: { value: 0 } },
147
158
  };
148
159
  own.ring = new Mesh(new PlaneGeometry(2 * PAD, 2 * PAD), undefined);
@@ -183,7 +194,16 @@ export default defineOperation({
183
194
  s.radius = (0.55 * across + 0.04 * s.field.extent.value) * Number(p.size);
184
195
  s.ring.quaternion.copy(new Quaternion().setFromUnitVectors(new Vector3(0, 0, 1), d));
185
196
  s.ring.visible = true;
197
+ s.keep.value = p.disappear === false ? 1 : 0;
198
+ hideMeshes(target, KEY, false);
186
199
  placeRing(s);
200
+ if (s.keep.value) {
201
+ // keep: the ring opens around the middle and holds (its spin runs on the hold clock)
202
+ return runProgress(target, s.field, 1, {
203
+ duration: Number(p.duration) * 0.3, ease: 'none', hold: true,
204
+ onUpdate: () => placeRing(s), onComplete: () => placeRing(s),
205
+ });
206
+ }
187
207
  return runProgress(target, s.field, 1, {
188
208
  duration: Number(p.duration), ease: 'power1.inOut',
189
209
  onUpdate: () => placeRing(s),
@@ -197,7 +217,7 @@ export default defineOperation({
197
217
  hideMeshes(target, KEY, false);
198
218
  s.ring.visible = true;
199
219
  return runProgress(target, s.field, 0, {
200
- duration: Number(p.duration), ease: 'power1.inOut',
220
+ duration: Number(p.duration) * (s.keep.value ? 0.3 : 1) * Math.min(1, Math.max(0.1, s.field.progress.value)), ease: s.keep.value ? 'none' : 'power1.inOut',
201
221
  onUpdate: () => placeRing(s),
202
222
  onComplete: () => {
203
223
  s.mats.forEach(m => removeLayer(m, KEY));
@@ -212,13 +232,15 @@ export default defineOperation({
212
232
  /** Put the ring on the moving plane (root-local) and open / close it at the ends. */
213
233
  function placeRing(s) {
214
234
  const pr = Math.min(1, Math.max(0, s.field.progress.value));
215
- const fs = -0.04 + 1.08 * pr;
235
+ const keep = s.keep.value > 0.5;
236
+ // keeping: the ring sits on the object's middle plane and never closes
237
+ const fs = keep ? 0.5 : -0.04 + 1.08 * pr;
216
238
  const d = sweepVector(s.field);
217
239
  const min = s.field.boundsMin.value, size = s.field.boundsSize.value;
218
240
  const c = min.clone().addScaledVector(size, 0.5);
219
241
  const half = (Math.abs(d.x) * size.x + Math.abs(d.y) * size.y + Math.abs(d.z) * size.z) / 2;
220
242
  s.ring.position.copy(c).addScaledVector(d, (fs - 0.5) * 2 * half);
221
- const open = Math.max(0, Math.min(1, pr / 0.08, (1 - pr) / 0.08 + 0.001));
243
+ const open = Math.max(0, Math.min(1, pr / 0.08, keep ? 1 : (1 - pr) / 0.08 + 0.001));
222
244
  s.ring.scale.setScalar(s.radius * (0.2 + 0.8 * Math.sqrt(open)));
223
245
  s.uni.uAlpha.value = open;
224
246
  }
@@ -1 +1 @@
1
- {"version":3,"file":"sandErosion.d.ts","sourceRoot":"","sources":["../../../src/operations/catalogue/sandErosion.ts"],"names":[],"mappings":";AAyEA,wBAkGG"}
1
+ {"version":3,"file":"sandErosion.d.ts","sourceRoot":"","sources":["../../../src/operations/catalogue/sandErosion.ts"],"names":[],"mappings":";AAmFA,wBA8GG"}
@@ -5,10 +5,12 @@
5
5
  // a cone-shaped mound around the base; grains that fell earlier lie lower, so the pile
6
6
  // really builds up. The pile stays while the effect is on; reverting sends every
7
7
  // grain back up to its exact spot and the surface closes.
8
+ // With Disappear off (an object effect's default) the whole object turns to sand and
9
+ // stays: a crust of grains that shimmers, with a faint wind ripple drifting across.
8
10
  import { Color } from 'three';
9
11
  import { defineOperation } from '../types.js';
10
12
  import { addLayer, forEachMeshMaterial, removeLayer } from './shaderLayers.js';
11
- import { DIRECTION_OPTIONS, createFieldState, fieldGlsl, fieldUniforms, hideMeshes, runProgress, setFieldParams, } from './transitionField.js';
13
+ import { DIRECTION_OPTIONS, createFieldState, fieldGlsl, fieldUniforms, hideMeshes, runProgress, setFieldParams, setReveal, } from './transitionField.js';
12
14
  import { sampleSurfaceCached } from './surfaceSample.js';
13
15
  import { GLSL_POINT_SIZE, createFxPoints, disposeFxPoints } from './fxPoints.js';
14
16
  const P = 'Snd';
@@ -31,16 +33,19 @@ float fxHash1(vec3 p) { return fract(sin(dot(p, vec3(127.1, 311.7, 74.7))) * 437
31
33
  * the crumble front eats it away with a per-grain jitter, so the edge breaks
32
34
  * up into grains instead of a smooth line — darker right at the breaking lip.
33
35
  */
34
- function sandLayer(field, sand, tint) {
36
+ function sandLayer(field, sand, tint, keep) {
35
37
  const crust = /* glsl */ `
36
38
  float f = SndField(vSndQ);
37
39
  float crust = smoothstep(0.0, 0.1, SndThreshold() - f);
38
- float pc = clamp(uSndProgress - ${LAG}, 0.0, 1.0);
40
+ // painting the object in: nothing ahead of the front, full crust right behind it
41
+ if (uSndReveal > 0.5) { if (SndThreshold() - f < 0.0) discard; crust = 1.0; }
42
+ // keep: the crust never crumbles
43
+ float pc = clamp(uSndProgress - ${LAG}, 0.0, 1.0) * (1.0 - uSndKeep);
39
44
  float tc = pc * (1.0 + 2.0 * uSndEdgeWidth) - uSndEdgeWidth;
40
45
  float jit = (fxHash1(floor(vSndR / uSndExtent * ${GRAIN_CELLS.toFixed(1)})) - 0.5) * 0.06;
41
46
  float d = f + jit - tc;`;
42
47
  return {
43
- uniforms: { ...fieldUniforms(P, field), uSndSand: sand, uSndTint: tint },
48
+ uniforms: { ...fieldUniforms(P, field), uSndSand: sand, uSndTint: tint, uSndKeep: keep },
44
49
  common: `varying vec3 vSndQ;\nvarying vec3 vSndR;\n${fieldGlsl(P)}\n${HASH1}`,
45
50
  vertex: /* glsl */ `
46
51
  vSndR = (uSndRootInv * modelMatrix * vec4(transformed, 1.0)).xyz;
@@ -51,6 +56,11 @@ function sandLayer(field, sand, tint) {
51
56
  if (d < 0.0) discard;
52
57
  float g = fxHash1(floor(vSndR / uSndExtent * ${GRAIN_CELLS.toFixed(1)} * 2.3));
53
58
  vec3 sandC = mix(uSndSand, diffuseColor.rgb, uSndTint) * (0.8 + 0.3 * g);
59
+ // holding as sand: grains shimmer, a faint wind ripple drifts across
60
+ float held = uSndKeep * step(0.9999, uSndProgress);
61
+ float shimmer = 0.5 + 0.5 * sin(uSndTime * (1.5 + 4.0 * g) + g * 60.0);
62
+ float ripple = sin((vSndR.x + vSndR.z * 0.4) / uSndExtent * 26.0 - uSndTime * 2.2 + fxNoise(vSndR / uSndExtent * 6.0) * 3.0);
63
+ sandC *= 1.0 + held * (0.08 * shimmer * step(0.85, g) + 0.05 * ripple);
54
64
  diffuseColor.rgb = mix(diffuseColor.rgb, sandC, crust);
55
65
  // the breaking lip is darker, like damp sand under the fall
56
66
  diffuseColor.rgb *= 1.0 - 0.45 * (1.0 - smoothstep(0.0, 0.05, d)) * step(0.0001, pc);
@@ -66,17 +76,20 @@ function sandLayer(field, sand, tint) {
66
76
  export default defineOperation({
67
77
  value: 'sandErosion',
68
78
  label: 'Sand erosion',
69
- description: 'Crumble the targets into grains that pour down into a mound — reverting pours them back up',
79
+ description: 'Crumble the targets into grains that pour down into a mound (or, Disappear off, turn them to sand and keep them) — reverting pours them back up',
70
80
  channel: 'visual',
71
81
  multi: true,
72
82
  toggles: true,
73
- usableIn: ['interaction', 'action'],
83
+ usableIn: ['interaction', 'action', 'object'],
84
+ objectParams: { disappear: false },
85
+ presence: 'paint',
74
86
  params: {
75
87
  direction: { type: 'select', label: 'Direction', default: 'down', options: DIRECTION_OPTIONS },
88
+ disappear: { type: 'boolean', label: 'Disappear', default: true, hint: 'Off: the object turns to sand and stays' },
76
89
  grains: { type: 'number', label: 'Grains', default: 45000, min: 5000, max: 150000, step: 1000 },
77
90
  grainSize: { type: 'number', label: 'Grain size', default: 1, min: 0.3, max: 4, step: 0.1, unit: '×' },
78
- pile: { type: 'number', label: 'Mound height', default: 1, min: 0, max: 3, step: 0.05, unit: '×' },
79
- spread: { type: 'number', label: 'Mound width', default: 1, min: 0.3, max: 3, step: 0.05, unit: '×' },
91
+ pile: { type: 'number', label: 'Mound height', default: 1, min: 0, max: 3, step: 0.05, unit: '×', showWhen: { disappear: ['true'] } },
92
+ spread: { type: 'number', label: 'Mound width', default: 1, min: 0.3, max: 3, step: 0.05, unit: '×', showWhen: { disappear: ['true'] } },
80
93
  sand: { type: 'color', label: 'Sand', default: '#d8b98a' },
81
94
  tint: { type: 'number', label: 'Object colour', default: 0.35, min: 0, max: 1, step: 0.05, hint: 'How much of the object’s own colour the sand keeps' },
82
95
  scale: { type: 'number', label: 'Grain pattern', default: 3, min: 0.5, max: 12, step: 0.1 },
@@ -87,11 +100,11 @@ export default defineOperation({
87
100
  if (!st) {
88
101
  const field = createFieldState(target);
89
102
  field.edgeWidth.value = 0.05;
90
- const life = { value: LIFE }, pile = { value: 1 }, spread = { value: 1 }, grain = { value: 1 }, sand = { value: new Color() }, tint = { value: 0.35 };
103
+ const life = { value: LIFE }, pile = { value: 1 }, spread = { value: 1 }, grain = { value: 1 }, sand = { value: new Color() }, tint = { value: 0.35 }, keep = { value: 0 };
91
104
  const sample = sampleSurfaceCached(target, Math.round(Number(p.grains)), 21);
92
105
  const points = createFxPoints(target, sample, {
93
106
  additive: false,
94
- uniforms: { ...fieldUniforms(P, field), uSndLife: life, uSndPile: pile, uSndSpread: spread, uSndGrain: grain, uSndSand: sand, uSndTint: tint },
107
+ uniforms: { ...fieldUniforms(P, field), uSndLife: life, uSndPile: pile, uSndSpread: spread, uSndGrain: grain, uSndSand: sand, uSndTint: tint, uSndKeep: keep },
95
108
  vertexPars: fieldGlsl(P) + GLSL_POINT_SIZE + HASH1,
96
109
  vertexMain: /* glsl */ `
97
110
  float f = ${P}Field(${P}Norm(aP));
@@ -99,7 +112,7 @@ export default defineOperation({
99
112
  float jit = (fxHash1(floor(aP / uSndExtent * ${GRAIN_CELLS.toFixed(1)})) - 0.5) * 0.06;
100
113
  float birth = ${LAG} + ${P}BirthOf(f + jit);
101
114
  float a = (uSndProgress - birth) / uSndLife;
102
- if (a <= 0.0) { gl_Position = vec4(2.0, 2.0, 2.0, 1.0); gl_PointSize = 0.0; vColor = vec3(0.0); vAlpha = 0.0; return; }
115
+ if (a <= 0.0 || uSndKeep > 0.5) { gl_Position = vec4(2.0, 2.0, 2.0, 1.0); gl_PointSize = 0.0; vColor = vec3(0.0); vAlpha = 0.0; return; }
103
116
  a = min(a, 1.0);
104
117
  float E = uSndExtent;
105
118
  vec3 C = u${P}Min + u${P}Size * 0.5;
@@ -126,10 +139,10 @@ export default defineOperation({
126
139
  vAlpha = 1.0;
127
140
  gl_PointSize = clamp(fxPointPx(E * 0.0032 * uSndGrain * (0.7 + 0.6 * aR.w), mv), 1.0, 10.0);`,
128
141
  });
129
- st = { field, life, pile, spread, grain, sand, points, mats: new Set() };
142
+ st = { field, life, pile, spread, grain, sand, keep, points, mats: new Set() };
130
143
  st.tint = tint;
131
144
  const own = st;
132
- const layer = sandLayer(field, sand, tint);
145
+ const layer = sandLayer(field, sand, tint, keep);
133
146
  forEachMeshMaterial(target, (mat, mesh) => {
134
147
  if (mesh.userData?.__opFxHelper)
135
148
  return;
@@ -145,6 +158,13 @@ export default defineOperation({
145
158
  s.grain.value = Number(p.grainSize);
146
159
  s.sand.value.set(String(p.sand));
147
160
  s.tint.value = Number(p.tint);
161
+ s.keep.value = p.disappear === false ? 1 : 0;
162
+ if (s.keep.value) {
163
+ // keep: the crust spreads over the whole object (0 → 1) and holds
164
+ hideMeshes(target, KEY, false);
165
+ return runProgress(target, s.field, 1, { duration: Number(p.duration) / END, ease: 'none', hold: true });
166
+ }
167
+ hideMeshes(target, KEY, false);
148
168
  return runProgress(target, s.field, END, {
149
169
  duration: Number(p.duration), ease: 'none',
150
170
  onComplete: () => hideMeshes(target, KEY, true),
@@ -155,8 +175,10 @@ export default defineOperation({
155
175
  if (!s)
156
176
  return;
157
177
  hideMeshes(target, KEY, false);
178
+ setReveal(s.field, p);
179
+ // from a hold it only has part of the way to go: keep the same speed
158
180
  return runProgress(target, s.field, 0, {
159
- duration: Number(p.duration), ease: 'none',
181
+ duration: Number(p.duration) * Math.min(1, Math.max(0.1, s.field.progress.value / END)), ease: 'none',
160
182
  onComplete: () => {
161
183
  s.mats.forEach(m => removeLayer(m, KEY));
162
184
  disposeFxPoints(s.points);
@@ -1 +1 @@
1
- {"version":3,"file":"teleport.d.ts","sourceRoot":"","sources":["../../../src/operations/catalogue/teleport.ts"],"names":[],"mappings":";AA2HA,wBAgGG"}
1
+ {"version":3,"file":"teleport.d.ts","sourceRoot":"","sources":["../../../src/operations/catalogue/teleport.ts"],"names":[],"mappings":";AAyIA,wBAiHG"}
@@ -5,10 +5,13 @@
5
5
  // column of light: a soft core that is brightest face-on, fine streaks flowing
6
6
  // upward, and thin rings of light climbing it. Reverting materialises the
7
7
  // object back down the beam.
8
+ // With Disappear off (an object effect's default) the object stays "charged": hot
9
+ // white at the top fading to its own colour below, the beam standing over it with
10
+ // particles cycling up it, nothing leaving.
8
11
  import { AdditiveBlending, Color, CylinderGeometry, Mesh, ShaderMaterial } from 'three';
9
12
  import { defineOperation } from '../types.js';
10
13
  import { GLSL_ROT, addLayer, forEachMeshMaterial, removeLayer } from './shaderLayers.js';
11
- import { DIRECTION_OPTIONS, createFieldState, fieldGlsl, fieldUniforms, hideMeshes, runProgress, setFieldParams, } from './transitionField.js';
14
+ import { DIRECTION_OPTIONS, createFieldState, fieldGlsl, fieldUniforms, hideMeshes, runProgress, setFieldParams, setReveal, } from './transitionField.js';
12
15
  import { sampleSurfaceCached } from './surfaceSample.js';
13
16
  import { GLSL_POINT_SIZE, createFxPoints, disposeFxPoints } from './fxPoints.js';
14
17
  import { hideUntilWarm } from './warmup.js';
@@ -21,14 +24,14 @@ const END = 1 + LIFE * 1.6;
21
24
  const SING = `(u${P}Min + u${P}Size * vec3(0.5, 1.0, 0.5) + vec3(0.0, 0.18 * u${P}Extent, 0.0))`;
22
25
  function surface(st) {
23
26
  return {
24
- uniforms: { ...fieldUniforms(P, st.field), uTelLife: st.life, uTelGlow: st.glow },
27
+ uniforms: { ...fieldUniforms(P, st.field), uTelLife: st.life, uTelGlow: st.glow, uTelKeep: st.keep },
25
28
  common: `varying float vTelK;\nvarying vec3 vTelQ;\n${fieldGlsl(P)}\n${GLSL_ROT}`,
26
29
  vertex: /* glsl */ `
27
30
  {
28
31
  mat4 toRoot = uTelRootInv * modelMatrix;
29
32
  vec3 rl = (toRoot * vec4(transformed, 1.0)).xyz;
30
33
  float birth = TelBirthOf(TelField(TelNorm(rl)));
31
- float k = clamp((uTelProgress - birth) / uTelLife, 0.0, 1.0);
34
+ float k = clamp((uTelProgress - birth) / uTelLife, 0.0, 1.0) * (1.0 - uTelKeep);
32
35
  k = k * k * (3.0 - 2.0 * k);
33
36
  vTelK = k;
34
37
  vTelQ = TelNorm(rl);
@@ -41,10 +44,21 @@ function surface(st) {
41
44
  }`,
42
45
  fragment: /* glsl */ `
43
46
  {
44
- float cut = 0.1 + 0.35 * fxFbm(vTelQ * 7.0);
45
- if (vTelK > cut) discard;
46
- float h = smoothstep(0.0, cut, vTelK);
47
- outgoingLight = mix(outgoingLight, uTelGlow * (1.5 + 3.5 * h), h);
47
+ if (uTelKeep > 0.5) {
48
+ // charged: heat climbs the object — white-hot at the top, its own colour below — and flickers
49
+ float TelD = TelField(vTelQ) - TelThreshold();
50
+ float charged = smoothstep(0.0, uTelEdgeWidth, -TelD);
51
+ // painting the object in: nothing ahead of the front, fully charged right behind it
52
+ if (uTelReveal > 0.5) { if (TelD > 0.0) discard; charged = 1.0; }
53
+ float heat = pow(clamp(vTelQ.y, 0.0, 1.0), 2.6) * (0.85 + 0.15 * sin(uTelTime * 5.0 + fxFbm(vTelQ * 6.0 + uTelTime * 0.4) * 12.0));
54
+ vec3 hot = mix(uTelGlow * 0.9, vec3(2.0), heat) * (0.5 + 1.5 * heat);
55
+ outgoingLight = mix(outgoingLight, hot, charged * (0.12 + 0.88 * heat));
56
+ } else {
57
+ float cut = 0.1 + 0.35 * fxFbm(vTelQ * 7.0);
58
+ if (vTelK > cut) discard;
59
+ float h = smoothstep(0.0, cut, vTelK);
60
+ outgoingLight = mix(outgoingLight, uTelGlow * (1.5 + 3.5 * h), h);
61
+ }
48
62
  }`,
49
63
  };
50
64
  }
@@ -114,13 +128,16 @@ function updateColumn(s) {
114
128
  export default defineOperation({
115
129
  value: 'teleport',
116
130
  label: 'Teleport',
117
- description: 'Draw the targets into a point of light and send them up a beam — reverting brings them back down',
131
+ description: 'Draw the targets into a point of light and send them up a beam (or, Disappear off, keep them charged under the beam) — reverting brings them back down',
118
132
  channel: 'visual',
119
133
  multi: true,
120
134
  toggles: true,
121
- usableIn: ['interaction', 'action'],
135
+ usableIn: ['interaction', 'action', 'object'],
136
+ objectParams: { disappear: false },
137
+ presence: 'paint',
122
138
  params: {
123
139
  direction: { type: 'select', label: 'Direction', default: 'down', options: DIRECTION_OPTIONS },
140
+ disappear: { type: 'boolean', label: 'Disappear', default: true, hint: 'Off: the object stays charged under the beam' },
124
141
  color: { type: 'color', label: 'Light', default: '#a8ecff' },
125
142
  sparks: { type: 'number', label: 'Particles', default: 9000, min: 1000, max: 60000, step: 1000 },
126
143
  beam: { type: 'number', label: 'Beam height', default: 3, min: 0.5, max: 10, step: 0.1, unit: '×' },
@@ -133,15 +150,17 @@ export default defineOperation({
133
150
  if (!st) {
134
151
  const field = createFieldState(target);
135
152
  field.edgeWidth.value = 0.1;
136
- const own = { field, life: { value: LIFE }, glow: { value: new Color() }, beam: { value: 3 }, width: { value: 1 }, mats: new Set() };
153
+ const own = { field, life: { value: LIFE }, glow: { value: new Color() }, beam: { value: 3 }, width: { value: 1 }, keep: { value: 0 }, mats: new Set() };
137
154
  const sample = sampleSurfaceCached(target, Math.round(Number(p.sparks)), 23);
138
155
  own.points = createFxPoints(target, sample, {
139
156
  additive: true,
140
- uniforms: { ...fieldUniforms(P, field), uTelLife: own.life, uTelGlow: own.glow, uTelBeam: own.beam, uTelWidth: own.width },
157
+ uniforms: { ...fieldUniforms(P, field), uTelLife: own.life, uTelGlow: own.glow, uTelBeam: own.beam, uTelWidth: own.width, uTelKeep: own.keep },
141
158
  vertexPars: fieldGlsl(P) + GLSL_POINT_SIZE + GLSL_ROT,
142
159
  vertexMain: /* glsl */ `
143
160
  float birth = TelBirthOf(TelField(TelNorm(aP)));
144
161
  float a = (uTelProgress - birth) / (uTelLife * 1.6);
162
+ // keep: once charged, a particle cycles up the beam over and over
163
+ if (uTelKeep > 0.5) a = uTelProgress > birth ? 0.5 + 0.5 * fract(u${P}Time * 0.2 + aR.w) : 0.0;
145
164
  if (a <= 0.0 || a >= 1.0) { gl_Position = vec4(2.0, 2.0, 2.0, 1.0); gl_PointSize = 0.0; vColor = vec3(0.0); vAlpha = 0.0; return; }
146
165
  float E = uTelExtent;
147
166
  vec3 S = ${SING};
@@ -184,7 +203,17 @@ export default defineOperation({
184
203
  s.beam.value = Number(p.beam);
185
204
  s.width.value = Number(p.width);
186
205
  s.points.visible = true;
206
+ s.keep.value = p.disappear === false ? 1 : 0;
207
+ hideMeshes(target, KEY, false);
187
208
  updateColumn(s);
209
+ if (s.keep.value) {
210
+ // keep: the charge climbs the object (0 → 1), the beam stands, and it holds
211
+ return runProgress(target, s.field, 1, {
212
+ duration: Number(p.duration) / END, ease: 'none', hold: true,
213
+ onUpdate: () => updateColumn(s),
214
+ onComplete: () => updateColumn(s),
215
+ });
216
+ }
188
217
  return runProgress(target, s.field, END, {
189
218
  duration: Number(p.duration), ease: 'power1.in',
190
219
  onUpdate: () => updateColumn(s),
@@ -196,9 +225,11 @@ export default defineOperation({
196
225
  if (!s)
197
226
  return;
198
227
  hideMeshes(target, KEY, false);
228
+ setReveal(s.field, p);
199
229
  s.points.visible = true;
230
+ // from a hold it only has part of the way to go: keep the same speed
200
231
  return runProgress(target, s.field, 0, {
201
- duration: Number(p.duration), ease: 'power1.out',
232
+ duration: Number(p.duration) * Math.min(1, Math.max(0.1, s.field.progress.value / END)), ease: s.keep.value ? 'none' : 'power1.out',
202
233
  onUpdate: () => updateColumn(s),
203
234
  onComplete: () => {
204
235
  s.mats.forEach(m => removeLayer(m, KEY));
@@ -1 +1 @@
1
- {"version":3,"file":"unweave.d.ts","sourceRoot":"","sources":["../../../src/operations/catalogue/unweave.ts"],"names":[],"mappings":";AA2EA,wBA+IG"}
1
+ {"version":3,"file":"unweave.d.ts","sourceRoot":"","sources":["../../../src/operations/catalogue/unweave.ts"],"names":[],"mappings":";AA+EA,wBAgKG"}