@almadar/ui 5.42.0 → 5.43.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.
@@ -28563,6 +28563,78 @@ var init_GameOverScreen = __esm({
28563
28563
  GameOverScreen.displayName = "GameOverScreen";
28564
28564
  }
28565
28565
  });
28566
+ function useRenderInterpolation(options = {}) {
28567
+ const tickIntervalMs = options.tickIntervalMs ?? 1e3 / 30;
28568
+ const prevRef = useRef(null);
28569
+ const currRef = useRef(null);
28570
+ const rafRef = useRef(null);
28571
+ const onSnapshot = useCallback((entities) => {
28572
+ prevRef.current = currRef.current;
28573
+ currRef.current = { entities, arrivedAt: performance.now() };
28574
+ }, []);
28575
+ const getInterpolated = useCallback((now) => {
28576
+ const out = /* @__PURE__ */ new Map();
28577
+ const curr = currRef.current;
28578
+ if (!curr) return out;
28579
+ const prev = prevRef.current;
28580
+ if (!prev) {
28581
+ for (const e of curr.entities) out.set(e.id, { x: e.x, y: e.y });
28582
+ return out;
28583
+ }
28584
+ const rawAlpha = (now - curr.arrivedAt) / tickIntervalMs;
28585
+ const alpha = rawAlpha < 0 ? 0 : rawAlpha > 1 ? 1 : rawAlpha;
28586
+ const prevMap = /* @__PURE__ */ new Map();
28587
+ for (const e of prev.entities) prevMap.set(e.id, e);
28588
+ for (const c of curr.entities) {
28589
+ const p2 = prevMap.get(c.id);
28590
+ if (!p2) {
28591
+ out.set(c.id, { x: c.x, y: c.y });
28592
+ } else {
28593
+ out.set(c.id, {
28594
+ x: p2.x + (c.x - p2.x) * alpha,
28595
+ y: p2.y + (c.y - p2.y) * alpha
28596
+ });
28597
+ }
28598
+ }
28599
+ return out;
28600
+ }, [tickIntervalMs]);
28601
+ const startLoop = useCallback(
28602
+ (draw) => {
28603
+ let active = true;
28604
+ const loop = () => {
28605
+ if (!active) return;
28606
+ try {
28607
+ draw(getInterpolated(performance.now()));
28608
+ } catch {
28609
+ }
28610
+ rafRef.current = requestAnimationFrame(loop);
28611
+ };
28612
+ rafRef.current = requestAnimationFrame(loop);
28613
+ return () => {
28614
+ active = false;
28615
+ if (rafRef.current !== null) {
28616
+ cancelAnimationFrame(rafRef.current);
28617
+ rafRef.current = null;
28618
+ }
28619
+ };
28620
+ },
28621
+ [getInterpolated]
28622
+ );
28623
+ useEffect(() => {
28624
+ return () => {
28625
+ if (rafRef.current !== null) {
28626
+ cancelAnimationFrame(rafRef.current);
28627
+ rafRef.current = null;
28628
+ }
28629
+ };
28630
+ }, []);
28631
+ return { onSnapshot, getInterpolated, startLoop };
28632
+ }
28633
+ var init_useRenderInterpolation = __esm({
28634
+ "hooks/useRenderInterpolation.ts"() {
28635
+ "use client";
28636
+ }
28637
+ });
28566
28638
  function PlatformerCanvas({
28567
28639
  player,
28568
28640
  platforms = [
@@ -28631,8 +28703,43 @@ function PlatformerCanvas({
28631
28703
  y: 336,
28632
28704
  width: 32,
28633
28705
  height: 48,
28706
+ vx: 0,
28707
+ vy: 0,
28708
+ grounded: true,
28634
28709
  facingRight: true
28635
28710
  };
28711
+ const playerRef = useRef(resolvedPlayer);
28712
+ playerRef.current = resolvedPlayer;
28713
+ const interp = useRenderInterpolation();
28714
+ useEffect(() => {
28715
+ interp.onSnapshot([{ id: "player", x: resolvedPlayer.x, y: resolvedPlayer.y }]);
28716
+ }, [resolvedPlayer.x, resolvedPlayer.y]);
28717
+ const propsRef = useRef({
28718
+ platforms,
28719
+ worldWidth,
28720
+ worldHeight,
28721
+ canvasWidth,
28722
+ canvasHeight,
28723
+ followCamera,
28724
+ bgColor,
28725
+ playerSprite,
28726
+ tileSprites,
28727
+ backgroundImage,
28728
+ assetBaseUrl
28729
+ });
28730
+ propsRef.current = {
28731
+ platforms,
28732
+ worldWidth,
28733
+ worldHeight,
28734
+ canvasWidth,
28735
+ canvasHeight,
28736
+ followCamera,
28737
+ bgColor,
28738
+ playerSprite,
28739
+ tileSprites,
28740
+ backgroundImage,
28741
+ assetBaseUrl
28742
+ };
28636
28743
  const handleKeyDown = useCallback((e) => {
28637
28744
  if (keysRef.current.has(e.code)) return;
28638
28745
  keysRef.current.add(e.code);
@@ -28681,124 +28788,149 @@ function PlatformerCanvas({
28681
28788
  canvas.width = canvasWidth * dpr;
28682
28789
  canvas.height = canvasHeight * dpr;
28683
28790
  ctx.scale(dpr, dpr);
28684
- let camX = 0;
28685
- let camY = 0;
28686
- if (followCamera) {
28687
- camX = Math.max(0, Math.min(resolvedPlayer.x - canvasWidth / 2, worldWidth - canvasWidth));
28688
- camY = Math.max(0, Math.min(resolvedPlayer.y - canvasHeight / 2 - 50, worldHeight - canvasHeight));
28689
- }
28690
- const bgImg = backgroundImage ? loadImage(backgroundImage) : null;
28691
- if (bgImg) {
28692
- ctx.drawImage(bgImg, 0, 0, canvasWidth, canvasHeight);
28693
- } else if (bgColor) {
28694
- ctx.fillStyle = bgColor;
28695
- ctx.fillRect(0, 0, canvasWidth, canvasHeight);
28696
- } else {
28697
- const grad = ctx.createLinearGradient(0, 0, 0, canvasHeight);
28698
- grad.addColorStop(0, SKY_GRADIENT_TOP);
28699
- grad.addColorStop(1, SKY_GRADIENT_BOTTOM);
28700
- ctx.fillStyle = grad;
28701
- ctx.fillRect(0, 0, canvasWidth, canvasHeight);
28702
- }
28703
- ctx.strokeStyle = GRID_COLOR;
28704
- ctx.lineWidth = 1;
28705
- const gridSize = 32;
28706
- for (let gx = -camX % gridSize; gx < canvasWidth; gx += gridSize) {
28707
- ctx.beginPath();
28708
- ctx.moveTo(gx, 0);
28709
- ctx.lineTo(gx, canvasHeight);
28710
- ctx.stroke();
28711
- }
28712
- for (let gy = -camY % gridSize; gy < canvasHeight; gy += gridSize) {
28713
- ctx.beginPath();
28714
- ctx.moveTo(0, gy);
28715
- ctx.lineTo(canvasWidth, gy);
28716
- ctx.stroke();
28717
- }
28718
- for (const plat of platforms) {
28719
- const px = plat.x - camX;
28720
- const py = plat.y - camY;
28721
- const platType = plat.type ?? "ground";
28722
- const spriteUrl = tileSprites?.[platType];
28723
- const tileImg = spriteUrl ? loadImage(spriteUrl) : null;
28724
- if (tileImg) {
28725
- const tileW = tileImg.naturalWidth;
28726
- const tileH = tileImg.naturalHeight;
28727
- const scaleH = plat.height / tileH;
28728
- const scaledW = tileW * scaleH;
28729
- for (let tx = 0; tx < plat.width; tx += scaledW) {
28730
- const drawW = Math.min(scaledW, plat.width - tx);
28731
- const srcW = drawW / scaleH;
28732
- ctx.drawImage(tileImg, 0, 0, srcW, tileH, px + tx, py, drawW, plat.height);
28733
- }
28791
+ }, [canvasWidth, canvasHeight]);
28792
+ useEffect(() => {
28793
+ const drawFrame = (positions) => {
28794
+ const canvas = canvasRef.current;
28795
+ if (!canvas) return;
28796
+ const ctx = canvas.getContext("2d");
28797
+ if (!ctx) return;
28798
+ const {
28799
+ platforms: plats,
28800
+ worldWidth: ww,
28801
+ worldHeight: wh,
28802
+ canvasWidth: cw,
28803
+ canvasHeight: ch,
28804
+ followCamera: fc,
28805
+ bgColor: bg,
28806
+ playerSprite: pSprite,
28807
+ tileSprites: tSprites,
28808
+ backgroundImage: bgImg
28809
+ } = propsRef.current;
28810
+ const auth = playerRef.current;
28811
+ const interped = positions.get("player");
28812
+ const px = interped?.x ?? auth.x;
28813
+ const py = interped?.y ?? auth.y;
28814
+ let camX = 0;
28815
+ let camY = 0;
28816
+ if (fc) {
28817
+ camX = Math.max(0, Math.min(px - cw / 2, ww - cw));
28818
+ camY = Math.max(0, Math.min(py - ch / 2 - 50, wh - ch));
28819
+ }
28820
+ const bgImage = bgImg ? loadImage(bgImg) : null;
28821
+ if (bgImage) {
28822
+ ctx.drawImage(bgImage, 0, 0, cw, ch);
28823
+ } else if (bg) {
28824
+ ctx.fillStyle = bg;
28825
+ ctx.fillRect(0, 0, cw, ch);
28734
28826
  } else {
28735
- const color = PLATFORM_COLORS[platType] ?? PLATFORM_COLORS.ground;
28736
- ctx.fillStyle = color;
28737
- ctx.fillRect(px, py, plat.width, plat.height);
28738
- ctx.fillStyle = "rgba(255, 255, 255, 0.15)";
28739
- ctx.fillRect(px, py, plat.width, 3);
28740
- ctx.fillStyle = "rgba(0, 0, 0, 0.3)";
28741
- ctx.fillRect(px, py + plat.height - 2, plat.width, 2);
28742
- if (platType === "hazard") {
28743
- ctx.strokeStyle = "#e74c3c";
28744
- ctx.lineWidth = 2;
28745
- for (let sx = px; sx < px + plat.width; sx += 12) {
28827
+ const grad = ctx.createLinearGradient(0, 0, 0, ch);
28828
+ grad.addColorStop(0, SKY_GRADIENT_TOP);
28829
+ grad.addColorStop(1, SKY_GRADIENT_BOTTOM);
28830
+ ctx.fillStyle = grad;
28831
+ ctx.fillRect(0, 0, cw, ch);
28832
+ }
28833
+ ctx.strokeStyle = GRID_COLOR;
28834
+ ctx.lineWidth = 1;
28835
+ const gridSize = 32;
28836
+ for (let gx = -camX % gridSize; gx < cw; gx += gridSize) {
28837
+ ctx.beginPath();
28838
+ ctx.moveTo(gx, 0);
28839
+ ctx.lineTo(gx, ch);
28840
+ ctx.stroke();
28841
+ }
28842
+ for (let gy = -camY % gridSize; gy < ch; gy += gridSize) {
28843
+ ctx.beginPath();
28844
+ ctx.moveTo(0, gy);
28845
+ ctx.lineTo(cw, gy);
28846
+ ctx.stroke();
28847
+ }
28848
+ for (const plat of plats) {
28849
+ const platX = plat.x - camX;
28850
+ const platY = plat.y - camY;
28851
+ const platType = plat.type ?? "ground";
28852
+ const spriteUrl = tSprites?.[platType];
28853
+ const tileImg = spriteUrl ? loadImage(spriteUrl) : null;
28854
+ if (tileImg) {
28855
+ const tileW = tileImg.naturalWidth;
28856
+ const tileH = tileImg.naturalHeight;
28857
+ const scaleH = plat.height / tileH;
28858
+ const scaledW = tileW * scaleH;
28859
+ for (let tx = 0; tx < plat.width; tx += scaledW) {
28860
+ const drawW = Math.min(scaledW, plat.width - tx);
28861
+ const srcW = drawW / scaleH;
28862
+ ctx.drawImage(tileImg, 0, 0, srcW, tileH, platX + tx, platY, drawW, plat.height);
28863
+ }
28864
+ } else {
28865
+ const color = PLATFORM_COLORS[platType] ?? PLATFORM_COLORS.ground;
28866
+ ctx.fillStyle = color;
28867
+ ctx.fillRect(platX, platY, plat.width, plat.height);
28868
+ ctx.fillStyle = "rgba(255, 255, 255, 0.15)";
28869
+ ctx.fillRect(platX, platY, plat.width, 3);
28870
+ ctx.fillStyle = "rgba(0, 0, 0, 0.3)";
28871
+ ctx.fillRect(platX, platY + plat.height - 2, plat.width, 2);
28872
+ if (platType === "hazard") {
28873
+ ctx.strokeStyle = "#e74c3c";
28874
+ ctx.lineWidth = 2;
28875
+ for (let sx = platX; sx < platX + plat.width; sx += 12) {
28876
+ ctx.beginPath();
28877
+ ctx.moveTo(sx, platY);
28878
+ ctx.lineTo(sx + 6, platY + plat.height);
28879
+ ctx.stroke();
28880
+ }
28881
+ }
28882
+ if (platType === "goal") {
28883
+ ctx.fillStyle = "rgba(241, 196, 15, 0.5)";
28746
28884
  ctx.beginPath();
28747
- ctx.moveTo(sx, py);
28748
- ctx.lineTo(sx + 6, py + plat.height);
28749
- ctx.stroke();
28885
+ ctx.arc(platX + plat.width / 2, platY - 10, 8, 0, Math.PI * 2);
28886
+ ctx.fill();
28750
28887
  }
28751
28888
  }
28752
- if (platType === "goal") {
28753
- ctx.fillStyle = "rgba(241, 196, 15, 0.5)";
28754
- ctx.beginPath();
28755
- ctx.arc(px + plat.width / 2, py - 10, 8, 0, Math.PI * 2);
28756
- ctx.fill();
28757
- }
28758
28889
  }
28759
- }
28760
- const pw = resolvedPlayer.width ?? 24;
28761
- const ph = resolvedPlayer.height ?? 32;
28762
- const ppx = resolvedPlayer.x - camX;
28763
- const ppy = resolvedPlayer.y - camY;
28764
- const facingRight = resolvedPlayer.facingRight ?? true;
28765
- const playerImg = playerSprite ? loadImage(playerSprite) : null;
28766
- if (playerImg) {
28767
- ctx.save();
28768
- if (!facingRight) {
28769
- ctx.translate(ppx + pw, ppy);
28770
- ctx.scale(-1, 1);
28771
- ctx.drawImage(playerImg, 0, 0, pw, ph);
28890
+ const pw = auth.width ?? 24;
28891
+ const ph = auth.height ?? 32;
28892
+ const ppx = px - camX;
28893
+ const ppy = py - camY;
28894
+ const facingRight = auth.facingRight ?? true;
28895
+ const playerImg = pSprite ? loadImage(pSprite) : null;
28896
+ if (playerImg) {
28897
+ ctx.save();
28898
+ if (!facingRight) {
28899
+ ctx.translate(ppx + pw, ppy);
28900
+ ctx.scale(-1, 1);
28901
+ ctx.drawImage(playerImg, 0, 0, pw, ph);
28902
+ } else {
28903
+ ctx.drawImage(playerImg, ppx, ppy, pw, ph);
28904
+ }
28905
+ ctx.restore();
28772
28906
  } else {
28773
- ctx.drawImage(playerImg, ppx, ppy, pw, ph);
28907
+ ctx.fillStyle = PLAYER_COLOR;
28908
+ const radius = Math.min(pw, ph) * 0.25;
28909
+ ctx.beginPath();
28910
+ ctx.moveTo(ppx + radius, ppy);
28911
+ ctx.lineTo(ppx + pw - radius, ppy);
28912
+ ctx.quadraticCurveTo(ppx + pw, ppy, ppx + pw, ppy + radius);
28913
+ ctx.lineTo(ppx + pw, ppy + ph - radius);
28914
+ ctx.quadraticCurveTo(ppx + pw, ppy + ph, ppx + pw - radius, ppy + ph);
28915
+ ctx.lineTo(ppx + radius, ppy + ph);
28916
+ ctx.quadraticCurveTo(ppx, ppy + ph, ppx, ppy + ph - radius);
28917
+ ctx.lineTo(ppx, ppy + radius);
28918
+ ctx.quadraticCurveTo(ppx, ppy, ppx + radius, ppy);
28919
+ ctx.fill();
28920
+ const eyeY = ppy + ph * 0.3;
28921
+ const eyeSize = 3;
28922
+ const eyeOffsetX = facingRight ? pw * 0.55 : pw * 0.2;
28923
+ ctx.fillStyle = PLAYER_EYE_COLOR;
28924
+ ctx.beginPath();
28925
+ ctx.arc(ppx + eyeOffsetX, eyeY, eyeSize, 0, Math.PI * 2);
28926
+ ctx.fill();
28927
+ ctx.beginPath();
28928
+ ctx.arc(ppx + eyeOffsetX + 7, eyeY, eyeSize, 0, Math.PI * 2);
28929
+ ctx.fill();
28774
28930
  }
28775
- ctx.restore();
28776
- } else {
28777
- ctx.fillStyle = PLAYER_COLOR;
28778
- const radius = Math.min(pw, ph) * 0.25;
28779
- ctx.beginPath();
28780
- ctx.moveTo(ppx + radius, ppy);
28781
- ctx.lineTo(ppx + pw - radius, ppy);
28782
- ctx.quadraticCurveTo(ppx + pw, ppy, ppx + pw, ppy + radius);
28783
- ctx.lineTo(ppx + pw, ppy + ph - radius);
28784
- ctx.quadraticCurveTo(ppx + pw, ppy + ph, ppx + pw - radius, ppy + ph);
28785
- ctx.lineTo(ppx + radius, ppy + ph);
28786
- ctx.quadraticCurveTo(ppx, ppy + ph, ppx, ppy + ph - radius);
28787
- ctx.lineTo(ppx, ppy + radius);
28788
- ctx.quadraticCurveTo(ppx, ppy, ppx + radius, ppy);
28789
- ctx.fill();
28790
- const eyeY = ppy + ph * 0.3;
28791
- const eyeSize = 3;
28792
- const eyeOffsetX = facingRight ? pw * 0.55 : pw * 0.2;
28793
- ctx.fillStyle = PLAYER_EYE_COLOR;
28794
- ctx.beginPath();
28795
- ctx.arc(ppx + eyeOffsetX, eyeY, eyeSize, 0, Math.PI * 2);
28796
- ctx.fill();
28797
- ctx.beginPath();
28798
- ctx.arc(ppx + eyeOffsetX + 7, eyeY, eyeSize, 0, Math.PI * 2);
28799
- ctx.fill();
28800
- }
28801
- }, [player, platforms, worldWidth, worldHeight, canvasWidth, canvasHeight, followCamera, bgColor, playerSprite, tileSprites, backgroundImage, assetBaseUrl, loadedImages]);
28931
+ };
28932
+ return interp.startLoop(drawFrame);
28933
+ }, [interp.startLoop, loadImage]);
28802
28934
  return /* @__PURE__ */ jsx(
28803
28935
  "canvas",
28804
28936
  {
@@ -28816,6 +28948,7 @@ var init_PlatformerCanvas = __esm({
28816
28948
  init_cn();
28817
28949
  init_useEventBus();
28818
28950
  init_verificationRegistry();
28951
+ init_useRenderInterpolation();
28819
28952
  PLATFORM_COLORS = {
28820
28953
  ground: "#4a7c59",
28821
28954
  platform: "#7c6b4a",
@@ -29198,13 +29331,13 @@ var init_MapView = __esm({
29198
29331
  shadowSize: [41, 41]
29199
29332
  });
29200
29333
  L.Marker.prototype.options.icon = defaultIcon;
29201
- const { useEffect: useEffect73, useRef: useRef68, useCallback: useCallback111, useState: useState104 } = React83__default;
29334
+ const { useEffect: useEffect74, useRef: useRef69, useCallback: useCallback112, useState: useState104 } = React83__default;
29202
29335
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
29203
29336
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
29204
29337
  function MapUpdater({ centerLat, centerLng, zoom }) {
29205
29338
  const map = useMap();
29206
- const prevRef = useRef68({ centerLat, centerLng, zoom });
29207
- useEffect73(() => {
29339
+ const prevRef = useRef69({ centerLat, centerLng, zoom });
29340
+ useEffect74(() => {
29208
29341
  const prev = prevRef.current;
29209
29342
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
29210
29343
  map.setView([centerLat, centerLng], zoom);
@@ -29215,7 +29348,7 @@ var init_MapView = __esm({
29215
29348
  }
29216
29349
  function MapClickHandler({ onMapClick }) {
29217
29350
  const map = useMap();
29218
- useEffect73(() => {
29351
+ useEffect74(() => {
29219
29352
  if (!onMapClick) return;
29220
29353
  const handler = (e) => {
29221
29354
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -29244,7 +29377,7 @@ var init_MapView = __esm({
29244
29377
  }) {
29245
29378
  const eventBus = useEventBus2();
29246
29379
  const [clickedPosition, setClickedPosition] = useState104(null);
29247
- const handleMapClick = useCallback111((lat, lng) => {
29380
+ const handleMapClick = useCallback112((lat, lng) => {
29248
29381
  if (showClickedPin) {
29249
29382
  setClickedPosition({ lat, lng });
29250
29383
  }
@@ -29253,7 +29386,7 @@ var init_MapView = __esm({
29253
29386
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
29254
29387
  }
29255
29388
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
29256
- const handleMarkerClick = useCallback111((marker) => {
29389
+ const handleMarkerClick = useCallback112((marker) => {
29257
29390
  onMarkerClick?.(marker);
29258
29391
  if (markerClickEvent) {
29259
29392
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
@@ -45106,11 +45239,13 @@ function SimulationCanvas({
45106
45239
  height = 400,
45107
45240
  running,
45108
45241
  speed = 1,
45242
+ bodies: externalBodies,
45109
45243
  className
45110
45244
  }) {
45111
45245
  const preset = useMemo(() => resolvePreset(presetProp), [presetProp]);
45112
45246
  const canvasRef = useRef(null);
45113
45247
  const bodiesRef = useRef(structuredClone(preset.bodies));
45248
+ const interp = useRenderInterpolation();
45114
45249
  useEffect(() => {
45115
45250
  bodiesRef.current = structuredClone(preset.bodies);
45116
45251
  }, [preset]);
@@ -45208,6 +45343,67 @@ function SimulationCanvas({
45208
45343
  }
45209
45344
  }, [width, height, preset]);
45210
45345
  useEffect(() => {
45346
+ if (!externalBodies) return;
45347
+ interp.onSnapshot(externalBodies.map((b) => ({ id: b.id, x: b.x, y: b.y })));
45348
+ }, [externalBodies]);
45349
+ const presetRef = useRef(preset);
45350
+ presetRef.current = preset;
45351
+ const widthRef = useRef(width);
45352
+ widthRef.current = width;
45353
+ const heightRef = useRef(height);
45354
+ heightRef.current = height;
45355
+ useEffect(() => {
45356
+ if (!externalBodies) return;
45357
+ const drawInterpolated = (positions) => {
45358
+ const canvas = canvasRef.current;
45359
+ if (!canvas) return;
45360
+ const ctx = canvas.getContext("2d");
45361
+ if (!ctx) return;
45362
+ const bodies = bodiesRef.current;
45363
+ const p2 = presetRef.current;
45364
+ const w = widthRef.current;
45365
+ const h = heightRef.current;
45366
+ ctx.clearRect(0, 0, w, h);
45367
+ ctx.fillStyle = p2.backgroundColor ?? "#1a1a2e";
45368
+ ctx.fillRect(0, 0, w, h);
45369
+ if (p2.constraints) {
45370
+ for (const c of p2.constraints) {
45371
+ const a = bodies[c.bodyA];
45372
+ const b = bodies[c.bodyB];
45373
+ if (a && b) {
45374
+ const aPos = positions.get(a.id) ?? a;
45375
+ const bPos = positions.get(b.id) ?? b;
45376
+ ctx.beginPath();
45377
+ ctx.moveTo(aPos.x, aPos.y);
45378
+ ctx.lineTo(bPos.x, bPos.y);
45379
+ ctx.strokeStyle = "#533483";
45380
+ ctx.lineWidth = 1;
45381
+ ctx.setLineDash([4, 4]);
45382
+ ctx.stroke();
45383
+ ctx.setLineDash([]);
45384
+ }
45385
+ }
45386
+ }
45387
+ for (const body of bodies) {
45388
+ const pos = positions.get(body.id) ?? body;
45389
+ ctx.beginPath();
45390
+ ctx.arc(pos.x, pos.y, body.radius, 0, Math.PI * 2);
45391
+ ctx.fillStyle = body.color ?? "#e94560";
45392
+ ctx.fill();
45393
+ if (p2.showVelocity) {
45394
+ ctx.beginPath();
45395
+ ctx.moveTo(pos.x, pos.y);
45396
+ ctx.lineTo(pos.x + body.vx * 0.1, pos.y + body.vy * 0.1);
45397
+ ctx.strokeStyle = "#16213e";
45398
+ ctx.lineWidth = 2;
45399
+ ctx.stroke();
45400
+ }
45401
+ }
45402
+ };
45403
+ return interp.startLoop(drawInterpolated);
45404
+ }, [externalBodies !== void 0, interp.startLoop]);
45405
+ useEffect(() => {
45406
+ if (externalBodies !== void 0) return;
45211
45407
  if (!running) return;
45212
45408
  let raf;
45213
45409
  const loop = () => {
@@ -45217,10 +45413,11 @@ function SimulationCanvas({
45217
45413
  };
45218
45414
  raf = requestAnimationFrame(loop);
45219
45415
  return () => cancelAnimationFrame(raf);
45220
- }, [running, step, draw]);
45416
+ }, [running, step, draw, externalBodies]);
45221
45417
  useEffect(() => {
45418
+ if (externalBodies !== void 0) return;
45222
45419
  draw();
45223
- }, [draw]);
45420
+ }, [draw, externalBodies]);
45224
45421
  useEffect(() => {
45225
45422
  if (typeof window === "undefined") return;
45226
45423
  const canvas = canvasRef.current;
@@ -45246,6 +45443,7 @@ var init_SimulationCanvas = __esm({
45246
45443
  init_atoms2();
45247
45444
  init_verificationRegistry();
45248
45445
  init_presets();
45446
+ init_useRenderInterpolation();
45249
45447
  SimulationCanvas.displayName = "SimulationCanvas";
45250
45448
  }
45251
45449
  });