@principal-ai/file-city-react 0.5.25 → 0.5.27
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":"FileCity3D.d.ts","sourceRoot":"","sources":["../../../src/components/FileCity3D/FileCity3D.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAA4D,MAAM,OAAO,CAAC;AAMjF,OAAO,KAAK,EACV,QAAQ,EACR,YAAY,EACZ,YAAY,EAEb,MAAM,iCAAiC,CAAC;AAEzC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAIxD,OAAO,QAAQ,OAAO,CAAC;IAErB,UAAU,GAAG,CAAC;QAEZ,UAAU,iBAAkB,SAAQ,aAAa;SAAG;KACrD;CACF;AAGD,YAAY,EAAE,QAAQ,EAAE,YAAY,EAAE,YAAY,EAAE,CAAC;AAGrD,MAAM,WAAW,cAAc;IAC7B,wBAAwB;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,8BAA8B;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,4BAA4B;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,yBAAyB;IACzB,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,0CAA0C;IAC1C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iDAAiD;IACjD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,6BAA6B;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,6BAA6B;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,mBAAmB;IACnB,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;CAC5B;AAED,gDAAgD;AAChD,MAAM,MAAM,aAAa,GACrB,MAAM,GACN,aAAa,GACb,UAAU,GACV,MAAM,CAAC;AAGX,MAAM,WAAW,eAAe;IAC9B,0CAA0C;IAC1C,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,mFAAmF;IACnF,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,2CAA2C;IAC3C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,4CAA4C;IAC5C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gDAAgD;IAChD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wCAAwC;AACxC,MAAM,MAAM,aAAa,GAAG,aAAa,GAAG,QAAQ,CAAC;AAErD,oFAAoF;AACpF,MAAM,WAAW,WAAW;IAC1B,qDAAqD;IACrD,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,qDAAqD;IACrD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,yDAAyD;AACzD,eAAO,MAAM,qBAAqB,EAAE,WAAW,EAS9C,CAAC;
|
|
1
|
+
{"version":3,"file":"FileCity3D.d.ts","sourceRoot":"","sources":["../../../src/components/FileCity3D/FileCity3D.tsx"],"names":[],"mappings":"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"}
|
|
@@ -472,55 +472,37 @@ function InstancedBuildings({ buildings, centerOffset, onHover, onClick, hovered
|
|
|
472
472
|
buildingIndex: d.index,
|
|
473
473
|
})), growProgress: growProgress, minHeight: minHeight, baseOffset: baseOffset, springDuration: springDuration, heightMultipliersRef: heightMultipliersRef })] }));
|
|
474
474
|
}
|
|
475
|
-
function AnimatedIcon({ x, z, targetHeight, iconSize, texture, opacity, growProgress,
|
|
475
|
+
function AnimatedIcon({ x, z, targetHeight, iconSize, texture, opacity, growProgress, }) {
|
|
476
476
|
const meshRef = useRef(null);
|
|
477
|
-
const startTimeRef = useRef(null);
|
|
478
477
|
const materialRef = useRef(null);
|
|
479
|
-
useFrame((
|
|
478
|
+
useFrame(() => {
|
|
480
479
|
if (!meshRef.current)
|
|
481
480
|
return;
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
}
|
|
485
|
-
const currentTime = clock.elapsedTime * 1000;
|
|
486
|
-
const animStartTime = startTimeRef.current ?? currentTime;
|
|
487
|
-
// Calculate per-icon animation progress
|
|
488
|
-
const elapsed = currentTime - animStartTime - staggerDelayMs;
|
|
489
|
-
let animProgress = growProgress;
|
|
490
|
-
if (growProgress > 0 && elapsed >= 0) {
|
|
491
|
-
const t = Math.min(elapsed / springDuration, 1);
|
|
492
|
-
const eased = 1 - Math.pow(1 - t, 3);
|
|
493
|
-
animProgress = eased * growProgress;
|
|
494
|
-
}
|
|
495
|
-
else if (growProgress > 0 && elapsed < 0) {
|
|
496
|
-
animProgress = 0;
|
|
497
|
-
}
|
|
481
|
+
// Icons track the global growProgress directly (no stagger)
|
|
482
|
+
// This keeps them in sync with the building heights
|
|
498
483
|
const minHeight = 0.3;
|
|
499
484
|
const baseOffset = 0.2;
|
|
500
|
-
const height =
|
|
485
|
+
const height = growProgress * targetHeight + minHeight;
|
|
501
486
|
const buildingTop = height + baseOffset;
|
|
502
|
-
// When flat (
|
|
503
|
-
// When grown (
|
|
504
|
-
const flatY = minHeight + baseOffset + 0.
|
|
505
|
-
const grownY = buildingTop + 0.
|
|
506
|
-
const yPosition = flatY + (grownY - flatY) *
|
|
487
|
+
// When flat (growProgress=0): icon lies flat at ground level
|
|
488
|
+
// When grown (growProgress=1): icon lies flat above building roof
|
|
489
|
+
const flatY = minHeight + baseOffset + 0.5;
|
|
490
|
+
const grownY = buildingTop + 0.5;
|
|
491
|
+
const yPosition = flatY + (grownY - flatY) * growProgress;
|
|
507
492
|
meshRef.current.position.y = yPosition;
|
|
508
493
|
// Keep icon flat (facing up) at all times
|
|
509
494
|
meshRef.current.rotation.x = -Math.PI / 2;
|
|
510
495
|
if (materialRef.current) {
|
|
511
|
-
|
|
512
|
-
const minOpacity = 0.8;
|
|
513
|
-
const effectiveOpacity = minOpacity + (1 - minOpacity) * animProgress;
|
|
514
|
-
materialRef.current.opacity = opacity * effectiveOpacity;
|
|
496
|
+
materialRef.current.opacity = opacity;
|
|
515
497
|
}
|
|
516
498
|
});
|
|
517
499
|
return (_jsxs("mesh", { ref: meshRef, position: [x, 0, z], scale: [iconSize, iconSize, 1], raycast: () => null, children: [_jsx("planeGeometry", { args: [1, 1] }), _jsx("meshBasicMaterial", { ref: materialRef, map: texture, transparent: true, opacity: 0.8, depthTest: true, depthWrite: false, side: THREE.DoubleSide })] }));
|
|
518
500
|
}
|
|
519
|
-
function BuildingIcons({ buildings, centerOffset, growProgress, heightScaling, linearScale, flatPatterns, highlightLayers, isolationMode, hasActiveHighlights,
|
|
501
|
+
function BuildingIcons({ buildings, centerOffset, growProgress, heightScaling, linearScale, flatPatterns, highlightLayers, isolationMode, hasActiveHighlights, }) {
|
|
520
502
|
// Pre-compute buildings with icons
|
|
521
503
|
const buildingsWithIcons = useMemo(() => {
|
|
522
504
|
return buildings
|
|
523
|
-
.map((building
|
|
505
|
+
.map((building) => {
|
|
524
506
|
const config = getConfigForFile(building);
|
|
525
507
|
if (!config.icon)
|
|
526
508
|
return null;
|
|
@@ -529,14 +511,13 @@ function BuildingIcons({ buildings, centerOffset, growProgress, heightScaling, l
|
|
|
529
511
|
const shouldDim = hasActiveHighlights && !isHighlighted;
|
|
530
512
|
const shouldHide = shouldDim && isolationMode === 'hide';
|
|
531
513
|
const shouldCollapse = shouldDim && isolationMode === 'collapse';
|
|
532
|
-
|
|
514
|
+
// Hide icons for buildings that are hidden or collapsed
|
|
515
|
+
if (shouldHide || shouldCollapse)
|
|
533
516
|
return null;
|
|
534
517
|
const fullHeight = calculateBuildingHeight(building, heightScaling, linearScale, flatPatterns);
|
|
535
|
-
const targetHeight =
|
|
518
|
+
const targetHeight = fullHeight;
|
|
536
519
|
const x = building.position.x - centerOffset.x;
|
|
537
520
|
const z = building.position.z - centerOffset.z;
|
|
538
|
-
const staggerIndex = staggerIndices[index] ?? index;
|
|
539
|
-
const staggerDelayMs = staggerDelay * staggerIndex;
|
|
540
521
|
return {
|
|
541
522
|
building,
|
|
542
523
|
config,
|
|
@@ -544,7 +525,6 @@ function BuildingIcons({ buildings, centerOffset, growProgress, heightScaling, l
|
|
|
544
525
|
z,
|
|
545
526
|
targetHeight,
|
|
546
527
|
shouldDim,
|
|
547
|
-
staggerDelayMs,
|
|
548
528
|
};
|
|
549
529
|
})
|
|
550
530
|
.filter(Boolean);
|
|
@@ -557,11 +537,9 @@ function BuildingIcons({ buildings, centerOffset, growProgress, heightScaling, l
|
|
|
557
537
|
heightScaling,
|
|
558
538
|
linearScale,
|
|
559
539
|
flatPatterns,
|
|
560
|
-
staggerIndices,
|
|
561
|
-
staggerDelay,
|
|
562
540
|
]);
|
|
563
541
|
// Icons are now always rendered (flat or grown)
|
|
564
|
-
return (_jsx(_Fragment, { children: buildingsWithIcons.map(({ building, config, x, z, targetHeight, shouldDim
|
|
542
|
+
return (_jsx(_Fragment, { children: buildingsWithIcons.map(({ building, config, x, z, targetHeight, shouldDim }) => {
|
|
565
543
|
const icon = config.icon;
|
|
566
544
|
const texture = getIconTexture(icon.name, icon.color || '#ffffff');
|
|
567
545
|
if (!texture)
|
|
@@ -571,7 +549,7 @@ function BuildingIcons({ buildings, centerOffset, growProgress, heightScaling, l
|
|
|
571
549
|
const minDimension = Math.min(width, depth);
|
|
572
550
|
const iconSize = minDimension * (icon.size || 0.6) * 1.7;
|
|
573
551
|
const opacity = shouldDim && isolationMode === 'transparent' ? 0.3 : 1;
|
|
574
|
-
return (_jsx(AnimatedIcon, { x: x, z: z, targetHeight: targetHeight, iconSize: iconSize, texture: texture, opacity: opacity, growProgress: growProgress
|
|
552
|
+
return (_jsx(AnimatedIcon, { x: x, z: z, targetHeight: targetHeight, iconSize: iconSize, texture: texture, opacity: opacity, growProgress: growProgress }, building.path));
|
|
575
553
|
}) }));
|
|
576
554
|
}
|
|
577
555
|
function DistrictFloor({ district, centerOffset, highlightColor, growProgress }) {
|
|
@@ -684,6 +662,7 @@ function AnimatedCamera({ citySize, isFlat, focusTarget, maxBuildingHeight = 0 }
|
|
|
684
662
|
const isOrbitingRef = useRef(false);
|
|
685
663
|
const hasAppliedInitial = useRef(false);
|
|
686
664
|
const frameCount = useRef(0);
|
|
665
|
+
const prevIsFlatRef = useRef(isFlat); // Track previous isFlat to detect actual state changes
|
|
687
666
|
// Calculate camera height to fit city in viewport (for top-down view)
|
|
688
667
|
// Formula: height = citySize / (2 * tan(fov/2) * min(1, aspect))
|
|
689
668
|
// Padding factor adds space around the city to match 2D component
|
|
@@ -785,11 +764,30 @@ function AnimatedCamera({ citySize, isFlat, focusTarget, maxBuildingHeight = 0 }
|
|
|
785
764
|
const orbitParamsRef = useRef(null);
|
|
786
765
|
// Track tilt parameters during vertical rotation
|
|
787
766
|
const tiltParamsRef = useRef(null);
|
|
788
|
-
// When
|
|
767
|
+
// When isFlat changes after initial setup, animate to new position
|
|
768
|
+
// We track isFlat explicitly rather than targetPos to avoid spurious animations
|
|
769
|
+
// from aspect ratio changes or other recalculations
|
|
789
770
|
useEffect(() => {
|
|
790
771
|
// Skip the first render - we handle that directly in useFrame
|
|
791
772
|
if (!hasAppliedInitial.current)
|
|
792
773
|
return;
|
|
774
|
+
// Only animate if isFlat actually changed (flat <-> grown transition)
|
|
775
|
+
const isFlatChanged = prevIsFlatRef.current !== isFlat;
|
|
776
|
+
prevIsFlatRef.current = isFlat;
|
|
777
|
+
if (!isFlatChanged) {
|
|
778
|
+
// isFlat didn't change, just update position directly without animation
|
|
779
|
+
// This handles things like focusTarget changes within the same flat/grown state
|
|
780
|
+
api.set({
|
|
781
|
+
camX: targetPos.x,
|
|
782
|
+
camY: targetPos.y,
|
|
783
|
+
camZ: targetPos.z,
|
|
784
|
+
lookX: targetPos.targetX,
|
|
785
|
+
lookY: targetPos.targetY,
|
|
786
|
+
lookZ: targetPos.targetZ,
|
|
787
|
+
});
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
// isFlat changed - animate the transition
|
|
793
791
|
api.start({
|
|
794
792
|
camX: targetPos.x,
|
|
795
793
|
camY: targetPos.y,
|
|
@@ -801,7 +799,7 @@ function AnimatedCamera({ citySize, isFlat, focusTarget, maxBuildingHeight = 0 }
|
|
|
801
799
|
isAnimatingRef.current = false;
|
|
802
800
|
},
|
|
803
801
|
});
|
|
804
|
-
}, [targetPos, api]);
|
|
802
|
+
}, [targetPos, api, isFlat]);
|
|
805
803
|
// Update camera each frame
|
|
806
804
|
useFrame(() => {
|
|
807
805
|
frameCount.current++;
|
|
@@ -1350,7 +1348,7 @@ function CityScene({ cityData, onBuildingHover, onBuildingClick, hoveredBuilding
|
|
|
1350
1348
|
// Focus color takes priority, then highlight layer color
|
|
1351
1349
|
const districtColor = (isFocused && buildingFocusColor) ? buildingFocusColor : highlightLayerColor;
|
|
1352
1350
|
return (_jsx(DistrictFloor, { district: district, centerOffset: centerOffset, opacity: 1, highlightColor: districtColor, growProgress: growProgress }, district.path));
|
|
1353
|
-
}), _jsx(InstancedBuildings, { buildings: cityData.buildings, centerOffset: centerOffset, onHover: onBuildingHover, onClick: onBuildingClick, hoveredIndex: hoveredIndex, selectedIndex: selectedIndex, growProgress: growProgress, animationConfig: animationConfig, heightScaling: heightScaling, linearScale: linearScale, flatPatterns: flatPatterns, staggerIndices: staggerIndices, focusDirectory: buildingFocusDirectory, highlightLayers: highlightLayers, isolationMode: isolationMode }), _jsx(BuildingIcons, { buildings: cityData.buildings, centerOffset: centerOffset, growProgress: growProgress, heightScaling: heightScaling, linearScale: linearScale, flatPatterns: flatPatterns, highlightLayers: highlightLayers, isolationMode: isolationMode, hasActiveHighlights: activeHighlights
|
|
1351
|
+
}), _jsx(InstancedBuildings, { buildings: cityData.buildings, centerOffset: centerOffset, onHover: onBuildingHover, onClick: onBuildingClick, hoveredIndex: hoveredIndex, selectedIndex: selectedIndex, growProgress: growProgress, animationConfig: animationConfig, heightScaling: heightScaling, linearScale: linearScale, flatPatterns: flatPatterns, staggerIndices: staggerIndices, focusDirectory: buildingFocusDirectory, highlightLayers: highlightLayers, isolationMode: isolationMode }), _jsx(BuildingIcons, { buildings: cityData.buildings, centerOffset: centerOffset, growProgress: growProgress, heightScaling: heightScaling, linearScale: linearScale, flatPatterns: flatPatterns, highlightLayers: highlightLayers, isolationMode: isolationMode, hasActiveHighlights: activeHighlights })] }));
|
|
1354
1352
|
}
|
|
1355
1353
|
/**
|
|
1356
1354
|
* FileCity3D - 3D visualization of codebase structure
|
package/package.json
CHANGED
|
@@ -769,9 +769,6 @@ interface BuildingIconsProps {
|
|
|
769
769
|
highlightLayers: HighlightLayer[];
|
|
770
770
|
isolationMode: IsolationMode;
|
|
771
771
|
hasActiveHighlights: boolean;
|
|
772
|
-
staggerIndices: number[];
|
|
773
|
-
springDuration: number;
|
|
774
|
-
staggerDelay: number;
|
|
775
772
|
}
|
|
776
773
|
|
|
777
774
|
// Individual animated icon component
|
|
@@ -783,8 +780,6 @@ interface AnimatedIconProps {
|
|
|
783
780
|
texture: THREE.Texture;
|
|
784
781
|
opacity: number;
|
|
785
782
|
growProgress: number;
|
|
786
|
-
staggerDelayMs: number;
|
|
787
|
-
springDuration: number;
|
|
788
783
|
}
|
|
789
784
|
|
|
790
785
|
function AnimatedIcon({
|
|
@@ -795,45 +790,25 @@ function AnimatedIcon({
|
|
|
795
790
|
texture,
|
|
796
791
|
opacity,
|
|
797
792
|
growProgress,
|
|
798
|
-
staggerDelayMs,
|
|
799
|
-
springDuration,
|
|
800
793
|
}: AnimatedIconProps) {
|
|
801
794
|
const meshRef = useRef<THREE.Mesh>(null);
|
|
802
|
-
const startTimeRef = useRef<number | null>(null);
|
|
803
795
|
const materialRef = useRef<THREE.MeshBasicMaterial>(null);
|
|
804
796
|
|
|
805
|
-
useFrame((
|
|
797
|
+
useFrame(() => {
|
|
806
798
|
if (!meshRef.current) return;
|
|
807
799
|
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
const currentTime = clock.elapsedTime * 1000;
|
|
813
|
-
const animStartTime = startTimeRef.current ?? currentTime;
|
|
814
|
-
|
|
815
|
-
// Calculate per-icon animation progress
|
|
816
|
-
const elapsed = currentTime - animStartTime - staggerDelayMs;
|
|
817
|
-
let animProgress = growProgress;
|
|
818
|
-
|
|
819
|
-
if (growProgress > 0 && elapsed >= 0) {
|
|
820
|
-
const t = Math.min(elapsed / springDuration, 1);
|
|
821
|
-
const eased = 1 - Math.pow(1 - t, 3);
|
|
822
|
-
animProgress = eased * growProgress;
|
|
823
|
-
} else if (growProgress > 0 && elapsed < 0) {
|
|
824
|
-
animProgress = 0;
|
|
825
|
-
}
|
|
826
|
-
|
|
800
|
+
// Icons track the global growProgress directly (no stagger)
|
|
801
|
+
// This keeps them in sync with the building heights
|
|
827
802
|
const minHeight = 0.3;
|
|
828
803
|
const baseOffset = 0.2;
|
|
829
|
-
const height =
|
|
804
|
+
const height = growProgress * targetHeight + minHeight;
|
|
830
805
|
const buildingTop = height + baseOffset;
|
|
831
806
|
|
|
832
|
-
// When flat (
|
|
833
|
-
// When grown (
|
|
834
|
-
const flatY = minHeight + baseOffset + 0.
|
|
835
|
-
const grownY = buildingTop + 0.
|
|
836
|
-
const yPosition = flatY + (grownY - flatY) *
|
|
807
|
+
// When flat (growProgress=0): icon lies flat at ground level
|
|
808
|
+
// When grown (growProgress=1): icon lies flat above building roof
|
|
809
|
+
const flatY = minHeight + baseOffset + 0.5;
|
|
810
|
+
const grownY = buildingTop + 0.5;
|
|
811
|
+
const yPosition = flatY + (grownY - flatY) * growProgress;
|
|
837
812
|
|
|
838
813
|
meshRef.current.position.y = yPosition;
|
|
839
814
|
|
|
@@ -841,10 +816,7 @@ function AnimatedIcon({
|
|
|
841
816
|
meshRef.current.rotation.x = -Math.PI / 2;
|
|
842
817
|
|
|
843
818
|
if (materialRef.current) {
|
|
844
|
-
|
|
845
|
-
const minOpacity = 0.8;
|
|
846
|
-
const effectiveOpacity = minOpacity + (1 - minOpacity) * animProgress;
|
|
847
|
-
materialRef.current.opacity = opacity * effectiveOpacity;
|
|
819
|
+
materialRef.current.opacity = opacity;
|
|
848
820
|
}
|
|
849
821
|
});
|
|
850
822
|
|
|
@@ -879,14 +851,11 @@ function BuildingIcons({
|
|
|
879
851
|
highlightLayers,
|
|
880
852
|
isolationMode,
|
|
881
853
|
hasActiveHighlights,
|
|
882
|
-
staggerIndices,
|
|
883
|
-
springDuration,
|
|
884
|
-
staggerDelay,
|
|
885
854
|
}: BuildingIconsProps) {
|
|
886
855
|
// Pre-compute buildings with icons
|
|
887
856
|
const buildingsWithIcons = useMemo(() => {
|
|
888
857
|
return buildings
|
|
889
|
-
.map((building
|
|
858
|
+
.map((building) => {
|
|
890
859
|
const config = getConfigForFile(building);
|
|
891
860
|
if (!config.icon) return null;
|
|
892
861
|
|
|
@@ -896,17 +865,15 @@ function BuildingIcons({
|
|
|
896
865
|
const shouldHide = shouldDim && isolationMode === 'hide';
|
|
897
866
|
const shouldCollapse = shouldDim && isolationMode === 'collapse';
|
|
898
867
|
|
|
899
|
-
|
|
868
|
+
// Hide icons for buildings that are hidden or collapsed
|
|
869
|
+
if (shouldHide || shouldCollapse) return null;
|
|
900
870
|
|
|
901
871
|
const fullHeight = calculateBuildingHeight(building, heightScaling, linearScale, flatPatterns);
|
|
902
|
-
const targetHeight =
|
|
872
|
+
const targetHeight = fullHeight;
|
|
903
873
|
|
|
904
874
|
const x = building.position.x - centerOffset.x;
|
|
905
875
|
const z = building.position.z - centerOffset.z;
|
|
906
876
|
|
|
907
|
-
const staggerIndex = staggerIndices[index] ?? index;
|
|
908
|
-
const staggerDelayMs = staggerDelay * staggerIndex;
|
|
909
|
-
|
|
910
877
|
return {
|
|
911
878
|
building,
|
|
912
879
|
config,
|
|
@@ -914,7 +881,6 @@ function BuildingIcons({
|
|
|
914
881
|
z,
|
|
915
882
|
targetHeight,
|
|
916
883
|
shouldDim,
|
|
917
|
-
staggerDelayMs,
|
|
918
884
|
};
|
|
919
885
|
})
|
|
920
886
|
.filter(Boolean) as Array<{
|
|
@@ -924,7 +890,6 @@ function BuildingIcons({
|
|
|
924
890
|
z: number;
|
|
925
891
|
targetHeight: number;
|
|
926
892
|
shouldDim: boolean;
|
|
927
|
-
staggerDelayMs: number;
|
|
928
893
|
}>;
|
|
929
894
|
}, [
|
|
930
895
|
buildings,
|
|
@@ -935,15 +900,13 @@ function BuildingIcons({
|
|
|
935
900
|
heightScaling,
|
|
936
901
|
linearScale,
|
|
937
902
|
flatPatterns,
|
|
938
|
-
staggerIndices,
|
|
939
|
-
staggerDelay,
|
|
940
903
|
]);
|
|
941
904
|
|
|
942
905
|
// Icons are now always rendered (flat or grown)
|
|
943
906
|
return (
|
|
944
907
|
<>
|
|
945
908
|
{buildingsWithIcons.map(
|
|
946
|
-
({ building, config, x, z, targetHeight, shouldDim
|
|
909
|
+
({ building, config, x, z, targetHeight, shouldDim }) => {
|
|
947
910
|
const icon = config.icon!;
|
|
948
911
|
const texture = getIconTexture(icon.name, icon.color || '#ffffff');
|
|
949
912
|
if (!texture) return null;
|
|
@@ -965,8 +928,6 @@ function BuildingIcons({
|
|
|
965
928
|
texture={texture}
|
|
966
929
|
opacity={opacity}
|
|
967
930
|
growProgress={growProgress}
|
|
968
|
-
staggerDelayMs={staggerDelayMs}
|
|
969
|
-
springDuration={springDuration}
|
|
970
931
|
/>
|
|
971
932
|
);
|
|
972
933
|
},
|
|
@@ -1185,6 +1146,7 @@ function AnimatedCamera({ citySize, isFlat, focusTarget, maxBuildingHeight = 0 }
|
|
|
1185
1146
|
const isOrbitingRef = useRef(false);
|
|
1186
1147
|
const hasAppliedInitial = useRef(false);
|
|
1187
1148
|
const frameCount = useRef(0);
|
|
1149
|
+
const prevIsFlatRef = useRef(isFlat); // Track previous isFlat to detect actual state changes
|
|
1188
1150
|
|
|
1189
1151
|
// Calculate camera height to fit city in viewport (for top-down view)
|
|
1190
1152
|
// Formula: height = citySize / (2 * tan(fov/2) * min(1, aspect))
|
|
@@ -1307,11 +1269,32 @@ function AnimatedCamera({ citySize, isFlat, focusTarget, maxBuildingHeight = 0 }
|
|
|
1307
1269
|
azimuthAngle: number; // horizontal angle to maintain
|
|
1308
1270
|
} | null>(null);
|
|
1309
1271
|
|
|
1310
|
-
// When
|
|
1272
|
+
// When isFlat changes after initial setup, animate to new position
|
|
1273
|
+
// We track isFlat explicitly rather than targetPos to avoid spurious animations
|
|
1274
|
+
// from aspect ratio changes or other recalculations
|
|
1311
1275
|
useEffect(() => {
|
|
1312
1276
|
// Skip the first render - we handle that directly in useFrame
|
|
1313
1277
|
if (!hasAppliedInitial.current) return;
|
|
1314
1278
|
|
|
1279
|
+
// Only animate if isFlat actually changed (flat <-> grown transition)
|
|
1280
|
+
const isFlatChanged = prevIsFlatRef.current !== isFlat;
|
|
1281
|
+
prevIsFlatRef.current = isFlat;
|
|
1282
|
+
|
|
1283
|
+
if (!isFlatChanged) {
|
|
1284
|
+
// isFlat didn't change, just update position directly without animation
|
|
1285
|
+
// This handles things like focusTarget changes within the same flat/grown state
|
|
1286
|
+
api.set({
|
|
1287
|
+
camX: targetPos.x,
|
|
1288
|
+
camY: targetPos.y,
|
|
1289
|
+
camZ: targetPos.z,
|
|
1290
|
+
lookX: targetPos.targetX,
|
|
1291
|
+
lookY: targetPos.targetY,
|
|
1292
|
+
lookZ: targetPos.targetZ,
|
|
1293
|
+
});
|
|
1294
|
+
return;
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
// isFlat changed - animate the transition
|
|
1315
1298
|
api.start({
|
|
1316
1299
|
camX: targetPos.x,
|
|
1317
1300
|
camY: targetPos.y,
|
|
@@ -1323,7 +1306,7 @@ function AnimatedCamera({ citySize, isFlat, focusTarget, maxBuildingHeight = 0 }
|
|
|
1323
1306
|
isAnimatingRef.current = false;
|
|
1324
1307
|
},
|
|
1325
1308
|
});
|
|
1326
|
-
}, [targetPos, api]);
|
|
1309
|
+
}, [targetPos, api, isFlat]);
|
|
1327
1310
|
|
|
1328
1311
|
// Update camera each frame
|
|
1329
1312
|
useFrame(() => {
|
|
@@ -2135,9 +2118,6 @@ function CityScene({
|
|
|
2135
2118
|
highlightLayers={highlightLayers}
|
|
2136
2119
|
isolationMode={isolationMode}
|
|
2137
2120
|
hasActiveHighlights={activeHighlights}
|
|
2138
|
-
staggerIndices={staggerIndices}
|
|
2139
|
-
springDuration={springDuration}
|
|
2140
|
-
staggerDelay={animationConfig.staggerDelay || 15}
|
|
2141
2121
|
/>
|
|
2142
2122
|
</>
|
|
2143
2123
|
);
|