@principal-ai/file-city-react 0.5.17 → 0.5.19

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":"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"}
1
+ {"version":3,"file":"FileCity3D.d.ts","sourceRoot":"","sources":["../../../src/components/FileCity3D/FileCity3D.tsx"],"names":[],"mappings":"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"}
@@ -439,11 +439,11 @@ function InstancedBuildings({ buildings, centerOffset, onHover, onClick, hovered
439
439
  })), growProgress: growProgress, minHeight: minHeight, baseOffset: baseOffset, springDuration: springDuration, heightMultipliersRef: heightMultipliersRef })] }));
440
440
  }
441
441
  function AnimatedIcon({ x, z, targetHeight, iconSize, texture, opacity, growProgress, staggerDelayMs, springDuration, }) {
442
- const spriteRef = useRef(null);
442
+ const meshRef = useRef(null);
443
443
  const startTimeRef = useRef(null);
444
444
  const materialRef = useRef(null);
445
445
  useFrame(({ clock }) => {
446
- if (!spriteRef.current)
446
+ if (!meshRef.current)
447
447
  return;
448
448
  if (startTimeRef.current === null && growProgress > 0) {
449
449
  startTimeRef.current = clock.elapsedTime * 1000;
@@ -465,13 +465,26 @@ function AnimatedIcon({ x, z, targetHeight, iconSize, texture, opacity, growProg
465
465
  const baseOffset = 0.2;
466
466
  const height = animProgress * targetHeight + minHeight;
467
467
  const buildingTop = height + baseOffset;
468
- const yPosition = buildingTop + iconSize / 2 + 2;
469
- spriteRef.current.position.y = yPosition;
468
+ // When flat (animProgress=0): icon lies flat at ground level
469
+ // When grown (animProgress=1): icon floats above building
470
+ const flatY = minHeight + baseOffset + 0.5;
471
+ const grownY = buildingTop + iconSize / 2 + 2;
472
+ const yPosition = flatY + (grownY - flatY) * animProgress;
473
+ meshRef.current.position.y = yPosition;
474
+ // Rotate from flat (facing up) to upright (facing camera-ish)
475
+ // Flat: -Math.PI / 2 (facing up)
476
+ // Grown: 0 (facing forward)
477
+ const flatRotationX = -Math.PI / 2;
478
+ const grownRotationX = 0;
479
+ meshRef.current.rotation.x = flatRotationX + (grownRotationX - flatRotationX) * animProgress;
470
480
  if (materialRef.current) {
471
- materialRef.current.opacity = opacity * animProgress;
481
+ // Show icons even when flat, fade out only slightly
482
+ const minOpacity = 0.8;
483
+ const effectiveOpacity = minOpacity + (1 - minOpacity) * animProgress;
484
+ materialRef.current.opacity = opacity * effectiveOpacity;
472
485
  }
473
486
  });
474
- return (_jsx("sprite", { ref: spriteRef, position: [x, 0, z], scale: [iconSize, iconSize, 1], raycast: () => null, children: _jsx("spriteMaterial", { ref: materialRef, map: texture, transparent: true, opacity: 0, depthTest: true, depthWrite: false }) }));
487
+ 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 })] }));
475
488
  }
