@lovo/matter 0.4.0 → 0.5.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.js CHANGED
@@ -1,4 +1,4 @@
1
- // src/runtime/createRenderer.ts
1
+ // src/runtime/create-renderer/create-renderer.ts
2
2
  import { Color } from "three";
3
3
  import { WebGPURenderer } from "three/webgpu";
4
4
  async function createRenderer(canvas, opts = {}) {
@@ -19,10 +19,10 @@ async function createRenderer(canvas, opts = {}) {
19
19
  const resolvedClearColor = clearColor instanceof Color ? clearColor : new Color(clearColor);
20
20
  three.setClearColor(resolvedClearColor, clearAlpha);
21
21
  const resize = () => {
22
- const w = canvas.clientWidth;
23
- const h = canvas.clientHeight;
24
- if (canvas.width !== w * three.getPixelRatio() || canvas.height !== h * three.getPixelRatio()) {
25
- three.setSize(w, h, false);
22
+ const canvasWidth = canvas.clientWidth;
23
+ const canvasHeight = canvas.clientHeight;
24
+ if (canvas.width !== canvasWidth * three.getPixelRatio() || canvas.height !== canvasHeight * three.getPixelRatio()) {
25
+ three.setSize(canvasWidth, canvasHeight, false);
26
26
  }
27
27
  };
28
28
  resize();
@@ -36,7 +36,7 @@ async function createRenderer(canvas, opts = {}) {
36
36
  };
37
37
  }
38
38
 
39
- // src/inputs/CursorInput.ts
39
+ // src/inputs/cursor-input/cursor-input.ts
40
40
  var CursorInput = class {
41
41
  value;
42
42
  target;
@@ -56,16 +56,19 @@ var CursorInput = class {
56
56
  this.element = element;
57
57
  this.handleMouseMove = (e) => {
58
58
  if (!(e instanceof MouseEvent)) return;
59
- const me = e;
59
+ const mouseEvent = e;
60
60
  if (this.element) {
61
- const r = this.element.getBoundingClientRect();
62
- const w = r.width || 1;
63
- const h = r.height || 1;
64
- this.target = [(me.clientX - r.left) / w, (me.clientY - r.top) / h];
61
+ const elementRect = this.element.getBoundingClientRect();
62
+ const elementWidth = elementRect.width || 1;
63
+ const elementHeight = elementRect.height || 1;
64
+ this.target = [
65
+ (mouseEvent.clientX - elementRect.left) / elementWidth,
66
+ (mouseEvent.clientY - elementRect.top) / elementHeight
67
+ ];
65
68
  } else {
66
- const w = typeof window !== "undefined" && window.innerWidth || 1;
67
- const h = typeof window !== "undefined" && window.innerHeight || 1;
68
- this.target = [me.clientX / w, me.clientY / h];
69
+ const viewportWidth = typeof window !== "undefined" && window.innerWidth || 1;
70
+ const viewportHeight = typeof window !== "undefined" && window.innerHeight || 1;
71
+ this.target = [mouseEvent.clientX / viewportWidth, mouseEvent.clientY / viewportHeight];
69
72
  }
70
73
  this.targetDirty = true;
71
74
  };
@@ -76,9 +79,9 @@ var CursorInput = class {
76
79
  return this.value;
77
80
  }
78
81
  /** Subscribe to change events. Returns an unsubscribe function. */
79
- on(_event, cb) {
80
- this.listeners.add(cb);
81
- return () => this.listeners.delete(cb);
82
+ on(_eventType, changeListener) {
83
+ this.listeners.add(changeListener);
84
+ return () => this.listeners.delete(changeListener);
82
85
  }
83
86
  /**
84
87
  * Advance the smoothing one tick. Called by the host scheduler; not
@@ -107,10 +110,10 @@ var CursorInput = class {
107
110
  this.listeners.clear();
108
111
  }
109
112
  };
110
- var clamp01 = (n) => Math.max(0, Math.min(1, n));
111
- var lerp = (a, b, t) => a + (b - a) * t;
113
+ var clamp01 = (value) => Math.max(0, Math.min(1, value));
114
+ var lerp = (startValue, endValue, blendFactor) => startValue + (endValue - startValue) * blendFactor;
112
115
 
113
- // src/primitives/colorRamp.ts
116
+ // src/primitives/color-ramp/color-ramp.ts
114
117
  import { mix, vec3 } from "three/tsl";
115
118
  import { clamp, div, sub } from "three/tsl";
116
119
  function colorRamp(t, stops) {
@@ -119,78 +122,78 @@ function colorRamp(t, stops) {
119
122
  if (stops.length === 1) return mix(first.color, first.color, 0);
120
123
  let result = mix(first.color, first.color, 0);
121
124
  for (let i = 1; i < stops.length; i += 1) {
122
- const prev = stops[i - 1];
125
+ const previousStop = stops[i - 1];
123
126
  const next = stops[i];
124
- if (prev === void 0 || next === void 0) continue;
125
- const span = next.position - prev.position;
126
- if (span <= 0) continue;
127
- const localT = clamp(div(sub(t, prev.position), span), 0, 1);
127
+ if (previousStop === void 0 || next === void 0) continue;
128
+ const positionSpan = next.position - previousStop.position;
129
+ if (positionSpan <= 0) continue;
130
+ const localT = clamp(div(sub(t, previousStop.position), positionSpan), 0, 1);
128
131
  result = mix(result, next.color, localT);
129
132
  }
130
133
  return result;
131
134
  }
132
135
 
133
- // src/primitives/noise.ts
136
+ // src/primitives/noise/noise.ts
134
137
  import { mx_noise_float } from "three/tsl";
135
- function noise(p) {
138
+ function simplexNoise(p) {
136
139
  return mx_noise_float(p);
137
140
  }
138
141
 
139
- // src/primitives/fbm.ts
142
+ // src/primitives/fbm/fbm.ts
140
143
  import { add, mul } from "three/tsl";
141
- function fbm(p, opts = {}) {
144
+ function fractalNoise(p, opts = {}) {
142
145
  const octaves = opts.octaves ?? 4;
143
146
  const lacunarity = opts.lacunarity ?? 2;
144
147
  const gain = opts.gain ?? 0.5;
145
- let sum = noise(p);
146
- let amp = 1;
147
- let freq = 1;
148
- let total = amp;
148
+ let sum = simplexNoise(p);
149
+ let amplitude = 1;
150
+ let frequency = 1;
151
+ let total = amplitude;
149
152
  for (let i = 1; i < octaves; i += 1) {
150
- freq *= lacunarity;
151
- amp *= gain;
152
- total += amp;
153
- const pAtFreq = add(mul(p, freq), i * 100);
154
- const layer = noise(pAtFreq).mul(amp);
153
+ frequency *= lacunarity;
154
+ amplitude *= gain;
155
+ total += amplitude;
156
+ const pAtFreq = add(mul(p, frequency), i * 100);
157
+ const layer = simplexNoise(pAtFreq).mul(amplitude);
155
158
  sum = sum.add(layer);
156
159
  }
157
160
  return sum.div(total);
158
161
  }
159
162
 
160
- // src/primitives/voronoi.ts
163
+ // src/primitives/voronoi/voronoi.ts
161
164
  import { mx_worley_noise_float } from "three/tsl";
162
165
  function voronoi(p) {
163
166
  return mx_worley_noise_float(p);
164
167
  }
165
168
 
166
- // src/primitives/quantize.ts
169
+ // src/primitives/quantize/quantize.ts
167
170
  function quantize(t, steps) {
168
171
  if (steps <= 1) {
169
172
  return t.mul(0);
170
173
  }
171
- const denom = steps - 1;
172
- return t.mul(denom).add(0.5).floor().div(denom);
174
+ const denominator = steps - 1;
175
+ return t.mul(denominator).add(0.5).floor().div(denominator);
173
176
  }
174
177
 
175
- // src/primitives/sdfCircle.ts
178
+ // src/primitives/sdf-circle/sdf-circle.ts
176
179
  import { length } from "three/tsl";
177
- function sdfCircle(p, radius) {
180
+ function signedDistanceFieldCircle(p, radius) {
178
181
  return length(p).sub(radius);
179
182
  }
180
183
 
181
- // src/primitives/displace.ts
184
+ // src/primitives/displace/displace.ts
182
185
  import { add as add2 } from "three/tsl";
183
186
  function displace(p, by) {
184
187
  return add2(p, by);
185
188
  }
186
189
 
187
- // src/primitives/cursorRipple.ts
190
+ // src/primitives/cursor-ripple/cursor-ripple.ts
188
191
  import { length as length2, sin, smoothstep, sub as sub2 } from "three/tsl";
189
192
 
190
- // src/primitives/time.ts
193
+ // src/primitives/time/time.ts
191
194
  import { time as _builtinTime } from "three/tsl";
192
195
 
193
- // src/runtime/reducedMotion.ts
196
+ // src/runtime/reduced-motion/reduced-motion.ts
194
197
  import { uniform } from "three/tsl";
195
198
  var state = {
196
199
  policy: "auto",
@@ -199,7 +202,7 @@ var state = {
199
202
  function setReducedMotionPolicy(policy) {
200
203
  if (state.policy === policy) return;
201
204
  state.policy = policy;
202
- for (const w of state.watchers) w.recompute();
205
+ for (const watcher of state.watchers) watcher.recompute();
203
206
  }
204
207
  function getReducedMotionPolicy() {
205
208
  return state.policy;
@@ -220,8 +223,8 @@ function createReducedMotionWatcher() {
220
223
  if (typeof matchMedia !== "function") {
221
224
  return {
222
225
  scale: () => computeScale(false),
223
- subscribe: (cb) => {
224
- void cb;
226
+ subscribe: (listener) => {
227
+ void listener;
225
228
  return () => {
226
229
  };
227
230
  },
@@ -230,33 +233,33 @@ function createReducedMotionWatcher() {
230
233
  }
231
234
  };
232
235
  }
233
- const mql = matchMedia("(prefers-reduced-motion: reduce)");
234
- const subs = /* @__PURE__ */ new Set();
235
- let last = computeScale(mql.matches);
236
+ const mediaQueryList = matchMedia("(prefers-reduced-motion: reduce)");
237
+ const subscriptions = /* @__PURE__ */ new Set();
238
+ let lastComputedScale = computeScale(mediaQueryList.matches);
236
239
  const onChange = () => {
237
- const next = computeScale(mql.matches);
238
- if (next !== last) {
239
- last = next;
240
- for (const cb of subs) cb(next);
240
+ const next = computeScale(mediaQueryList.matches);
241
+ if (next !== lastComputedScale) {
242
+ lastComputedScale = next;
243
+ for (const listener of subscriptions) listener(next);
241
244
  }
242
245
  };
243
- mql.addEventListener("change", onChange);
246
+ mediaQueryList.addEventListener("change", onChange);
244
247
  const watcher = {
245
- scale: () => last,
246
- subscribe(cb) {
247
- subs.add(cb);
248
- return () => subs.delete(cb);
248
+ scale: () => lastComputedScale,
249
+ subscribe(listener) {
250
+ subscriptions.add(listener);
251
+ return () => subscriptions.delete(listener);
249
252
  },
250
253
  recompute() {
251
- const next = computeScale(mql.matches);
252
- if (next !== last) {
253
- last = next;
254
- for (const cb of subs) cb(next);
254
+ const next = computeScale(mediaQueryList.matches);
255
+ if (next !== lastComputedScale) {
256
+ lastComputedScale = next;
257
+ for (const listener of subscriptions) listener(next);
255
258
  }
256
259
  },
257
260
  dispose() {
258
- mql.removeEventListener("change", onChange);
259
- subs.clear();
261
+ mediaQueryList.removeEventListener("change", onChange);
262
+ subscriptions.clear();
260
263
  state.watchers.delete(watcher);
261
264
  }
262
265
  };
@@ -277,32 +280,30 @@ function getReducedMotionTimeScale() {
277
280
  return globalScaleUniform;
278
281
  }
279
282
 
280
- // src/primitives/time.ts
281
- var time = _builtinTime.mul(getReducedMotionTimeScale());
283
+ // src/primitives/time/time.ts
284
+ var elapsedTime = _builtinTime.mul(getReducedMotionTimeScale());
282
285
 
283
- // src/primitives/cursorRipple.ts
286
+ // src/primitives/cursor-ripple/cursor-ripple.ts
284
287
  function cursorRipple(p, center, opts = {}) {
285
288
  const reach = opts.reach ?? 0.4;
286
289
  const frequency = opts.frequency ?? 30;
287
290
  const speed = opts.speed ?? 6;
288
291
  const amplitude = opts.amplitude ?? 0.5;
289
292
  const d = length2(sub2(p, center));
290
- const wave = sin(d.mul(frequency).sub(time.mul(speed)));
293
+ const wave = sin(d.mul(frequency).sub(elapsedTime.mul(speed)));
291
294
  const decay = smoothstep(reach, 0, d);
292
295
  return wave.mul(amplitude).mul(decay);
293
296
  }
294
297
 
295
- // src/primitives/filmGrain.ts
296
- import { fract, length as length3, sin as sin2, vec2 } from "three/tsl";
297
- function filmGrain(uvNode, intensity, timeOffset = 0) {
298
- const HASH_C1 = vec2(2127.1, 81.17);
299
- const HASH_C2 = vec2(1269.5, 283.37);
300
- const base = vec2(uvNode.dot(HASH_C1).add(timeOffset), uvNode.dot(HASH_C2).add(timeOffset));
301
- const hash = fract(sin2(base).mul(43758.5453));
302
- return length3(hash).sub(0.765).mul(intensity);
298
+ // src/primitives/film-grain/film-grain.ts
299
+ import { hash, mul as mul2, screenCoordinate } from "three/tsl";
300
+ function filmGrain(intensity, timeOffset = 0) {
301
+ const pixel = screenCoordinate.xy.floor();
302
+ const seed = pixel.x.toUint().mul(1973).add(pixel.y.toUint().mul(9277)).add(mul2(timeOffset, 26699).toUint());
303
+ return hash(seed).sub(0.5).mul(intensity);
303
304
  }
304
305
 
305
- // src/runtime/visibility.ts
306
+ // src/runtime/visibility/visibility.ts
306
307
  function createVisibilityWatcher() {
307
308
  if (typeof document === "undefined") {
308
309
  return {
@@ -313,26 +314,26 @@ function createVisibilityWatcher() {
313
314
  }
314
315
  };
315
316
  }
316
- const subs = /* @__PURE__ */ new Set();
317
+ const subscriptions = /* @__PURE__ */ new Set();
317
318
  const onChange = () => {
318
- const v = document.visibilityState === "visible";
319
- for (const cb of subs) cb(v);
319
+ const isVisible = document.visibilityState === "visible";
320
+ for (const listener of subscriptions) listener(isVisible);
320
321
  };
321
322
  document.addEventListener("visibilitychange", onChange);
322
323
  return {
323
324
  isVisible: () => document.visibilityState === "visible",
324
- subscribe(cb) {
325
- subs.add(cb);
326
- return () => subs.delete(cb);
325
+ subscribe(listener) {
326
+ subscriptions.add(listener);
327
+ return () => subscriptions.delete(listener);
327
328
  },
328
329
  dispose() {
329
330
  document.removeEventListener("visibilitychange", onChange);
330
- subs.clear();
331
+ subscriptions.clear();
331
332
  }
332
333
  };
333
334
  }
334
335
 
335
- // src/runtime/intersection.ts
336
+ // src/runtime/intersection/intersection.ts
336
337
  function createIntersectionWatcher(canvas) {
337
338
  if (typeof IntersectionObserver === "undefined") {
338
339
  return {
@@ -343,41 +344,50 @@ function createIntersectionWatcher(canvas) {
343
344
  }
344
345
  };
345
346
  }
346
- const subs = /* @__PURE__ */ new Set();
347
+ const subscriptions = /* @__PURE__ */ new Set();
347
348
  let inView = true;
348
- const obs = new IntersectionObserver(
349
+ const observer = new IntersectionObserver(
349
350
  (entries) => {
350
- const next = entries.some((e) => e.isIntersecting);
351
+ const next = entries.some((entry) => entry.isIntersecting);
351
352
  if (next === inView) return;
352
353
  inView = next;
353
- for (const cb of subs) cb(inView);
354
+ for (const listener of subscriptions) listener(inView);
354
355
  },
355
356
  { threshold: 0 }
356
357
  );
357
- obs.observe(canvas);
358
+ observer.observe(canvas);
358
359
  return {
359
360
  isInView: () => inView,
360
- subscribe(cb) {
361
- subs.add(cb);
362
- return () => subs.delete(cb);
361
+ subscribe(listener) {
362
+ subscriptions.add(listener);
363
+ return () => subscriptions.delete(listener);
363
364
  },
364
365
  dispose() {
365
- obs.disconnect();
366
- subs.clear();
366
+ observer.disconnect();
367
+ subscriptions.clear();
367
368
  }
368
369
  };
369
370
  }
370
371
 
371
- // src/runtime/frame-scheduler.ts
372
+ // src/runtime/frame-scheduler/frame-scheduler.ts
372
373
  var FrameScheduler = class {
373
374
  clients = /* @__PURE__ */ new Set();
374
375
  rafId = null;
375
376
  running = false;
376
377
  paused = false;
377
- idle = false;
378
378
  flushPending = false;
379
379
  startedAt = 0;
380
380
  lastTickAt = 0;
381
+ // Reference-counted idle voting. The scheduler is idle only when at least
382
+ // one component has voted idle AND no component has voted animated. This
383
+ // prevents a static component (e.g. LinearGradient speed=0) from halting
384
+ // the loop while an animated overlay (e.g. FilmGrain) is still running.
385
+ idleVotes = 0;
386
+ animatedVotes = 0;
387
+ /** True when all participating components prefer idle and none need animation. */
388
+ get idle() {
389
+ return this.idleVotes > 0 && this.animatedVotes === 0;
390
+ }
381
391
  /** Activate the scheduler. The rAF loop starts on the first client added. */
382
392
  start() {
383
393
  this.running = true;
@@ -413,19 +423,39 @@ var FrameScheduler = class {
413
423
  this.clients.clear();
414
424
  }
415
425
  /**
416
- * Mark the scheduler idle. The next tick still fires (a final flush so
417
- * uniform changes that triggered the idle state are rendered), then the
418
- * rAF loop halts. Use `requestRender()` or `setIdle(false)` to wake.
426
+ * Cast a vote on whether the scheduler should be idle.
427
+ *
428
+ * `setIdle(true)` increments the idle-vote count; the returned cleanup
429
+ * decrements it. `setIdle(false)` increments the animated-vote count;
430
+ * its cleanup decrements that. The scheduler halts (after one flush tick)
431
+ * only when `idleVotes > 0 && animatedVotes === 0`.
432
+ *
433
+ * Callers are responsible for calling the returned cleanup on unmount.
434
+ * Use `requestRender()` or cast a `setIdle(false)` vote to wake the loop
435
+ * without permanently registering an animated preference.
419
436
  */
420
437
  setIdle(idle) {
421
- if (this.idle === idle) return;
422
- this.idle = idle;
423
438
  if (idle) {
424
- this.flushPending = true;
425
- this.maybeQueue();
439
+ const wasIdle = this.idle;
440
+ this.idleVotes += 1;
441
+ const nowIdle = this.idle;
442
+ if (!wasIdle && nowIdle) this.onBecameIdle();
443
+ return () => {
444
+ const prevIdle = this.idle;
445
+ this.idleVotes = Math.max(0, this.idleVotes - 1);
446
+ const afterIdle = this.idle;
447
+ if (prevIdle && !afterIdle) this.onBecameAnimated();
448
+ };
426
449
  } else {
427
- this.flushPending = false;
428
- this.maybeQueue();
450
+ const wasIdle = this.idle;
451
+ this.animatedVotes += 1;
452
+ if (wasIdle) this.onBecameAnimated();
453
+ return () => {
454
+ const prevIdle = this.idle;
455
+ this.animatedVotes = Math.max(0, this.animatedVotes - 1);
456
+ const nowIdle = this.idle;
457
+ if (!prevIdle && nowIdle) this.onBecameIdle();
458
+ };
429
459
  }
430
460
  }
431
461
  /** Force a single tick while idle. Useful for prop-change invalidation. */
@@ -434,6 +464,14 @@ var FrameScheduler = class {
434
464
  this.flushPending = true;
435
465
  this.maybeQueue();
436
466
  }
467
+ onBecameIdle() {
468
+ this.flushPending = true;
469
+ this.maybeQueue();
470
+ }
471
+ onBecameAnimated() {
472
+ this.flushPending = false;
473
+ this.maybeQueue();
474
+ }
437
475
  maybeQueue() {
438
476
  if (this.rafId !== null) return;
439
477
  if (!this.running) return;
@@ -475,15 +513,15 @@ export {
475
513
  createVisibilityWatcher,
476
514
  cursorRipple,
477
515
  displace,
478
- fbm,
516
+ elapsedTime,
479
517
  filmGrain,
518
+ fractalNoise,
480
519
  getReducedMotionPolicy,
481
520
  getReducedMotionTimeScale,
482
- noise,
483
521
  quantize,
484
- sdfCircle,
485
522
  setReducedMotionPolicy,
486
- time,
523
+ signedDistanceFieldCircle,
524
+ simplexNoise,
487
525
  voronoi
488
526
  };
489
527
  //# sourceMappingURL=index.js.map