@semio/utils 0.0.0 → 0.1.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.mjs CHANGED
@@ -19,103 +19,114 @@ function getId(lookup) {
19
19
  }
20
20
 
21
21
  // src/player.ts
22
+ var PlayerDirection = /* @__PURE__ */ ((PlayerDirection3) => {
23
+ PlayerDirection3["Reverse"] = "reverse";
24
+ PlayerDirection3["Forward"] = "forward";
25
+ return PlayerDirection3;
26
+ })(PlayerDirection || {});
22
27
  function reset(player, stamp) {
23
28
  const p = { ...player };
24
29
  const nowVal = now();
25
30
  p._currentTime = nowVal;
26
31
  p._previousTime = nowVal;
27
32
  p.stamp = stamp ? stamp : 0;
33
+ p._currentDirection = p.direction;
34
+ p._bounced = false;
35
+ p._enteredBounds = false;
28
36
  return p;
29
37
  }
30
38
  function now() {
31
39
  return (typeof performance === "undefined" ? Date : performance).now();
32
40
  }
33
- function update(player, coldStart) {
41
+ function updated(player, coldStart) {
34
42
  const p = { ...player };
35
43
  const duration = p.duration;
36
44
  const t = now();
37
45
  p._previousTime = coldStart ? t : p._currentTime;
38
46
  p._currentTime = t;
39
47
  const currentInBounds = p.stamp >= p.bounds[0] && p.stamp <= p.bounds[1];
40
- const currentViewportCenter = (p.viewport[1] + p.viewport[0]) / 2;
41
- const nearViewportCenterCurrent = Math.abs(p.stamp - currentViewportCenter) < 0.01;
42
- const delta = (p._currentTime - p._previousTime) * p.timescale / duration;
48
+ if (currentInBounds && !p._enteredBounds) {
49
+ p._enteredBounds = true;
50
+ }
51
+ const effectiveTimescale = p.speed * (p._currentDirection === "forward" /* Forward */ ? 1 : -1);
52
+ const delta = (p._currentTime - p._previousTime) * effectiveTimescale;
43
53
  const updatedStamp = p.stamp + delta;
44
- const [start, end] = currentInBounds ? p.bounds : [0, 1];
45
- let attachOverride = false;
54
+ const [start, end] = !p._enteredBounds && !currentInBounds ? [0, duration] : p.bounds;
55
+ const movingTowardBounds = p.stamp < start && p._currentDirection === "forward" /* Forward */ || p.stamp > end && p._currentDirection === "reverse" /* Reverse */;
46
56
  if (updatedStamp > end) {
47
- if (p.playback === "loop") {
48
- p.stamp = start;
49
- } else if (p.playback === "bounce") {
50
- p.stamp = end;
51
- p.timescale *= -1;
57
+ if (movingTowardBounds) {
58
+ p.stamp = updatedStamp;
52
59
  } else {
53
- p.stamp = start;
54
- p.running = false;
55
- }
56
- attachOverride = true;
57
- } else if (currentInBounds && updatedStamp < start) {
58
- if (p.playback === "loop") {
59
- p.stamp = start;
60
- if (p.timescale < 0) {
60
+ if (p.bounce && (p.looping || !p._bounced)) {
61
+ p.stamp = end;
62
+ p._currentDirection = p._currentDirection === "forward" /* Forward */ ? "reverse" /* Reverse */ : "forward" /* Forward */;
63
+ p._bounced = true;
64
+ } else if (p.looping) {
65
+ p.stamp = start;
66
+ } else {
67
+ p.stamp = end;
61
68
  p.running = false;
62
69
  }
63
- } else if (p.playback === "bounce") {
64
- p.stamp = start;
65
- p.timescale *= -1;
70
+ }
71
+ } else if (updatedStamp < start) {
72
+ if (movingTowardBounds) {
73
+ p.stamp = updatedStamp;
66
74
  } else {
67
- p.stamp = start;
68
- p.running = false;
75
+ if (p.bounce && (p.looping || !p._bounced)) {
76
+ p.stamp = start;
77
+ p._currentDirection = p._currentDirection === "forward" /* Forward */ ? "reverse" /* Reverse */ : "forward" /* Forward */;
78
+ p._bounced = true;
79
+ } else if (p.looping) {
80
+ p.stamp = end;
81
+ } else {
82
+ p.stamp = start;
83
+ p.running = false;
84
+ }
69
85
  }
70
- attachOverride = true;
71
86
  } else {
72
87
  p.stamp = updatedStamp;
73
88
  }
74
- const newNearViewportCenter = Math.abs(p.stamp - currentViewportCenter) < 0.01;
75
- const boundsInsideViewport = p.viewport[0] <= p.bounds[0] || p.viewport[1] >= p.bounds[1];
76
- if (p.running && !boundsInsideViewport && (nearViewportCenterCurrent || newNearViewportCenter || attachOverride)) {
77
- p.viewport = getFittedViewport(p.stamp, p.viewport);
78
- } else if (p.running && !boundsInsideViewport && !newNearViewportCenter) {
79
- const oomphOffset = currentViewportCenter < p.stamp ? delta : 0;
80
- const idealViewport = getFittedViewport(p.stamp + oomphOffset, p.viewport);
81
- p.viewport = [
82
- p.viewport[0] * 0.6 + idealViewport[0] * 0.4,
83
- p.viewport[1] * 0.6 + idealViewport[1] * 0.4
84
- ];
85
- }
86
89
  return p;
87
90
  }
88
- function setBounds(player, bounds) {
91
+ function withBounds(player, bounds) {
89
92
  const p = { ...player };
90
93
  p.bounds = bounds;
91
94
  return p;
92
95
  }
93
- function setViewport(player, viewport) {
94
- const p = { ...player };
95
- p.viewport = viewport;
96
- return p;
97
- }
98
- function play(player, speed) {
96
+ function play(player, speed, direction) {
99
97
  const p = { ...player };
100
98
  p.running = true;
101
- p.timescale = speed ?? 1;
102
- if (p.playback === "once" && p.stamp === p.bounds[1]) {
99
+ if (speed !== void 0) p.speed = Math.abs(speed);
100
+ if (direction !== void 0) {
101
+ p.direction = direction;
102
+ }
103
+ p._currentDirection = p.direction;
104
+ p._bounced = false;
105
+ p._enteredBounds = false;
106
+ if (!p.looping && !p.bounce) {
107
+ if (p.direction === "forward" /* Forward */ && p.stamp === p.bounds[1]) {
108
+ p.stamp = p.bounds[0];
109
+ } else if (p.direction === "reverse" /* Reverse */ && p.stamp === p.bounds[0]) {
110
+ p.stamp = p.bounds[1];
111
+ }
112
+ }
113
+ const tolerance = 1;
114
+ if (p._currentDirection === "forward" /* Forward */ && Math.abs(p.stamp - p.bounds[1]) <= tolerance) {
103
115
  p.stamp = p.bounds[0];
116
+ } else if (p._currentDirection === "reverse" /* Reverse */ && Math.abs(p.stamp - p.bounds[0]) <= tolerance) {
117
+ p.stamp = p.bounds[1];
104
118
  }
105
119
  return p;
106
120
  }
107
121
  function pause(player) {
108
122
  const p = { ...player };
109
123
  p.running = false;
110
- p.timescale = 0;
111
124
  return p;
112
125
  }
113
126
  function seek(player, stamp) {
114
- const p = reset(player, stamp);
115
- p.viewport = getFittedViewport(stamp, p.viewport);
116
- return p;
127
+ return reset(player, stamp);
117
128
  }
118
- function setDuration(player, duration) {
129
+ function withDuration(player, duration) {
119
130
  const p = { ...player };
120
131
  p.duration = duration;
121
132
  return p;
@@ -126,25 +137,52 @@ function newPlayer() {
126
137
  _previousTime: now(),
127
138
  _currentTime: now(),
128
139
  stamp: 0,
129
- timescale: 0,
130
- bounds: [0, 1],
131
- playback: "loop",
132
- viewport: [0, 1],
133
- duration: 1e3
140
+ speed: 1,
141
+ direction: "forward" /* Forward */,
142
+ _currentDirection: "forward" /* Forward */,
143
+ _bounced: false,
144
+ _enteredBounds: false,
145
+ bounds: [0, 5e3],
146
+ bounce: false,
147
+ looping: true,
148
+ duration: 5e3
134
149
  };
135
150
  }
136
- var getFittedViewport = (playhead, proposedViewport) => {
137
- const viewportWidth = proposedViewport[1] - proposedViewport[0];
138
- const proposedLeft = playhead - viewportWidth / 2;
139
- const proposedRight = playhead + viewportWidth / 2;
140
- if (proposedLeft >= 0 && proposedRight <= 1) {
141
- return [playhead - viewportWidth / 2, playhead + viewportWidth / 2];
142
- } else if (proposedLeft < 0) {
143
- return [0, viewportWidth];
144
- } else {
145
- return [1 - viewportWidth, 1];
151
+ function withSpeed(player, speed) {
152
+ const p = { ...player };
153
+ if (speed < 0) {
154
+ throw new Error("Speed must be a positive number.");
146
155
  }
147
- };
156
+ p.speed = speed;
157
+ return p;
158
+ }
159
+ function withDirection(player, direction) {
160
+ const p = { ...player };
161
+ p.direction = direction;
162
+ p._currentDirection = direction;
163
+ p._bounced = false;
164
+ p._enteredBounds = false;
165
+ return p;
166
+ }
167
+ function reversed(player) {
168
+ const p = { ...player };
169
+ const newDirection = p.direction === "forward" /* Forward */ ? "reverse" /* Reverse */ : "forward" /* Forward */;
170
+ p.direction = newDirection;
171
+ p._currentDirection = newDirection;
172
+ p._bounced = false;
173
+ p._enteredBounds = false;
174
+ return p;
175
+ }
176
+ function withBounce(player, bounce) {
177
+ const p = { ...player };
178
+ p.bounce = bounce;
179
+ return p;
180
+ }
181
+ function withLooping(player, looping) {
182
+ const p = { ...player };
183
+ p.looping = looping;
184
+ return p;
185
+ }
148
186
 
149
187
  // src/player-store.ts
150
188
  import { createContext } from "react";
@@ -156,29 +194,50 @@ THREE.Object3D.DEFAULT_UP.set(0, 0, 1);
156
194
  enableMapSet();
157
195
  var PlayerSlice = (set) => ({
158
196
  player: newPlayer(),
159
- // Set the duration of the player
197
+ // Set the duration of the player. Also rescales bounds if they were at
198
+ // the old default. Uses `produce` (rather than the spread+`withX` pattern
199
+ // the other actions use) because the bounds rescaling is a multi-field
200
+ // atomic conditional write. Viewport rescaling is handled separately by
201
+ // the viewport-follow subscription.
160
202
  updateDuration: (duration) => {
161
203
  set(
162
204
  produce((state) => {
205
+ const oldDuration = state.player.duration;
163
206
  state.player.duration = duration;
207
+ if (oldDuration > 0 && duration > 0) {
208
+ const b = state.player.bounds;
209
+ if (Math.abs(b[1] - oldDuration) < 1) {
210
+ state.player.bounds = [b[0], duration];
211
+ }
212
+ }
164
213
  })
165
214
  );
166
215
  },
167
- // Set the timescale of the animation playback
168
- updateTimescale: (speed) => {
169
- set(
170
- produce((state) => {
171
- state.player.timescale = speed;
172
- })
173
- );
216
+ // Set the speed of the animation playback
217
+ updateSpeed: (speed) => {
218
+ set((state) => ({
219
+ player: withSpeed(state.player, speed)
220
+ }));
221
+ },
222
+ // Set the direction of the animation playback
223
+ updateDirection: (direction) => {
224
+ set((state) => ({
225
+ player: withDirection(state.player, direction)
226
+ }));
227
+ },
228
+ // Reverse the current direction of the animation playback
229
+ reverseDirection: () => {
230
+ set((state) => ({
231
+ player: reversed(state.player)
232
+ }));
174
233
  },
175
234
  // Set the time of the animation
176
235
  resetPlayer: (time) => {
177
236
  set((state) => ({ player: reset(state.player, time) }));
178
237
  },
179
238
  // Play the animation
180
- playPlayer: (speed) => {
181
- set((state) => ({ player: play(state.player, speed) }));
239
+ playPlayer: (speed, direction) => {
240
+ set((state) => ({ player: play(state.player, speed, direction) }));
182
241
  },
183
242
  // Pause the animation
184
243
  pausePlayer: () => {
@@ -187,13 +246,13 @@ var PlayerSlice = (set) => ({
187
246
  // Update the timer
188
247
  updatePlayer: (coldStart) => {
189
248
  set((state) => ({
190
- player: update(state.player, coldStart ?? false)
249
+ player: updated(state.player, coldStart ?? false)
191
250
  }));
192
251
  },
193
252
  // Set the player bounds
194
253
  updatePlayerBounds: (start, end) => {
195
254
  set((state) => ({
196
- player: setBounds(state.player, [start, end])
255
+ player: withBounds(state.player, [start, end])
197
256
  }));
198
257
  },
199
258
  updatePlayerBound: (bound, time) => {
@@ -201,54 +260,32 @@ var PlayerSlice = (set) => ({
201
260
  const t = time ?? state.player.stamp;
202
261
  if (bound === "start" && state.player.bounds[1] <= t) {
203
262
  return {
204
- player: setBounds(state.player, [state.player.bounds[1], t])
263
+ player: withBounds(state.player, [state.player.bounds[1], t])
205
264
  };
206
265
  } else if (bound === "end" && state.player.bounds[0] >= t) {
207
266
  return {
208
- player: setBounds(state.player, [t, state.player.bounds[0]])
267
+ player: withBounds(state.player, [t, state.player.bounds[0]])
209
268
  };
210
269
  } else if (bound === "start") {
211
270
  return {
212
- player: setBounds(state.player, [t, state.player.bounds[1]])
271
+ player: withBounds(state.player, [t, state.player.bounds[1]])
213
272
  };
214
273
  } else {
215
274
  return {
216
- player: setBounds(state.player, [state.player.bounds[0], t])
275
+ player: withBounds(state.player, [state.player.bounds[0], t])
217
276
  };
218
277
  }
219
278
  });
220
279
  },
221
- // Set the player viewport
222
- updatePlayerViewport: (start, end) => {
280
+ updateBounce: (bounce) => {
223
281
  set((state) => ({
224
- player: setViewport(state.player, [start, end])
282
+ player: withBounce(state.player, bounce)
225
283
  }));
226
284
  },
227
- updatePlayerViewportCenter: (time) => {
285
+ updateLooping: (looping) => {
228
286
  set((state) => ({
229
- player: seek(state.player, time ?? state.player.stamp)
287
+ player: withLooping(state.player, looping)
230
288
  }));
231
- },
232
- updatePlayerViewportBound: (bound, time) => {
233
- set((state) => {
234
- if (bound === "start" && state.player.viewport[1] <= time) {
235
- return {
236
- player: setViewport(state.player, [state.player.viewport[1], time])
237
- };
238
- } else if (bound === "end" && state.player.viewport[0] >= time) {
239
- return {
240
- player: setViewport(state.player, [time, state.player.viewport[0]])
241
- };
242
- } else if (bound === "start") {
243
- return {
244
- player: setViewport(state.player, [time, state.player.viewport[1]])
245
- };
246
- } else {
247
- return {
248
- player: setViewport(state.player, [state.player.viewport[0], time])
249
- };
250
- }
251
- });
252
289
  }
253
290
  });
254
291
  var usePlayerStore = create()(
@@ -257,6 +294,17 @@ var usePlayerStore = create()(
257
294
  var PlayerContext = createContext(null);
258
295
 
259
296
  // src/animated-values.ts
297
+ var AnimatableGroupType = /* @__PURE__ */ ((AnimatableGroupType2) => {
298
+ AnimatableGroupType2["Vector2"] = "vector2";
299
+ AnimatableGroupType2["Vector3"] = "vector3";
300
+ AnimatableGroupType2["Euler"] = "euler";
301
+ AnimatableGroupType2["RGB"] = "rgb";
302
+ AnimatableGroupType2["HSL"] = "hsl";
303
+ AnimatableGroupType2["Group"] = "group";
304
+ AnimatableGroupType2["Entity"] = "entity";
305
+ AnimatableGroupType2["Instance"] = "instance";
306
+ return AnimatableGroupType2;
307
+ })(AnimatableGroupType || {});
260
308
  function instanceOfRawBoolean(object) {
261
309
  return typeof object === "boolean";
262
310
  }
@@ -270,10 +318,10 @@ function instanceOfRawVector3(object) {
270
318
  return object.x !== void 0 && object.y !== void 0 && object.z !== void 0;
271
319
  }
272
320
  function instanceOfRawVector2(object) {
273
- return object.x !== void 0 && object.y !== void 0;
321
+ return object.x !== void 0 && object.y !== void 0 && object.z === void 0;
274
322
  }
275
323
  function instanceOfRawEuler(object) {
276
- return object.x !== void 0 && object.y !== void 0 && object.z !== void 0;
324
+ return object.r !== void 0 && object.p !== void 0 && object.y !== void 0;
277
325
  }
278
326
  function instanceOfRawColor(object) {
279
327
  return object.r !== void 0 && object.g !== void 0 && object.b !== void 0 || object.h !== void 0 && object.s !== void 0 && object.l !== void 0;
@@ -284,6 +332,12 @@ function instanceOfRawRGB(object) {
284
332
  function instanceOfRawHSL(object) {
285
333
  return object.h !== void 0 && object.s !== void 0 && object.l !== void 0;
286
334
  }
335
+ function warnUnexpectedFeatureValue(feature, expected, value) {
336
+ console.warn(
337
+ `Feature "${feature}" expected ${expected}, received ${typeof value}:`,
338
+ value
339
+ );
340
+ }
287
341
  function isRawObject(value) {
288
342
  if (instanceOfRawVector3(value) || instanceOfRawVector2(value) || instanceOfRawEuler(value) || instanceOfRawColor(value) || instanceOfRawRGB(value) || instanceOfRawHSL(value))
289
343
  return true;
@@ -365,9 +419,11 @@ function getDegenerateHexagonPath(x, y, size, height) {
365
419
  }
366
420
 
367
421
  // src/closest-frame.ts
368
- function closestFrame(completion, count) {
369
- const frameIdx = Math.round(completion * count);
370
- return frameIdx / count;
422
+ function closestFrame(stampMs, framerate) {
423
+ if (framerate <= 0) return stampMs;
424
+ const frameDurationMs = 1e3 / framerate;
425
+ const frameIdx = Math.round(stampMs / frameDurationMs);
426
+ return frameIdx * frameDurationMs;
371
427
  }
372
428
 
373
429
  // src/angles.ts
@@ -462,19 +518,62 @@ function rawRGBToHex({ r, g, b }) {
462
518
  function rawHSLToHex({ h, s, l }) {
463
519
  return Color({ h, s, l }).hex();
464
520
  }
521
+ function randomHexString(constrainLuminosity, constrainSaturation) {
522
+ const h = Math.floor(Math.random() * 255);
523
+ const s = Math.floor(constrainSaturation ? constrainSaturation * 100 : Math.random() * 100);
524
+ const l = Math.floor(constrainLuminosity ? constrainLuminosity * 100 : Math.random() * 100);
525
+ return Color({ h, s, l }).hex();
526
+ }
527
+ function rgbChroma([r, g, b]) {
528
+ const max = Math.max(r, g, b);
529
+ const min = Math.min(r, g, b);
530
+ return (max - min) / 255;
531
+ }
532
+ function rgbLightness([r, g, b]) {
533
+ const max = Math.max(r, g, b);
534
+ const min = Math.min(r, g, b);
535
+ return (max + min) / (2 * 255);
536
+ }
537
+ function colorChroma(color) {
538
+ return rgbChroma(hexToRgbArray(color));
539
+ }
540
+ function colorLightness(color) {
541
+ return rgbLightness(hexToRgbArray(color));
542
+ }
543
+ function shouldUseBlendForLabelColor(color, opts) {
544
+ if (!color) return false;
545
+ let rgb;
546
+ try {
547
+ rgb = hexToRgbArray(color);
548
+ } catch {
549
+ return false;
550
+ }
551
+ const chroma = rgbChroma(rgb);
552
+ const lightness = rgbLightness(rgb);
553
+ const { chromaCutoff = 0.08, minLightness = 0.28, maxLightness = 0.75 } = opts ?? {};
554
+ const isNearGray = chroma < chromaCutoff;
555
+ const isMidLightness = lightness > minLightness && lightness < maxLightness;
556
+ return !(isNearGray && isMidLightness);
557
+ }
558
+ function isNearNeutralMid(color, opts) {
559
+ const { chromaCutoff = 0.08, minLightness = 0.28, maxLightness = 0.75 } = opts ?? {};
560
+ const c = colorChroma(color);
561
+ const l = colorLightness(color);
562
+ return c < chromaCutoff && l > minLightness && l < maxLightness;
563
+ }
465
564
 
466
565
  // src/use-deep.ts
467
566
  import { useRef } from "react";
468
567
  import deepEqual from "deep-equal";
469
568
  function useDeepSelector(selector) {
470
- const prev = useRef();
569
+ const prev = useRef(void 0);
471
570
  return (state) => {
472
571
  const next = selector(state);
473
572
  return deepEqual(prev.current, next) ? prev.current : prev.current = next;
474
573
  };
475
574
  }
476
575
  function useDeep(initializer, dependencies) {
477
- const prevValue = useRef();
576
+ const prevValue = useRef(void 0);
478
577
  const prevDependencies = useRef([]);
479
578
  return deepEqual(prevDependencies.current, dependencies) ? prevValue.current : prevValue.current = initializer();
480
579
  }
@@ -612,6 +711,98 @@ function angularDistance(euler1, euler2) {
612
711
  ];
613
712
  return rotationMatrixToAngle(R12);
614
713
  }
714
+ function eulerToQuaternion(euler) {
715
+ const [z, y, x] = euler;
716
+ const cx = Math.cos(x * 0.5);
717
+ const sx = Math.sin(x * 0.5);
718
+ const cy = Math.cos(y * 0.5);
719
+ const sy = Math.sin(y * 0.5);
720
+ const cz = Math.cos(z * 0.5);
721
+ const sz = Math.sin(z * 0.5);
722
+ const w = cx * cy * cz + sx * sy * sz;
723
+ const qx = sx * cy * cz - cx * sy * sz;
724
+ const qy = cx * sy * cz + sx * cy * sz;
725
+ const qz = cx * cy * sz - sx * sy * cz;
726
+ return [w, qx, qy, qz];
727
+ }
728
+ function quaternionToEuler(q) {
729
+ const [w, x, y, z] = q;
730
+ const sinrCosp = 2 * (w * x + y * z);
731
+ const cosrCosp = 1 - 2 * (x * x + y * y);
732
+ const roll = Math.atan2(sinrCosp, cosrCosp);
733
+ const sinp = 2 * (w * y - z * x);
734
+ let pitch;
735
+ if (Math.abs(sinp) >= 1) {
736
+ pitch = Math.sign(sinp) * Math.PI / 2;
737
+ } else {
738
+ pitch = Math.asin(sinp);
739
+ }
740
+ const sinyCosp = 2 * (w * z + x * y);
741
+ const cosyCosp = 1 - 2 * (y * y + z * z);
742
+ const yaw = Math.atan2(sinyCosp, cosyCosp);
743
+ return [yaw, pitch, roll];
744
+ }
745
+ function quaternionMultiply(q1, q2) {
746
+ const [w1, x1, y1, z1] = q1;
747
+ const [w2, x2, y2, z2] = q2;
748
+ const w = w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2;
749
+ const x = w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2;
750
+ const y = w1 * y2 - x1 * z2 + y1 * w2 + z1 * x2;
751
+ const z = w1 * z2 + x1 * y2 - y1 * x2 + z1 * w2;
752
+ return [w, x, y, z];
753
+ }
754
+ function quaternionConjugate(q) {
755
+ const [w, x, y, z] = q;
756
+ return [w, -x, -y, -z];
757
+ }
758
+ function quaternionSlerp(q1, q2, t) {
759
+ const [w1, x1, y1, z1] = q1;
760
+ const [w2, x2, y2, z2] = q2;
761
+ let dot = w1 * w2 + x1 * x2 + y1 * y2 + z1 * z2;
762
+ let q2Adjusted = [w2, x2, y2, z2];
763
+ if (dot < 0) {
764
+ q2Adjusted = [-w2, -x2, -y2, -z2];
765
+ dot = -dot;
766
+ }
767
+ const DOT_THRESHOLD = 0.9995;
768
+ if (dot > DOT_THRESHOLD) {
769
+ const result = [
770
+ w1 + t * (q2Adjusted[0] - w1),
771
+ x1 + t * (q2Adjusted[1] - x1),
772
+ y1 + t * (q2Adjusted[2] - y1),
773
+ z1 + t * (q2Adjusted[3] - z1)
774
+ ];
775
+ const length = Math.sqrt(
776
+ result[0] * result[0] + result[1] * result[1] + result[2] * result[2] + result[3] * result[3]
777
+ );
778
+ return [result[0] / length, result[1] / length, result[2] / length, result[3] / length];
779
+ }
780
+ const theta0 = Math.acos(Math.abs(dot));
781
+ const sinTheta0 = Math.sin(theta0);
782
+ const theta = theta0 * t;
783
+ const sinTheta = Math.sin(theta);
784
+ const s0 = Math.cos(theta) - dot * sinTheta / sinTheta0;
785
+ const s1 = sinTheta / sinTheta0;
786
+ return [
787
+ s0 * w1 + s1 * q2Adjusted[0],
788
+ s0 * x1 + s1 * q2Adjusted[1],
789
+ s0 * y1 + s1 * q2Adjusted[2],
790
+ s0 * z1 + s1 * q2Adjusted[3]
791
+ ];
792
+ }
793
+ function eulerSlerp(euler1, euler2, t) {
794
+ const q1 = eulerToQuaternion(euler1);
795
+ const q2 = eulerToQuaternion(euler2);
796
+ const qResult = quaternionSlerp(q1, q2, t);
797
+ return quaternionToEuler(qResult);
798
+ }
799
+ function eulerRotationBetween(euler1, euler2) {
800
+ const q1 = eulerToQuaternion(euler1);
801
+ const q2 = eulerToQuaternion(euler2);
802
+ const q1Conjugate = quaternionConjugate(q1);
803
+ const qDiff = quaternionMultiply(q2, q1Conjugate);
804
+ return quaternionToEuler(qDiff);
805
+ }
615
806
 
616
807
  // src/overlapping-segments.ts
617
808
  function overlappingSegments(original) {
@@ -806,30 +997,227 @@ var EMAIL = /^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\]
806
997
 
807
998
  // src/map-deep-equal.ts
808
999
  import isDeepEqual from "fast-deep-equal";
809
- function isMapDeepEqual(map1, map2) {
810
- if (map1.size !== map2.size) return false;
1000
+ function isMapDeepEqual(map1, map2, options = {}) {
1001
+ if (map1 === map2) return true;
1002
+ const { filter } = options || {};
1003
+ if (!filter) {
1004
+ if (map1.size !== map2.size) return false;
1005
+ for (const [key, value] of map1.entries()) {
1006
+ if (!map2.has(key)) return false;
1007
+ const otherValue = map2.get(key);
1008
+ if (value !== otherValue && !isDeepEqual(value, otherValue)) {
1009
+ return false;
1010
+ }
1011
+ }
1012
+ return true;
1013
+ }
1014
+ const filteredEntries1 = [];
1015
+ const filteredEntries2 = [];
811
1016
  for (const [key, value] of map1.entries()) {
812
- if (!map2.has(key)) return false;
813
- const otherValue = map2.get(key);
814
- if (!isDeepEqual(value, otherValue)) {
1017
+ if (filter(key, value)) {
1018
+ filteredEntries1.push([key, value]);
1019
+ }
1020
+ }
1021
+ for (const [key, value] of map2.entries()) {
1022
+ if (filter(key, value)) {
1023
+ filteredEntries2.push([key, value]);
1024
+ }
1025
+ }
1026
+ if (filteredEntries1.length !== filteredEntries2.length) {
1027
+ return false;
1028
+ }
1029
+ const filteredMap2 = new Map(filteredEntries2);
1030
+ for (const [key, value] of filteredEntries1) {
1031
+ if (!filteredMap2.has(key)) return false;
1032
+ const otherValue = filteredMap2.get(key);
1033
+ if (value !== otherValue && !isDeepEqual(value, otherValue)) {
815
1034
  return false;
816
1035
  }
817
1036
  }
818
1037
  return true;
819
1038
  }
1039
+
1040
+ // src/migrate-euler.ts
1041
+ function storedEulerXYZtoRPY(robotData) {
1042
+ if ("rotation" in robotData.features) {
1043
+ if (!robotData.features.rotation.animated) {
1044
+ const rotationValue = robotData.features.rotation.value;
1045
+ if (!instanceOfRawEuler(rotationValue)) {
1046
+ const oldRot = rotationValue;
1047
+ const newRot = { r: oldRot.x, p: oldRot.y, y: oldRot.z };
1048
+ robotData.features.rotation.value = newRot;
1049
+ }
1050
+ } else {
1051
+ const rotationValue = robotData.features.rotation.value;
1052
+ if (!instanceOfRawEuler(rotationValue.default)) {
1053
+ const oldRot = rotationValue.default;
1054
+ const newRot = { r: oldRot.x, p: oldRot.y, y: oldRot.z };
1055
+ robotData.features.rotation.value.default = newRot;
1056
+ }
1057
+ }
1058
+ }
1059
+ if ("origin" in robotData) {
1060
+ const rotationValue = robotData.origin.rotation;
1061
+ if (!instanceOfRawEuler(rotationValue)) {
1062
+ const oldRot = rotationValue;
1063
+ const newRot = { r: oldRot.x, p: oldRot.y, y: oldRot.z };
1064
+ robotData.origin.rotation = newRot;
1065
+ }
1066
+ }
1067
+ return robotData;
1068
+ }
1069
+
1070
+ // src/react/context-providers.tsx
1071
+ import { useContext, createElement } from "react";
1072
+ function createContextHook(context, name) {
1073
+ return function useRequiredContext() {
1074
+ const value = useContext(context);
1075
+ if (!value) {
1076
+ throw new Error(
1077
+ `${name} context is required. Make sure you're using the component within a ${name}Provider.`
1078
+ );
1079
+ }
1080
+ return value;
1081
+ };
1082
+ }
1083
+ function withOptionalContext(CoreComponent, contextConfig) {
1084
+ const { context, defaultValue } = contextConfig;
1085
+ return function ComponentWithContext(props) {
1086
+ const { skipContext = false, ...coreProps } = props;
1087
+ const existingContext = useContext(context);
1088
+ if (skipContext || existingContext) {
1089
+ return createElement(CoreComponent, coreProps);
1090
+ }
1091
+ return createElement(
1092
+ context.Provider,
1093
+ { value: defaultValue },
1094
+ createElement(CoreComponent, coreProps)
1095
+ );
1096
+ };
1097
+ }
1098
+ function composeContexts(contexts) {
1099
+ return function ComposedContexts({ children }) {
1100
+ return contexts.reduceRight((acc, { Provider, condition }) => {
1101
+ if (condition && !condition()) {
1102
+ return acc;
1103
+ }
1104
+ return createElement(Provider, {}, acc);
1105
+ }, children);
1106
+ };
1107
+ }
1108
+ function createContextProvider(context, defaultValue) {
1109
+ return function ContextProvider({
1110
+ children,
1111
+ value = defaultValue
1112
+ }) {
1113
+ return createElement(context.Provider, { value }, children);
1114
+ };
1115
+ }
1116
+ function createContextSolution(config) {
1117
+ const { context, defaultValue, name } = config;
1118
+ const useRequiredContext = createContextHook(context, name);
1119
+ const Provider = createContextProvider(context, defaultValue);
1120
+ const withOptional = (Component) => withOptionalContext(Component, config);
1121
+ return {
1122
+ useContext: useRequiredContext,
1123
+ Provider,
1124
+ withOptional,
1125
+ context: config.context
1126
+ };
1127
+ }
1128
+ function useOptionalContext(context) {
1129
+ return useContext(context);
1130
+ }
1131
+ function createContextChecker(context) {
1132
+ return function useHasContext() {
1133
+ return useContext(context) !== null;
1134
+ };
1135
+ }
1136
+
1137
+ // src/values-store.ts
1138
+ import { create as create2 } from "zustand";
1139
+ import { subscribeWithSelector as subscribeWithSelector2 } from "zustand/middleware";
1140
+ function mergeValueMaps(base, updates) {
1141
+ if (updates.size === 0) return base;
1142
+ let hasChange = false;
1143
+ updates.forEach((value, key) => {
1144
+ if (hasChange) return;
1145
+ if (value === void 0) {
1146
+ if (base.has(key)) hasChange = true;
1147
+ } else if (base.get(key) !== value) {
1148
+ hasChange = true;
1149
+ }
1150
+ });
1151
+ if (!hasChange) return base;
1152
+ const merged = new Map(base);
1153
+ updates.forEach((value, key) => {
1154
+ if (value === void 0) {
1155
+ merged.delete(key);
1156
+ } else {
1157
+ merged.set(key, value);
1158
+ }
1159
+ });
1160
+ return merged;
1161
+ }
1162
+ var useValuesStore = create2()(
1163
+ subscribeWithSelector2((set) => ({
1164
+ values: /* @__PURE__ */ new Map(),
1165
+ velocities: /* @__PURE__ */ new Map(),
1166
+ updateValues: (updates) => {
1167
+ set((state) => ({
1168
+ values: mergeValueMaps(state.values, updates)
1169
+ }));
1170
+ },
1171
+ updateVelocities: (updates) => {
1172
+ set((state) => ({
1173
+ velocities: mergeValueMaps(state.velocities, updates)
1174
+ }));
1175
+ },
1176
+ setTrackValue: (trackKey, value) => {
1177
+ set((state) => {
1178
+ const next = new Map(state.values);
1179
+ next.set(trackKey, value);
1180
+ return { values: next };
1181
+ });
1182
+ },
1183
+ replaceAll: (values, velocities) => {
1184
+ set({ values, velocities });
1185
+ }
1186
+ }))
1187
+ );
1188
+
1189
+ // src/three-guards.ts
1190
+ function isOrthographicCamera(camera) {
1191
+ return "isOrthographicCamera" in camera;
1192
+ }
1193
+ function isPerspectiveCamera(camera) {
1194
+ return "isPerspectiveCamera" in camera;
1195
+ }
820
1196
  export {
1197
+ AnimatableGroupType,
821
1198
  EMAIL,
822
1199
  PlayerContext,
1200
+ PlayerDirection,
823
1201
  PlayerSlice,
824
1202
  Result,
825
1203
  alpha,
826
1204
  altColor,
827
1205
  angularDistance,
828
1206
  closestFrame,
1207
+ colorChroma,
1208
+ colorLightness,
1209
+ composeContexts,
829
1210
  composeProgress,
830
1211
  composeProgressiveResult,
1212
+ createContextChecker,
1213
+ createContextHook,
1214
+ createContextProvider,
1215
+ createContextSolution,
831
1216
  downloadBlob,
832
1217
  downloadJSONFile,
1218
+ eulerRotationBetween,
1219
+ eulerSlerp,
1220
+ eulerToQuaternion,
833
1221
  eulerToRotationMatrix,
834
1222
  getDegenerateHexagonPath,
835
1223
  getHexagonPath,
@@ -850,8 +1238,12 @@ export {
850
1238
  instanceOfRawVector2,
851
1239
  instanceOfRawVector3,
852
1240
  isMapDeepEqual,
1241
+ isNearNeutralMid,
1242
+ isOrthographicCamera,
1243
+ isPerspectiveCamera,
853
1244
  isRawObject,
854
1245
  log,
1246
+ mergeValueMaps,
855
1247
  newPlayer,
856
1248
  now,
857
1249
  numberToDurationString,
@@ -862,22 +1254,39 @@ export {
862
1254
  pause,
863
1255
  play,
864
1256
  pointsToPath,
1257
+ quaternionConjugate,
1258
+ quaternionMultiply,
1259
+ quaternionSlerp,
1260
+ quaternionToEuler,
1261
+ randomHexString,
865
1262
  rawHSLToHex,
866
1263
  rawRGBToHex,
867
1264
  reset,
1265
+ reversed,
1266
+ rgbChroma,
1267
+ rgbLightness,
868
1268
  rgbToRgba,
869
1269
  rotationMatrixToAngle,
870
1270
  seek,
871
- setBounds,
872
- setDuration,
873
- setViewport,
1271
+ shouldUseBlendForLabelColor,
1272
+ storedEulerXYZtoRPY,
874
1273
  stringifyMapped,
875
1274
  toDegrees,
876
1275
  toRadians,
877
- update,
1276
+ updated,
878
1277
  useDeep,
879
1278
  useDeepSelector,
880
1279
  useLazy,
881
1280
  useMediaQuery,
882
- usePlayerStore
1281
+ useOptionalContext,
1282
+ usePlayerStore,
1283
+ useValuesStore,
1284
+ warnUnexpectedFeatureValue,
1285
+ withBounce,
1286
+ withBounds,
1287
+ withDirection,
1288
+ withDuration,
1289
+ withLooping,
1290
+ withOptionalContext,
1291
+ withSpeed
883
1292
  };