476
489
  function BuildingIcons({ buildings, centerOffset, growProgress, heightScaling, linearScale, highlightLayers, isolationMode, hasActiveHighlights, staggerIndices, springDuration, staggerDelay, }) {
477
490
  // Pre-compute buildings with icons
@@ -516,9 +529,7 @@ function BuildingIcons({ buildings, centerOffset, growProgress, heightScaling, l
516
529
  staggerIndices,
517
530
  staggerDelay,
518
531
  ]);
519
- // Don't render if no progress yet
520
- if (growProgress < 0.1)
521
- return null;
532
+ // Icons are now always rendered (flat or grown)
522
533
  return (_jsx(_Fragment, { children: buildingsWithIcons.map(({ building, config, x, z, targetHeight, shouldDim, staggerDelayMs }) => {
523
534
  const icon = config.icon;
524
535
  const texture = getIconTexture(icon.name, icon.color || '#ffffff');
@@ -526,14 +537,14 @@ function BuildingIcons({ buildings, centerOffset, growProgress, heightScaling, l
526
537
  return null;
527
538
  // Icon size based on building dimensions
528
539
  const [width] = building.dimensions;
529
- const baseSize = Math.max(width * 0.8, 6);
540
+ const baseSize = Math.max(width * 1.2, 8);
530
541
  const heightBoost = Math.min(targetHeight / 20, 3);
531
542
  const iconSize = (baseSize + heightBoost) * (icon.size || 1);
532
543
  const opacity = shouldDim && isolationMode === 'transparent' ? 0.3 : 1;
533
544
  return (_jsx(AnimatedIcon, { x: x, z: z, targetHeight: targetHeight, iconSize: iconSize, texture: texture, opacity: opacity, growProgress: growProgress, staggerDelayMs: staggerDelayMs, springDuration: springDuration }, building.path));
534
545
  }) }));
535
546
  }
536
- function DistrictFloor({ district, centerOffset, highlightColor }) {
547
+ function DistrictFloor({ district, centerOffset, highlightColor, growProgress }) {
537
548
  const { worldBounds } = district;
538
549
  const width = worldBounds.maxX - worldBounds.minX;
539
550
  const depth = worldBounds.maxZ - worldBounds.minZ;
@@ -545,7 +556,19 @@ function DistrictFloor({ district, centerOffset, highlightColor }) {
545
556
  const borderColor = highlightColor || '#475569';
546
557
  const lineWidth = highlightColor ? 3 : 1;
547
558
  const labelColor = highlightColor || '#cbd5e1';
548
- return (_jsxs("group", { position: [centerX, 0, centerZ], children: [_jsxs("lineSegments", { rotation: [-Math.PI / 2, 0, 0], position: [0, floorY, 0], renderOrder: -1, children: [_jsx("edgesGeometry", { args: [new THREE.PlaneGeometry(width, depth)], attach: "geometry" }), _jsx("lineBasicMaterial", { color: borderColor, linewidth: lineWidth, depthWrite: false })] }), highlightColor && (_jsxs("mesh", { rotation: [-Math.PI / 2, 0, 0], position: [0, floorY - 0.1, 0], renderOrder: -2, children: [_jsx("planeGeometry", { args: [width, depth] }), _jsx("meshBasicMaterial", { color: highlightColor, transparent: true, opacity: 0.15, depthWrite: false })] })), district.label && (_jsx(Text, { position: [0, 1.5, depth / 2 + 2], rotation: [-Math.PI / 6, 0, 0], fontSize: Math.min(3, width / 6), color: labelColor, anchorX: "center", anchorY: "middle", outlineWidth: 0.1, outlineColor: "#0f172a", children: dirName }))] }));
559
+ // Interpolate text rotation and position based on growProgress
560
+ // Flat: -Math.PI / 2 (facing up), positioned at center of district
561
+ // Grown: -Math.PI / 6 (angled), positioned at edge of district
562
+ const flatRotationX = -Math.PI / 2;
563
+ const grownRotationX = -Math.PI / 6;
564
+ const textRotationX = flatRotationX + (grownRotationX - flatRotationX) * growProgress;
565
+ const flatY = 0.5;
566
+ const grownY = 1.5;
567
+ const textY = flatY + (grownY - flatY) * growProgress;
568
+ const flatZ = 0; // Center of district when flat
569
+ const grownZ = depth / 2 + 2; // Edge of district when grown
570
+ const textZ = flatZ + (grownZ - flatZ) * growProgress;
571
+ return (_jsxs("group", { position: [centerX, 0, centerZ], children: [_jsxs("lineSegments", { rotation: [-Math.PI / 2, 0, 0], position: [0, floorY, 0], renderOrder: -1, children: [_jsx("edgesGeometry", { args: [new THREE.PlaneGeometry(width, depth)], attach: "geometry" }), _jsx("lineBasicMaterial", { color: borderColor, linewidth: lineWidth, depthWrite: false })] }), highlightColor && (_jsxs("mesh", { rotation: [-Math.PI / 2, 0, 0], position: [0, floorY - 0.1, 0], renderOrder: -2, children: [_jsx("planeGeometry", { args: [width, depth] }), _jsx("meshBasicMaterial", { color: highlightColor, transparent: true, opacity: 0.15, depthWrite: false })] })), district.label && (_jsx(Text, { position: [0, textY, textZ], rotation: [textRotationX, 0, 0], fontSize: Math.min(3, width / 6), color: labelColor, anchorX: "center", anchorY: "middle", outlineWidth: 0.1, outlineColor: "#0f172a", children: dirName }))] }));
549
572
  }
550
573
  let cameraApi = null;
551
574
  export function resetCamera() {
@@ -632,8 +655,21 @@ function AnimatedCamera({ citySize, isFlat, focusTarget, maxBuildingHeight = 0 }
632
655
  const hasAppliedInitial = useRef(false);
633
656
  const frameCount = useRef(0);
634
657
  // Compute target camera position
635
- // When flat, use a more top-down view; when grown, use an angled view
658
+ // When flat, always use top-down view (ignore focusTarget)
659
+ // When grown, use focusTarget if available, otherwise angled overview
636
660
  const targetPos = useMemo(() => {
661
+ // Flat state: always top-down, ignore any focus
662
+ if (isFlat) {
663
+ return {
664
+ x: 0,
665
+ y: citySize * 2.0,
666
+ z: 0.001, // Near-zero for top-down (tiny offset to avoid gimbal lock)
667
+ targetX: 0,
668
+ targetY: 0,
669
+ targetZ: 0,
670
+ };
671
+ }
672
+ // Grown state: use focusTarget if available
637
673
  if (focusTarget) {
638
674
  const distance = Math.max(focusTarget.size * 2, 50);
639
675
  const height = Math.max(focusTarget.size * 1.5, 40);
@@ -646,19 +682,15 @@ function AnimatedCamera({ citySize, isFlat, focusTarget, maxBuildingHeight = 0 }
646
682
  targetZ: focusTarget.z,
647
683
  };
648
684
  }
649
- // Default overview - adjust angle based on flat/grown state
650
- // Flat: directly overhead (90 degrees, looking straight down)
651
- // Grown: angled view to see building heights (optionally based on max building height)
685
+ // Grown state without focus: angled overview
652
686
  const baseHeight = citySize * 1.1;
653
687
  const buildingAwareHeight = maxBuildingHeight > 0
654
688
  ? Math.max(baseHeight, maxBuildingHeight * 2.5)
655
689
  : baseHeight;
656
- const targetHeight = isFlat ? citySize * 2.0 : buildingAwareHeight;
657
- const targetZ = isFlat ? 0.001 : citySize * 1.3; // Near-zero for top-down (tiny offset to avoid gimbal lock)
658
690
  return {
659
691
  x: 0,
660
- y: targetHeight,
661
- z: targetZ,
692
+ y: buildingAwareHeight,
693
+ z: citySize * 1.3,
662
694
  targetX: 0,
663
695
  targetY: 0,
664
696
  targetZ: 0,
@@ -1297,7 +1329,7 @@ function CityScene({ cityData, onBuildingHover, onBuildingClick, hoveredBuilding
1297
1329
  // Use buildingFocusColor (synced with animation) instead of focusColor prop
1298
1330
  // Focus color takes priority, then highlight layer color
1299
1331
  const districtColor = (isFocused && buildingFocusColor) ? buildingFocusColor : highlightLayerColor;
1300
- return (_jsx(DistrictFloor, { district: district, centerOffset: centerOffset, opacity: 1, highlightColor: districtColor }, district.path));
1332
+ return (_jsx(DistrictFloor, { district: district, centerOffset: centerOffset, opacity: 1, highlightColor: districtColor, growProgress: growProgress }, district.path));
1301
1333
  }), _jsx(InstancedBuildings, { buildings: cityData.buildings, centerOffset: centerOffset, onHover: onBuildingHover, onClick: onBuildingClick, hoveredIndex: hoveredIndex, selectedIndex: selectedIndex, growProgress: growProgress, animationConfig: animationConfig, heightScaling: heightScaling, linearScale: linearScale, staggerIndices: staggerIndices, focusDirectory: buildingFocusDirectory, highlightLayers: highlightLayers, isolationMode: isolationMode }), _jsx(BuildingIcons, { buildings: cityData.buildings, centerOffset: centerOffset, growProgress: growProgress, heightScaling: heightScaling, linearScale: linearScale, highlightLayers: highlightLayers, isolationMode: isolationMode, hasActiveHighlights: activeHighlights, staggerIndices: staggerIndices, springDuration: springDuration, staggerDelay: animationConfig.staggerDelay || 15 })] }));
1302
1334
  }
1303
1335
  /**
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * FileCity3D - 3D visualization component
3
3
  */
4
- export { FileCity3D, resetCamera, moveCameraTo, setCameraTarget, getCameraTarget, rotateCameraTo, rotateCameraBy, tiltCameraTo, tiltCameraBy, getCameraPosition, getCameraAngle, getCameraTilt } from './FileCity3D';
5
- export type { FileCity3DProps, AnimationConfig, HighlightLayer, HighlightItem, IsolationMode, HeightScaling, CityData, CityBuilding, CityDistrict, RotateOptions, } from './FileCity3D';
4
+ export { FileCity3D, resetCamera, getCameraAngle, getCameraTarget, getCameraTilt, rotateCameraTo, rotateCameraBy, tiltCameraTo, tiltCameraBy, moveCameraTo, setCameraTarget, } from './FileCity3D';
5
+ export type { FileCity3DProps, AnimationConfig, HighlightLayer, HighlightItem, IsolationMode, HeightScaling, CityData, CityBuilding, CityDistrict, } from './FileCity3D';
6
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/FileCity3D/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,cAAc,EAAE,cAAc,EAAE,YAAY,EAAE,YAAY,EAAE,iBAAiB,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AACrN,YAAY,EACV,eAAe,EACf,eAAe,EACf,cAAc,EACd,aAAa,EACb,aAAa,EACb,aAAa,EACb,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,aAAa,GACd,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/FileCity3D/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,UAAU,EACV,WAAW,EACX,cAAc,EACd,eAAe,EACf,aAAa,EACb,cAAc,EACd,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,eAAe,GAChB,MAAM,cAAc,CAAC;AACtB,YAAY,EACV,eAAe,EACf,eAAe,EACf,cAAc,EACd,aAAa,EACb,aAAa,EACb,aAAa,EACb,QAAQ,EACR,YAAY,EACZ,YAAY,GACb,MAAM,cAAc,CAAC"}
@@ -1,4 +1,4 @@
1
1
  /**
2
2
  * FileCity3D - 3D visualization component
3
3
  */
4
- export { FileCity3D, resetCamera, moveCameraTo, setCameraTarget, getCameraTarget, rotateCameraTo, rotateCameraBy, tiltCameraTo, tiltCameraBy, getCameraPosition, getCameraAngle, getCameraTilt } from './FileCity3D';
4
+ export { FileCity3D, resetCamera, getCameraAngle, getCameraTarget, getCameraTilt, rotateCameraTo, rotateCameraBy, tiltCameraTo, tiltCameraBy, moveCameraTo, setCameraTarget, } from './FileCity3D';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@principal-ai/file-city-react",
3
- "version": "0.5.17",
3
+ "version": "0.5.19",
4
4
  "type": "module",
5
5
  "description": "React components for File City visualization",
6
6
  "main": "dist/index.js",
@@ -745,12 +745,12 @@ function AnimatedIcon({
745
745
  staggerDelayMs,
746
746
  springDuration,
747
747
  }: AnimatedIconProps) {
748
- const spriteRef = useRef<THREE.Sprite>(null);
748
+ const meshRef = useRef<THREE.Mesh>(null);
749
749
  const startTimeRef = useRef<number | null>(null);
750
- const materialRef = useRef<THREE.SpriteMaterial>(null);
750
+ const materialRef = useRef<THREE.MeshBasicMaterial>(null);
751
751
 
752
752
  useFrame(({ clock }) => {
753
- if (!spriteRef.current) return;
753
+ if (!meshRef.current) return;
754
754
 
755
755
  if (startTimeRef.current === null && growProgress > 0) {
756
756
  startTimeRef.current = clock.elapsedTime * 1000;
@@ -775,31 +775,48 @@ function AnimatedIcon({
775
775
  const baseOffset = 0.2;
776
776
  const height = animProgress * targetHeight + minHeight;
777
777
  const buildingTop = height + baseOffset;
778
- const yPosition = buildingTop + iconSize / 2 + 2;
779
778
 
780
- spriteRef.current.position.y = yPosition;
779
+ // When flat (animProgress=0): icon lies flat at ground level
780
+ // When grown (animProgress=1): icon floats above building
781
+ const flatY = minHeight + baseOffset + 0.5;
782
+ const grownY = buildingTop + iconSize / 2 + 2;
783
+ const yPosition = flatY + (grownY - flatY) * animProgress;
784
+
785
+ meshRef.current.position.y = yPosition;
786
+
787
+ // Rotate from flat (facing up) to upright (facing camera-ish)
788
+ // Flat: -Math.PI / 2 (facing up)
789
+ // Grown: 0 (facing forward)
790
+ const flatRotationX = -Math.PI / 2;
791
+ const grownRotationX = 0;
792
+ meshRef.current.rotation.x = flatRotationX + (grownRotationX - flatRotationX) * animProgress;
781
793
 
782
794
  if (materialRef.current) {
783
- materialRef.current.opacity = opacity * animProgress;
795
+ // Show icons even when flat, fade out only slightly
796
+ const minOpacity = 0.8;
797
+ const effectiveOpacity = minOpacity + (1 - minOpacity) * animProgress;
798
+ materialRef.current.opacity = opacity * effectiveOpacity;
784
799
  }
785
800
  });
786
801
 
787
802
  return (
788
- <sprite
789
- ref={spriteRef}
803
+ <mesh
804
+ ref={meshRef}
790
805
  position={[x, 0, z]}
791
806
  scale={[iconSize, iconSize, 1]}
792
807
  raycast={() => null}
793
808
  >
794
- <spriteMaterial
809
+ <planeGeometry args={[1, 1]} />
810
+ <meshBasicMaterial
795
811
  ref={materialRef}
796
812
  map={texture}
797
813
  transparent
798
- opacity={0}
814
+ opacity={0.8}
799
815
  depthTest={true}
800
816
  depthWrite={false}
817
+ side={THREE.DoubleSide}
801
818
  />
802
- </sprite>
819
+ </mesh>
803
820
  );
804
821
  }
805
822
 
@@ -871,9 +888,7 @@ function BuildingIcons({
871
888
  staggerDelay,
872
889
  ]);
873
890
 
874
- // Don't render if no progress yet
875
- if (growProgress < 0.1) return null;
876
-
891
+ // Icons are now always rendered (flat or grown)
877
892
  return (
878
893
  <>
879
894
  {buildingsWithIcons.map(
@@ -884,7 +899,7 @@ function BuildingIcons({
884
899
 
885
900
  // Icon size based on building dimensions
886
901
  const [width] = building.dimensions;
887
- const baseSize = Math.max(width * 0.8, 6);
902
+ const baseSize = Math.max(width * 1.2, 8);
888
903
  const heightBoost = Math.min(targetHeight / 20, 3);
889
904
  const iconSize = (baseSize + heightBoost) * (icon.size || 1);
890
905
 
@@ -916,9 +931,10 @@ interface DistrictFloorProps {
916
931
  centerOffset: { x: number; z: number };
917
932
  opacity: number;
918
933
  highlightColor?: string | null;
934
+ growProgress: number;
919
935
  }
920
936
 
921
- function DistrictFloor({ district, centerOffset, highlightColor }: DistrictFloorProps) {
937
+ function DistrictFloor({ district, centerOffset, highlightColor, growProgress }: DistrictFloorProps) {
922
938
  const { worldBounds } = district;
923
939
  const width = worldBounds.maxX - worldBounds.minX;
924
940
  const depth = worldBounds.maxZ - worldBounds.minZ;
@@ -934,6 +950,21 @@ function DistrictFloor({ district, centerOffset, highlightColor }: DistrictFloor
934
950
  const lineWidth = highlightColor ? 3 : 1;
935
951
  const labelColor = highlightColor || '#cbd5e1';
936
952
 
953
+ // Interpolate text rotation and position based on growProgress
954
+ // Flat: -Math.PI / 2 (facing up), positioned at center of district
955
+ // Grown: -Math.PI / 6 (angled), positioned at edge of district
956
+ const flatRotationX = -Math.PI / 2;
957
+ const grownRotationX = -Math.PI / 6;
958
+ const textRotationX = flatRotationX + (grownRotationX - flatRotationX) * growProgress;
959
+
960
+ const flatY = 0.5;
961
+ const grownY = 1.5;
962
+ const textY = flatY + (grownY - flatY) * growProgress;
963
+
964
+ const flatZ = 0; // Center of district when flat
965
+ const grownZ = depth / 2 + 2; // Edge of district when grown
966
+ const textZ = flatZ + (grownZ - flatZ) * growProgress;
967
+
937
968
  return (
938
969
  <group position={[centerX, 0, centerZ]}>
939
970
  {/* Border outline */}
@@ -952,8 +983,8 @@ function DistrictFloor({ district, centerOffset, highlightColor }: DistrictFloor
952
983
 
953
984
  {district.label && (
954
985
  <Text
955
- position={[0, 1.5, depth / 2 + 2]}
956
- rotation={[-Math.PI / 6, 0, 0]}
986
+ position={[0, textY, textZ]}
987
+ rotation={[textRotationX, 0, 0]}
957
988
  fontSize={Math.min(3, width / 6)}
958
989
  color={labelColor}
959
990
  anchorX="center"
@@ -1107,8 +1138,22 @@ function AnimatedCamera({ citySize, isFlat, focusTarget, maxBuildingHeight = 0 }
1107
1138
  const frameCount = useRef(0);
1108
1139
 
1109
1140
  // Compute target camera position
1110
- // When flat, use a more top-down view; when grown, use an angled view
1141
+ // When flat, always use top-down view (ignore focusTarget)
1142
+ // When grown, use focusTarget if available, otherwise angled overview
1111
1143
  const targetPos = useMemo(() => {
1144
+ // Flat state: always top-down, ignore any focus
1145
+ if (isFlat) {
1146
+ return {
1147
+ x: 0,
1148
+ y: citySize * 2.0,
1149
+ z: 0.001, // Near-zero for top-down (tiny offset to avoid gimbal lock)
1150
+ targetX: 0,
1151
+ targetY: 0,
1152
+ targetZ: 0,
1153
+ };
1154
+ }
1155
+
1156
+ // Grown state: use focusTarget if available
1112
1157
  if (focusTarget) {
1113
1158
  const distance = Math.max(focusTarget.size * 2, 50);
1114
1159
  const height = Math.max(focusTarget.size * 1.5, 40);
@@ -1121,19 +1166,16 @@ function AnimatedCamera({ citySize, isFlat, focusTarget, maxBuildingHeight = 0 }
1121
1166
  targetZ: focusTarget.z,
1122
1167
  };
1123
1168
  }
1124
- // Default overview - adjust angle based on flat/grown state
1125
- // Flat: directly overhead (90 degrees, looking straight down)
1126
- // Grown: angled view to see building heights (optionally based on max building height)
1169
+
1170
+ // Grown state without focus: angled overview
1127
1171
  const baseHeight = citySize * 1.1;
1128
1172
  const buildingAwareHeight = maxBuildingHeight > 0
1129
1173
  ? Math.max(baseHeight, maxBuildingHeight * 2.5)
1130
1174
  : baseHeight;
1131
- const targetHeight = isFlat ? citySize * 2.0 : buildingAwareHeight;
1132
- const targetZ = isFlat ? 0.001 : citySize * 1.3; // Near-zero for top-down (tiny offset to avoid gimbal lock)
1133
1175
  return {
1134
1176
  x: 0,
1135
- y: targetHeight,
1136
- z: targetZ,
1177
+ y: buildingAwareHeight,
1178
+ z: citySize * 1.3,
1137
1179
  targetX: 0,
1138
1180
  targetY: 0,
1139
1181
  targetZ: 0,
@@ -2018,6 +2060,7 @@ function CityScene({
2018
2060
  centerOffset={centerOffset}
2019
2061
  opacity={1}
2020
2062
  highlightColor={districtColor}
2063
+ growProgress={growProgress}
2021
2064
  />
2022
2065
  );
2023
2066
  })}
@@ -2,7 +2,19 @@
2
2
  * FileCity3D - 3D visualization component
3
3
  */
4
4
 
5
- export { FileCity3D, resetCamera, moveCameraTo, setCameraTarget, getCameraTarget, rotateCameraTo, rotateCameraBy, tiltCameraTo, tiltCameraBy, getCameraPosition, getCameraAngle, getCameraTilt } from './FileCity3D';
5
+ export {
6
+ FileCity3D,
7
+ resetCamera,
8
+ getCameraAngle,
9
+ getCameraTarget,
10
+ getCameraTilt,
11
+ rotateCameraTo,
12
+ rotateCameraBy,
13
+ tiltCameraTo,
14
+ tiltCameraBy,
15
+ moveCameraTo,
16
+ setCameraTarget,
17
+ } from './FileCity3D';
6
18
  export type {
7
19
  FileCity3DProps,
8
20
  AnimationConfig,
@@ -13,5 +25,4 @@ export type {
13
25
  CityData,
14
26
  CityBuilding,
15
27
  CityDistrict,
16
- RotateOptions,
17
28
  } from './FileCity3D';