@buley/hexgrid-3d 3.5.0 → 3.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HexGrid.d.ts","sourceRoot":"","sources":["../../src/components/HexGrid.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAA;AAYhF,OAAO,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,UAAU,CAAA;AAI/C,YAAY,EAAE,KAAK,EAAE,CAAA;AAWrB,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,OAAO;IAEvC,KAAK,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAA;IACrB,MAAM,CAAC,EAAE,KAAK,EAAE,CAAA;IAGhB,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,KAAK,IAAI,CAAA;IACzC,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;IAEnC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAA;IAC9C,mBAAmB,CAAC,EAAE,CAAC,WAAW,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,KAAK,IAAI,CAAA;IAC5G,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,0BAA0B,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACpD,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,KAAK,CAAA;IACZ,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC9B,aAAa,EAAE,MAAM,CAAA;IACrB,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;IAC1C,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;CACf;AAoLD,eAAO,MAAM,OAAO,GAAI,CAAC,GAAG,OAAO,EAAE,iMAalC,YAAY,CAAC,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"HexGrid.d.ts","sourceRoot":"","sources":["../../src/components/HexGrid.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAA;AAYhF,OAAO,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,UAAU,CAAA;AAI/C,YAAY,EAAE,KAAK,EAAE,CAAA;AAWrB,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,OAAO;IAEvC,KAAK,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAA;IACrB,MAAM,CAAC,EAAE,KAAK,EAAE,CAAA;IAGhB,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,KAAK,IAAI,CAAA;IACzC,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;IAEnC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAA;IAC9C,mBAAmB,CAAC,EAAE,CAAC,WAAW,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,KAAK,IAAI,CAAA;IAC5G,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,0BAA0B,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACpD,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,KAAK,CAAA;IACZ,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC9B,aAAa,EAAE,MAAM,CAAA;IACrB,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;IAC1C,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;CACf;AAoLD,eAAO,MAAM,OAAO,GAAI,CAAC,GAAG,OAAO,EAAE,iMAalC,YAAY,CAAC,CAAC,CAAC,4CA4xIjB,CAAA;AAk7DD,eAAe,OAAO,CAAC"}
|
|
@@ -152,6 +152,7 @@ export const HexGrid = ({ items, photos: photosProp, onItemClick, onHexClick, sp
|
|
|
152
152
|
// Stream control refs to coordinate long-running UI streaming from worker
|
|
153
153
|
const streamTokenRef = useRef(0);
|
|
154
154
|
const streamActiveRef = useRef(false);
|
|
155
|
+
const streamTouchesOccupancyRef = useRef(false);
|
|
155
156
|
// Reactive state for telemetry overlay (mirrors streamActiveRef)
|
|
156
157
|
const [streamingActive, setStreamingActive] = useState(false);
|
|
157
158
|
// How many tiles remain to stream in the current streaming run
|
|
@@ -1253,6 +1254,8 @@ export const HexGrid = ({ items, photos: photosProp, onItemClick, onHexClick, sp
|
|
|
1253
1254
|
dlog('HexGrid: Grid dimensions changed, reinitializing infections', { old: prevGridKeyRef.current, new: gridKey });
|
|
1254
1255
|
const isSpherical = gridMetadataRef.current?.isSpherical ?? false;
|
|
1255
1256
|
const initState = initializeInfectionSystem(hexPositions, photos, effectiveHexRadius, workerDebugRef.current?.spawnClusterMax ?? 8, logger, isSpherical);
|
|
1257
|
+
blankNeighborCountGenerationRef.current = -1;
|
|
1258
|
+
blankNeighborCountRef.current = new Map();
|
|
1256
1259
|
setInfectionState(initState);
|
|
1257
1260
|
infectionStateRef.current = initState;
|
|
1258
1261
|
// Post to worker immediately (use sendEvolve helper so generation is logged)
|
|
@@ -1857,6 +1860,16 @@ export const HexGrid = ({ items, photos: photosProp, onItemClick, onHexClick, sp
|
|
|
1857
1860
|
const tileAlphaRef = useRef(new Map());
|
|
1858
1861
|
// Per-tile arrival pulse timestamps (ms) to show a brief highlight when a tile appears/updates
|
|
1859
1862
|
const tilePulseRef = useRef(new Map());
|
|
1863
|
+
// Worker-precomputed neighbor blank counts (index -> blank-neighbor count)
|
|
1864
|
+
const blankNeighborCountRef = useRef(new Map());
|
|
1865
|
+
const blankNeighborCountGenerationRef = useRef(-1);
|
|
1866
|
+
// Fallback cache for local blank-neighbor computation when worker data is unavailable/mismatched
|
|
1867
|
+
const localBlankNeighborCacheRef = useRef({
|
|
1868
|
+
infectionMap: null,
|
|
1869
|
+
positions: null,
|
|
1870
|
+
hexRadius: -1,
|
|
1871
|
+
counts: new Map()
|
|
1872
|
+
});
|
|
1860
1873
|
// Runtime telemetry: frame timing buffer for FPS/ms overlay
|
|
1861
1874
|
const frameTimesRef = useRef([]);
|
|
1862
1875
|
const lastFrameTimeRef = useRef(performance.now());
|
|
@@ -2064,8 +2077,9 @@ export const HexGrid = ({ items, photos: photosProp, onItemClick, onHexClick, sp
|
|
|
2064
2077
|
const lastDrawTimeRef = useRef(0);
|
|
2065
2078
|
const lastDrawGenRef = useRef(0);
|
|
2066
2079
|
const cameraDirtyRef = useRef(true);
|
|
2067
|
-
//
|
|
2068
|
-
const
|
|
2080
|
+
// Draw cadence: 30fps when actively animating, lower cadence while static.
|
|
2081
|
+
const activeFrameMs = 1000 / 30;
|
|
2082
|
+
const idleFrameMs = 250;
|
|
2069
2083
|
// Helper to get effective batchPerFrame (transient override if present)
|
|
2070
2084
|
const getEffectiveBatch = () => {
|
|
2071
2085
|
const base = Math.max(0, Math.floor(workerDebugRef.current?.batchPerFrame ?? 0));
|
|
@@ -2249,6 +2263,8 @@ export const HexGrid = ({ items, photos: photosProp, onItemClick, onHexClick, sp
|
|
|
2249
2263
|
lastEvolutionTime: infectionStateRef.current.lastEvolutionTime,
|
|
2250
2264
|
generation: infectionStateRef.current.generation
|
|
2251
2265
|
};
|
|
2266
|
+
blankNeighborCountGenerationRef.current = -1;
|
|
2267
|
+
blankNeighborCountRef.current = new Map();
|
|
2252
2268
|
setInfectionState(filteredState);
|
|
2253
2269
|
infectionStateRef.current = filteredState;
|
|
2254
2270
|
if (workerRef.current) {
|
|
@@ -2276,6 +2292,8 @@ export const HexGrid = ({ items, photos: photosProp, onItemClick, onHexClick, sp
|
|
|
2276
2292
|
// Use the live spawnClusterMax from workerDebug when available so photo reloads honor debug tuning
|
|
2277
2293
|
const isSpherical = gridMetadataRef.current?.isSpherical ?? false;
|
|
2278
2294
|
const initState = initializeInfectionSystem(hexPositions, photos, effectiveHexRadius, workerDebugRef.current?.spawnClusterMax ?? 8, logger, isSpherical);
|
|
2295
|
+
blankNeighborCountGenerationRef.current = -1;
|
|
2296
|
+
blankNeighborCountRef.current = new Map();
|
|
2279
2297
|
setInfectionState(initState);
|
|
2280
2298
|
infectionStateRef.current = initState;
|
|
2281
2299
|
if (workerRef.current) {
|
|
@@ -2407,6 +2425,20 @@ export const HexGrid = ({ items, photos: photosProp, onItemClick, onHexClick, sp
|
|
|
2407
2425
|
dlog('Processing evolved state with', data.infections.length, 'gossip entries');
|
|
2408
2426
|
// Convert back from array to Map
|
|
2409
2427
|
const newInfectionsMap = new Map(data.infections);
|
|
2428
|
+
if (Array.isArray(data.blankNeighborCounts)) {
|
|
2429
|
+
try {
|
|
2430
|
+
blankNeighborCountRef.current = new Map(data.blankNeighborCounts);
|
|
2431
|
+
blankNeighborCountGenerationRef.current = typeof data.generation === 'number' ? data.generation : -1;
|
|
2432
|
+
}
|
|
2433
|
+
catch (err) {
|
|
2434
|
+
// ignore malformed worker payload
|
|
2435
|
+
}
|
|
2436
|
+
}
|
|
2437
|
+
else {
|
|
2438
|
+
// Fallback to local computation when worker payload doesn't include counts.
|
|
2439
|
+
blankNeighborCountGenerationRef.current = -1;
|
|
2440
|
+
blankNeighborCountRef.current = new Map();
|
|
2441
|
+
}
|
|
2410
2442
|
// Capture tile centers for debug visualization if available
|
|
2411
2443
|
if (data.tileCenters && Array.isArray(data.tileCenters)) {
|
|
2412
2444
|
try {
|
|
@@ -2476,6 +2508,7 @@ export const HexGrid = ({ items, photos: photosProp, onItemClick, onHexClick, sp
|
|
|
2476
2508
|
});
|
|
2477
2509
|
// If no changes, just replace state
|
|
2478
2510
|
if (changes.length === 0) {
|
|
2511
|
+
streamTouchesOccupancyRef.current = false;
|
|
2479
2512
|
setInfectionState({
|
|
2480
2513
|
infections: newInfectionsMap,
|
|
2481
2514
|
availableIndices: data.availableIndices,
|
|
@@ -2493,6 +2526,7 @@ export const HexGrid = ({ items, photos: photosProp, onItemClick, onHexClick, sp
|
|
|
2493
2526
|
// Start streaming changes; cancel any previous streamer.
|
|
2494
2527
|
streamTokenRef.current += 1;
|
|
2495
2528
|
const token = streamTokenRef.current;
|
|
2529
|
+
streamTouchesOccupancyRef.current = changes.some((ch) => ch.type !== 'update');
|
|
2496
2530
|
// Mark streaming active so other code (animation loop) can avoid posting new evolves
|
|
2497
2531
|
streamActiveRef.current = true;
|
|
2498
2532
|
try {
|
|
@@ -2519,6 +2553,7 @@ export const HexGrid = ({ items, photos: photosProp, onItemClick, onHexClick, sp
|
|
|
2519
2553
|
// stop if cancelled
|
|
2520
2554
|
if (streamTokenRef.current !== token) {
|
|
2521
2555
|
streamActiveRef.current = false;
|
|
2556
|
+
streamTouchesOccupancyRef.current = false;
|
|
2522
2557
|
try {
|
|
2523
2558
|
setStreamingActive(false);
|
|
2524
2559
|
}
|
|
@@ -2566,6 +2601,7 @@ export const HexGrid = ({ items, photos: photosProp, onItemClick, onHexClick, sp
|
|
|
2566
2601
|
}
|
|
2567
2602
|
else {
|
|
2568
2603
|
streamActiveRef.current = false;
|
|
2604
|
+
streamTouchesOccupancyRef.current = false;
|
|
2569
2605
|
try {
|
|
2570
2606
|
setStreamingActive(false);
|
|
2571
2607
|
}
|
|
@@ -2597,6 +2633,10 @@ export const HexGrid = ({ items, photos: photosProp, onItemClick, onHexClick, sp
|
|
|
2597
2633
|
streamActiveRef.current = false;
|
|
2598
2634
|
}
|
|
2599
2635
|
catch (e) { }
|
|
2636
|
+
try {
|
|
2637
|
+
streamTouchesOccupancyRef.current = false;
|
|
2638
|
+
}
|
|
2639
|
+
catch (e) { }
|
|
2600
2640
|
try {
|
|
2601
2641
|
setStreamingActive(false);
|
|
2602
2642
|
}
|
|
@@ -2656,6 +2696,7 @@ export const HexGrid = ({ items, photos: photosProp, onItemClick, onHexClick, sp
|
|
|
2656
2696
|
}
|
|
2657
2697
|
finally {
|
|
2658
2698
|
streamActiveRef.current = false;
|
|
2699
|
+
streamTouchesOccupancyRef.current = false;
|
|
2659
2700
|
try {
|
|
2660
2701
|
setStreamingActive(false);
|
|
2661
2702
|
}
|
|
@@ -2686,25 +2727,54 @@ export const HexGrid = ({ items, photos: photosProp, onItemClick, onHexClick, sp
|
|
|
2686
2727
|
// Clear canvas
|
|
2687
2728
|
ctx.fillStyle = '#001122';
|
|
2688
2729
|
ctx.fillRect(0, 0, screenWidth, screenHeight);
|
|
2689
|
-
|
|
2690
|
-
const
|
|
2691
|
-
const
|
|
2692
|
-
const
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2730
|
+
const infections = infectionState.infections;
|
|
2731
|
+
const dbg = workerDebugRef.current;
|
|
2732
|
+
const projectedPositions = projectedPositionsRef.current;
|
|
2733
|
+
const pulseMap = tilePulseRef.current;
|
|
2734
|
+
const prevAlphaMap = tileAlphaRef.current;
|
|
2735
|
+
const smooth = Math.max(0, Math.min(1, dbg?.translucencySmoothing ?? 0.08));
|
|
2736
|
+
const scratchEnabled = !!dbg?.scratchEnabled;
|
|
2737
|
+
const sheenIntensity = dbg?.sheenIntensity ?? 0.12;
|
|
2738
|
+
const sheenEnabled = !!dbg?.sheenEnabled;
|
|
2739
|
+
const seamInset = dbg?.clusterUvInset ?? 0.0;
|
|
2740
|
+
const isSpherical = gridMetadataRef.current?.isSpherical ?? false;
|
|
2741
|
+
const computeLocalBlankNeighborCounts = () => {
|
|
2742
|
+
const cache = localBlankNeighborCacheRef.current;
|
|
2743
|
+
if (cache.infectionMap === infections &&
|
|
2744
|
+
cache.positions === hexPositions &&
|
|
2745
|
+
cache.hexRadius === drawnHexRadius) {
|
|
2746
|
+
return cache.counts;
|
|
2747
|
+
}
|
|
2748
|
+
const infectedIndices = Array.from(infections.keys());
|
|
2749
|
+
const infectedSet = new Set(infectedIndices);
|
|
2750
|
+
const computed = new Map();
|
|
2751
|
+
for (const idx of infectedIndices) {
|
|
2752
|
+
if (idx < 0 || idx >= hexPositions.length)
|
|
2753
|
+
continue;
|
|
2754
|
+
const neighbors = getNeighbors(idx, hexPositions, drawnHexRadius, isSpherical);
|
|
2755
|
+
let count = 0;
|
|
2756
|
+
for (const n of neighbors) {
|
|
2757
|
+
if (!infectedSet.has(n))
|
|
2758
|
+
count++;
|
|
2759
|
+
}
|
|
2760
|
+
computed.set(idx, count);
|
|
2703
2761
|
}
|
|
2704
|
-
|
|
2705
|
-
|
|
2762
|
+
localBlankNeighborCacheRef.current = {
|
|
2763
|
+
infectionMap: infections,
|
|
2764
|
+
positions: hexPositions,
|
|
2765
|
+
hexRadius: drawnHexRadius,
|
|
2766
|
+
counts: computed
|
|
2767
|
+
};
|
|
2768
|
+
return computed;
|
|
2769
|
+
};
|
|
2770
|
+
const canUseWorkerBlankCounts = blankNeighborCountRef.current.size > 0 &&
|
|
2771
|
+
blankNeighborCountGenerationRef.current === infectionState.generation &&
|
|
2772
|
+
!(streamActiveRef.current && streamTouchesOccupancyRef.current);
|
|
2773
|
+
const blankNeighborCount = canUseWorkerBlankCounts
|
|
2774
|
+
? blankNeighborCountRef.current
|
|
2775
|
+
: computeLocalBlankNeighborCounts();
|
|
2706
2776
|
// compute sheen progress using configured speed
|
|
2707
|
-
const sheenSpeed = Math.max(0.1,
|
|
2777
|
+
const sheenSpeed = Math.max(0.1, dbg?.sheenSpeed || 10);
|
|
2708
2778
|
const now = performance.now();
|
|
2709
2779
|
const sheenProgress = ((now / 1000) % sheenSpeed) / sheenSpeed;
|
|
2710
2780
|
// Draw hexagons (with alpha smoothing)
|
|
@@ -2742,39 +2812,30 @@ export const HexGrid = ({ items, photos: photosProp, onItemClick, onHexClick, sp
|
|
|
2742
2812
|
}
|
|
2743
2813
|
for (let index = 0; index < hexPositions.length; index++) {
|
|
2744
2814
|
const position = hexPositions[index];
|
|
2745
|
-
|
|
2746
|
-
if (index < 0 || index >= hexPositions.length)
|
|
2747
|
-
continue;
|
|
2748
|
-
const infection = infectionState.infections.get(index);
|
|
2749
|
-
// CRITICAL GUARD: Skip drawing if this is an uninfected cell with no valid position data
|
|
2750
|
-
// This prevents "ghost" hexagons from appearing in the background
|
|
2751
|
-
if (!infection && !position)
|
|
2815
|
+
if (!position)
|
|
2752
2816
|
continue;
|
|
2817
|
+
const infection = infections.get(index);
|
|
2753
2818
|
const blankCount = infection ? (blankNeighborCount.get(index) || 0) : 0;
|
|
2754
2819
|
// target alpha based on blank count
|
|
2755
2820
|
const targetAlpha = infection ? (1.0 - Math.min(blankCount, 6) * 0.066) : 1.0;
|
|
2756
|
-
const prevAlphaMap = tileAlphaRef.current;
|
|
2757
2821
|
const prev = prevAlphaMap.get(index) ?? targetAlpha;
|
|
2758
|
-
const smooth = Math.max(0, Math.min(1, workerDebugRef.current?.translucencySmoothing ?? 0.08));
|
|
2759
2822
|
const smoothed = prev + (targetAlpha - prev) * smooth;
|
|
2760
2823
|
prevAlphaMap.set(index, smoothed);
|
|
2761
|
-
const scratchEnabled = !!workerDebugRef.current?.scratchEnabled;
|
|
2762
|
-
const sheenIntensity = workerDebugRef.current?.sheenIntensity ?? 0.12;
|
|
2763
2824
|
// Compute pulse progress for this tile
|
|
2764
|
-
const pulseInfo =
|
|
2825
|
+
const pulseInfo = pulseMap.get(index);
|
|
2765
2826
|
let pulseProgress = 0;
|
|
2766
2827
|
if (pulseInfo) {
|
|
2767
|
-
const elapsed =
|
|
2828
|
+
const elapsed = now - pulseInfo.start;
|
|
2768
2829
|
pulseProgress = Math.max(0, Math.min(1, elapsed / pulseInfo.duration));
|
|
2769
2830
|
if (pulseProgress >= 1)
|
|
2770
|
-
|
|
2831
|
+
pulseMap.delete(index);
|
|
2771
2832
|
}
|
|
2772
2833
|
// Use projected position and scale for drawing
|
|
2773
|
-
const proj =
|
|
2774
|
-
const projX = proj[0];
|
|
2775
|
-
const projY = proj[1];
|
|
2776
|
-
const projScale = proj[2];
|
|
2777
|
-
const projAngle = proj[3] || 0;
|
|
2834
|
+
const proj = projectedPositions[index];
|
|
2835
|
+
const projX = proj ? proj[0] : position[0];
|
|
2836
|
+
const projY = proj ? proj[1] : position[1];
|
|
2837
|
+
const projScale = proj ? proj[2] : 1;
|
|
2838
|
+
const projAngle = proj ? (proj[3] || 0) : 0;
|
|
2778
2839
|
// proj[4] is z-depth (not needed for drawing, only for click detection)
|
|
2779
2840
|
// Selective culling: when low-res mode is active, always draw infected tiles but
|
|
2780
2841
|
// sample uninfected/background tiles to reduce draw count while preserving layout.
|
|
@@ -2789,17 +2850,15 @@ export const HexGrid = ({ items, photos: photosProp, onItemClick, onHexClick, sp
|
|
|
2789
2850
|
}
|
|
2790
2851
|
}
|
|
2791
2852
|
// Draw primary - use drawnHexRadius to account for spacing
|
|
2792
|
-
|
|
2793
|
-
drawHexagon(ctx, [projX, projY, 0], drawnHexRadius * projScale, infection, textures, index, blankCount, smoothed, sheenProgress, sheenIntensity, sheenEnabled, scratchEnabled, scratchCanvasRef.current, workerDebugRef.current?.clusterUvInset ?? 0.0, pulseProgress, false, false, projAngle);
|
|
2853
|
+
drawHexagon(ctx, [projX, projY, 0], drawnHexRadius * projScale, infection, textures, index, blankCount, smoothed, sheenProgress, sheenIntensity, sheenEnabled, scratchEnabled, scratchCanvasRef.current, seamInset, pulseProgress, false, false, projAngle);
|
|
2794
2854
|
// Optionally draw antipodal copy (opposite side of sphere).
|
|
2795
2855
|
// Use the same projection helper as hit-tests so angles/positions match exactly.
|
|
2796
|
-
if (
|
|
2856
|
+
if (dbg?.renderBothSides) {
|
|
2797
2857
|
try {
|
|
2798
2858
|
const anti = mapAndProject(hexPositions[index], true);
|
|
2799
2859
|
const antiScale = anti.scale || 1;
|
|
2800
2860
|
const antiAngle = anti.angle || 0;
|
|
2801
|
-
|
|
2802
|
-
drawHexagon(ctx, [anti.x, anti.y, 0], drawnHexRadius * antiScale, infection, textures, index, blankCount, smoothed, sheenProgress, sheenIntensity, sheenEnabledAnti, scratchEnabled, scratchCanvasRef.current, workerDebugRef.current?.clusterUvInset ?? 0.0, pulseProgress, false, true, antiAngle);
|
|
2861
|
+
drawHexagon(ctx, [anti.x, anti.y, 0], drawnHexRadius * antiScale, infection, textures, index, blankCount, smoothed, sheenProgress, sheenIntensity, sheenEnabled, scratchEnabled, scratchCanvasRef.current, seamInset, pulseProgress, false, true, antiAngle);
|
|
2803
2862
|
}
|
|
2804
2863
|
catch (err) {
|
|
2805
2864
|
// Skip drawing antipodal hex if projection fails
|
|
@@ -2815,10 +2874,11 @@ export const HexGrid = ({ items, photos: photosProp, onItemClick, onHexClick, sp
|
|
|
2815
2874
|
try {
|
|
2816
2875
|
if (workerDebugRef.current?.debugLogs) {
|
|
2817
2876
|
// Sample a few random infected hexes to check
|
|
2877
|
+
const infectedIndices = Array.from(infections.keys());
|
|
2818
2878
|
const sampleSize = Math.min(5, infectedIndices.length);
|
|
2819
2879
|
for (let s = 0; s < sampleSize; s++) {
|
|
2820
2880
|
const idx = infectedIndices[Math.floor(Math.random() * infectedIndices.length)];
|
|
2821
|
-
const infection =
|
|
2881
|
+
const infection = infections.get(idx);
|
|
2822
2882
|
if (!infection)
|
|
2823
2883
|
continue;
|
|
2824
2884
|
const texture = textures.get(infection.photo.id);
|
|
@@ -2828,7 +2888,7 @@ export const HexGrid = ({ items, photos: photosProp, onItemClick, onHexClick, sp
|
|
|
2828
2888
|
const isSpherical = gridMetadataRef.current?.isSpherical ?? false;
|
|
2829
2889
|
const neighbors = getNeighbors(idx, hexPositions, drawnHexRadius, isSpherical);
|
|
2830
2890
|
for (const nIdx of neighbors) {
|
|
2831
|
-
const nInf =
|
|
2891
|
+
const nInf = infections.get(nIdx);
|
|
2832
2892
|
// Only check neighbors with the same photo (adjacent tiles of same image)
|
|
2833
2893
|
if (!nInf || nInf.photo.id !== infection.photo.id)
|
|
2834
2894
|
continue;
|
|
@@ -3015,7 +3075,9 @@ export const HexGrid = ({ items, photos: photosProp, onItemClick, onHexClick, sp
|
|
|
3015
3075
|
// If camera changed recently or infections changed (generation advanced), draw immediately
|
|
3016
3076
|
const gen = infectionState.generation;
|
|
3017
3077
|
const genChanged = gen !== lastDrawGenRef.current;
|
|
3018
|
-
|
|
3078
|
+
const hasVisualAnimation = !!workerDebugRef.current?.sheenEnabled || tilePulseRef.current.size > 0 || streamActiveRef.current;
|
|
3079
|
+
const targetFrameMs = hasVisualAnimation ? activeFrameMs : idleFrameMs;
|
|
3080
|
+
if (cameraDirtyRef.current || genChanged || timeSinceLast >= targetFrameMs) {
|
|
3019
3081
|
draw();
|
|
3020
3082
|
lastDrawTimeRef.current = now;
|
|
3021
3083
|
lastDrawGenRef.current = gen;
|
|
@@ -3112,6 +3174,8 @@ export const HexGrid = ({ items, photos: photosProp, onItemClick, onHexClick, sp
|
|
|
3112
3174
|
logger.error('spawnClusterAt: Invalid center index', centerIndex);
|
|
3113
3175
|
return;
|
|
3114
3176
|
}
|
|
3177
|
+
blankNeighborCountGenerationRef.current = -1;
|
|
3178
|
+
blankNeighborCountRef.current = new Map();
|
|
3115
3179
|
setInfectionState(prevState => {
|
|
3116
3180
|
const newInfections = new Map(prevState.infections);
|
|
3117
3181
|
const newAvailableIndices = [...prevState.availableIndices];
|
|
@@ -153,6 +153,24 @@ function getNeighborsCached(index, positions, hexRadius) {
|
|
|
153
153
|
function calculateUvBoundsFromGridPosition(gridCol, gridRow, tilesX, tilesY) {
|
|
154
154
|
return _calculateUvBoundsFromGridPosition(gridCol, gridRow, tilesX, tilesY);
|
|
155
155
|
}
|
|
156
|
+
function buildBlankNeighborCounts(infections, positions, hexRadius) {
|
|
157
|
+
if (!infections || infections.size === 0)
|
|
158
|
+
return [];
|
|
159
|
+
const infectedSet = new Set(infections.keys());
|
|
160
|
+
const out = [];
|
|
161
|
+
for (const idx of infectedSet) {
|
|
162
|
+
if (idx < 0 || idx >= positions.length)
|
|
163
|
+
continue;
|
|
164
|
+
const neighbors = getNeighborsCached(idx, positions, hexRadius);
|
|
165
|
+
let blankCount = 0;
|
|
166
|
+
for (const n of neighbors) {
|
|
167
|
+
if (!infectedSet.has(n))
|
|
168
|
+
blankCount++;
|
|
169
|
+
}
|
|
170
|
+
out.push([idx, blankCount]);
|
|
171
|
+
}
|
|
172
|
+
return out;
|
|
173
|
+
}
|
|
156
174
|
function findConnectedComponents(indices, positions, hexRadius) {
|
|
157
175
|
// Immediate synchronous check - if this doesn't log, the function isn't being called or is blocked
|
|
158
176
|
const startMarker = performance.now();
|
|
@@ -1854,6 +1872,7 @@ self.onmessage = function (ev) {
|
|
|
1854
1872
|
availableIndices: res.availableIndices,
|
|
1855
1873
|
lastEvolutionTime: res.lastEvolutionTime,
|
|
1856
1874
|
generation: res.generation,
|
|
1875
|
+
blankNeighborCounts: buildBlankNeighborCounts(res.infections, positions, hexRadius),
|
|
1857
1876
|
};
|
|
1858
1877
|
if (res.tileCenters && res.tileCenters.length > 0) {
|
|
1859
1878
|
payload.tileCenters = res.tileCenters;
|