@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.
- package/dist/components/FileCity3D/FileCity3D.d.ts.map +1 -1
- package/dist/components/FileCity3D/FileCity3D.js +53 -21
- package/dist/components/FileCity3D/index.d.ts +2 -2
- package/dist/components/FileCity3D/index.d.ts.map +1 -1
- package/dist/components/FileCity3D/index.js +1 -1
- package/package.json +1 -1
- package/src/components/FileCity3D/FileCity3D.tsx +69 -26
- package/src/components/FileCity3D/index.ts +13 -2
|
@@ -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;AAGxD,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;CAClB;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;
|
|
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
|
|
442
|
+
const meshRef = useRef(null);
|
|
443
443
|
const startTimeRef = useRef(null);
|
|
444
444
|
const materialRef = useRef(null);
|
|
445
445
|
useFrame(({ clock }) => {
|
|
446
|
-
if (!
|
|
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
|
-
|
|
469
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
//
|
|
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 *
|
|
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
|
-
|
|
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
|
|
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
|
-
//
|
|
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:
|
|
661
|
-
z:
|
|
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,
|
|
5
|
-
export type { FileCity3DProps, AnimationConfig, HighlightLayer, HighlightItem, IsolationMode, HeightScaling, CityData, CityBuilding, CityDistrict,
|
|
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,
|
|
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,
|
|
4
|
+
export { FileCity3D, resetCamera, getCameraAngle, getCameraTarget, getCameraTilt, rotateCameraTo, rotateCameraBy, tiltCameraTo, tiltCameraBy, moveCameraTo, setCameraTarget, } from './FileCity3D';
|
package/package.json
CHANGED
|
@@ -745,12 +745,12 @@ function AnimatedIcon({
|
|
|
745
745
|
staggerDelayMs,
|
|
746
746
|
springDuration,
|
|
747
747
|
}: AnimatedIconProps) {
|
|
748
|
-
const
|
|
748
|
+
const meshRef = useRef<THREE.Mesh>(null);
|
|
749
749
|
const startTimeRef = useRef<number | null>(null);
|
|
750
|
-
const materialRef = useRef<THREE.
|
|
750
|
+
const materialRef = useRef<THREE.MeshBasicMaterial>(null);
|
|
751
751
|
|
|
752
752
|
useFrame(({ clock }) => {
|
|
753
|
-
if (!
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
<
|
|
789
|
-
ref={
|
|
803
|
+
<mesh
|
|
804
|
+
ref={meshRef}
|
|
790
805
|
position={[x, 0, z]}
|
|
791
806
|
scale={[iconSize, iconSize, 1]}
|
|
792
807
|
raycast={() => null}
|
|
793
808
|
>
|
|
794
|
-
<
|
|
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
|
-
</
|
|
819
|
+
</mesh>
|
|
803
820
|
);
|
|
804
821
|
}
|
|
805
822
|
|
|
@@ -871,9 +888,7 @@ function BuildingIcons({
|
|
|
871
888
|
staggerDelay,
|
|
872
889
|
]);
|
|
873
890
|
|
|
874
|
-
//
|
|
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 *
|
|
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,
|
|
956
|
-
rotation={[
|
|
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
|
|
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
|
-
|
|
1125
|
-
//
|
|
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:
|
|
1136
|
-
z:
|
|
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 {
|
|
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';
|