@principal-ai/file-city-react 0.5.43 → 0.5.45
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 +42 -1
- package/dist/components/FileCity3D/FileCity3D.d.ts.map +1 -1
- package/dist/components/FileCity3D/FileCity3D.js +62 -9
- 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 +2 -1
- package/src/components/FileCity3D/FileCity3D.tsx +113 -3
- package/src/components/FileCity3D/index.ts +3 -0
- package/src/stories/CameraOffsetForOverlays.stories.tsx +470 -0
- package/src/stories/HighlightLayersFlatDebug.stories.tsx +319 -0
- package/src/stories/LeaderLineSnippetOverlay3D.stories.tsx +1060 -0
- package/src/stories/SequenceDiagramOverlay.stories.tsx +427 -0
- package/src/stories/WorkflowSequenceDiagramPrototype.stories.tsx +754 -0
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import React from 'react';
|
|
10
10
|
import type { CityData, CityBuilding, CityDistrict, HighlightLayer as BuilderHighlightLayer, LayerItem, LayerRenderStrategy } from '@principal-ai/file-city-builder';
|
|
11
|
+
import * as THREE from 'three';
|
|
11
12
|
import type { ThreeElements } from '@react-three/fiber';
|
|
12
13
|
declare module 'react' {
|
|
13
14
|
namespace JSX {
|
|
@@ -24,6 +25,23 @@ export interface SelectionStyle {
|
|
|
24
25
|
/** Ring border width in world units. Default: 2. */
|
|
25
26
|
borderWidth?: number;
|
|
26
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Per-frame camera callback signature. Fires once per R3F render frame from
|
|
30
|
+
* inside the Canvas, so projection done in the callback is in lockstep with
|
|
31
|
+
* the city render — useful for HTML/SVG overlays that need to track world
|
|
32
|
+
* positions (e.g. leader lines anchored to buildings).
|
|
33
|
+
*
|
|
34
|
+
* The callback receives the live `THREE.Camera` and the current canvas
|
|
35
|
+
* size in CSS pixels. To project a world point to canvas-local pixels:
|
|
36
|
+
*
|
|
37
|
+
* const v = new THREE.Vector3(x, y, z).project(camera);
|
|
38
|
+
* const px = (v.x * 0.5 + 0.5) * size.width;
|
|
39
|
+
* const py = (v.y * -0.5 + 0.5) * size.height;
|
|
40
|
+
*/
|
|
41
|
+
export type OnCameraFrame = (camera: THREE.Camera, size: {
|
|
42
|
+
width: number;
|
|
43
|
+
height: number;
|
|
44
|
+
}) => void;
|
|
27
45
|
/** What to do with non-highlighted buildings */
|
|
28
46
|
export type IsolationMode = 'none' | 'transparent' | 'collapse' | 'hide';
|
|
29
47
|
export interface AnimationConfig {
|
|
@@ -125,6 +143,13 @@ export interface CameraControlsConfig {
|
|
|
125
143
|
export declare const DEFAULT_CAMERA_CONTROLS: Required<Omit<CameraControlsConfig, 'panSpeed' | 'rotateSpeed' | 'zoomSpeed'>> & Pick<CameraControlsConfig, 'panSpeed' | 'rotateSpeed' | 'zoomSpeed'>;
|
|
126
144
|
export declare function resetCamera(): void;
|
|
127
145
|
export declare function moveCameraTo(x: number, z: number, size?: number): void;
|
|
146
|
+
/**
|
|
147
|
+
* Position the camera straight overhead a target at a specific height (flat
|
|
148
|
+
* top-down view). Use to fit the city inside a sub-rect of the canvas — e.g.
|
|
149
|
+
* when overlays cover part of the viewport. Larger `height` = city appears
|
|
150
|
+
* smaller; smaller `height` = zoomed in.
|
|
151
|
+
*/
|
|
152
|
+
export declare function setCameraFlatView(x: number, z: number, height: number, options?: RotateOptions): void;
|
|
128
153
|
/**
|
|
129
154
|
* Set the camera's look-at target (center point for orbiting).
|
|
130
155
|
* Camera maintains its current distance and angles relative to the new target.
|
|
@@ -254,6 +279,14 @@ export interface FileCity3DProps {
|
|
|
254
279
|
adaptCameraToBuildings?: boolean;
|
|
255
280
|
/** Base file type color layers (resolved with highlightLayers) */
|
|
256
281
|
fileColorLayers?: HighlightLayer[];
|
|
282
|
+
/**
|
|
283
|
+
* Override the per-building color fallback. When unset (default), buildings
|
|
284
|
+
* not matched by a fill highlight layer are colored by file extension via
|
|
285
|
+
* the built-in file-type palette. Set to a CSS color (e.g. `'#475569'`) to
|
|
286
|
+
* render unmatched buildings in a neutral tone — useful for debug stories
|
|
287
|
+
* that want to isolate highlight-layer rendering.
|
|
288
|
+
*/
|
|
289
|
+
defaultBuildingColor?: string;
|
|
257
290
|
/**
|
|
258
291
|
* Translucent slabs rendered above the city showing scope coverage as
|
|
259
292
|
* elevated planes over the directories they own.
|
|
@@ -280,6 +313,14 @@ export interface FileCity3DProps {
|
|
|
280
313
|
* Memoize this object to avoid unnecessary camera re-mounts.
|
|
281
314
|
*/
|
|
282
315
|
cameraControls?: CameraControlsConfig;
|
|
316
|
+
/**
|
|
317
|
+
* Fires once per R3F render frame with the live camera and canvas size.
|
|
318
|
+
* Use to project world points to canvas pixels for HTML/SVG overlays that
|
|
319
|
+
* need to track buildings as the camera pans / zooms / rotates.
|
|
320
|
+
*
|
|
321
|
+
* Memoize the callback to avoid re-mounting the bridge.
|
|
322
|
+
*/
|
|
323
|
+
onCameraFrame?: OnCameraFrame;
|
|
283
324
|
}
|
|
284
325
|
/**
|
|
285
326
|
* FileCity3D - 3D visualization of codebase structure
|
|
@@ -287,6 +328,6 @@ export interface FileCity3DProps {
|
|
|
287
328
|
* Renders CityData as an interactive 3D city where buildings represent files
|
|
288
329
|
* and their height corresponds to line count or file size.
|
|
289
330
|
*/
|
|
290
|
-
export declare function FileCity3D({ cityData, width, height, onBuildingClick, onBuildingHover, className, style, animation, isGrown: externalIsGrown, onGrowChange, showControls, elevatedScopePanels, dismissingPanelIds, onPanelDismissed, highlightLayers: externalHighlightLayers, isolationMode: externalIsolationMode, dimOpacity: _dimOpacity, isLoading, loadingMessage, emptyMessage, heightScaling, linearScale, flatPatterns, focusDirectory: externalFocusDirectory, focusColor: externalFocusColor, onDirectorySelect: _onDirectorySelect, backgroundColor, textColor, selectedBuilding, selectedPath, selectionStyle, adaptCameraToBuildings, fileColorLayers, cameraControls, }: FileCity3DProps): import("react/jsx-runtime").JSX.Element;
|
|
331
|
+
export declare function FileCity3D({ cityData, width, height, onBuildingClick, onBuildingHover, className, style, animation, isGrown: externalIsGrown, onGrowChange, showControls, elevatedScopePanels, dismissingPanelIds, onPanelDismissed, highlightLayers: externalHighlightLayers, isolationMode: externalIsolationMode, dimOpacity: _dimOpacity, isLoading, loadingMessage, emptyMessage, heightScaling, linearScale, flatPatterns, focusDirectory: externalFocusDirectory, focusColor: externalFocusColor, onDirectorySelect: _onDirectorySelect, backgroundColor, textColor, selectedBuilding, selectedPath, selectionStyle, adaptCameraToBuildings, fileColorLayers, defaultBuildingColor, cameraControls, onCameraFrame, }: FileCity3DProps): import("react/jsx-runtime").JSX.Element;
|
|
291
332
|
export default FileCity3D;
|
|
292
333
|
//# sourceMappingURL=FileCity3D.d.ts.map
|
|
@@ -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;AAOjF,OAAO,KAAK,EACV,QAAQ,EACR,YAAY,EACZ,YAAY,EAEZ,cAAc,IAAI,qBAAqB,EACvC,SAAS,EACT,mBAAmB,EACpB,MAAM,iCAAiC,CAAC;
|
|
1
|
+
{"version":3,"file":"FileCity3D.d.ts","sourceRoot":"","sources":["../../../src/components/FileCity3D/FileCity3D.tsx"],"names":[],"mappings":"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"}
|
|
@@ -430,7 +430,7 @@ function BorderHighlights({ buildings, centerOffset, highlightLayers, growProgre
|
|
|
430
430
|
});
|
|
431
431
|
if (borderEdgeData.length === 0)
|
|
432
432
|
return null;
|
|
433
|
-
return (_jsxs("instancedMesh", { ref: meshRef, args: [undefined, undefined, borderEdgeData.length], frustumCulled: false, children: [_jsx("boxGeometry", { args: [1, 1, 1] }), _jsx("meshBasicMaterial", { transparent: true, opacity: 0.9
|
|
433
|
+
return (_jsxs("instancedMesh", { ref: meshRef, args: [undefined, undefined, borderEdgeData.length], frustumCulled: false, children: [_jsx("boxGeometry", { args: [1, 1, 1] }), _jsx("meshBasicMaterial", { transparent: true, opacity: 0.9 })] }));
|
|
434
434
|
}
|
|
435
435
|
// Helper to check if a path is inside a directory
|
|
436
436
|
function isPathInDirectory(path, directory) {
|
|
@@ -438,7 +438,7 @@ function isPathInDirectory(path, directory) {
|
|
|
438
438
|
return true;
|
|
439
439
|
return path === directory || path.startsWith(directory + '/');
|
|
440
440
|
}
|
|
441
|
-
function InstancedBuildings({ buildings, centerOffset, onHover, onClick, hoveredIndex, selectedIndex, growProgress, animationConfig, heightScaling, linearScale, flatPatterns, staggerIndices, focusDirectory, highlightLayers, isolationMode, }) {
|
|
441
|
+
function InstancedBuildings({ buildings, centerOffset, onHover, onClick, hoveredIndex, selectedIndex, growProgress, animationConfig, heightScaling, linearScale, flatPatterns, staggerIndices, focusDirectory, highlightLayers, isolationMode, defaultBuildingColor, }) {
|
|
442
442
|
const meshRef = useRef(null);
|
|
443
443
|
const startTimeRef = useRef(null);
|
|
444
444
|
const tempObject = useMemo(() => new THREE.Object3D(), []);
|
|
@@ -515,7 +515,7 @@ function InstancedBuildings({ buildings, centerOffset, onHover, onClick, hovered
|
|
|
515
515
|
// Get all layer matches and find first fill match for building color
|
|
516
516
|
const matches = getLayerMatchesForPath(building.path, highlightLayers);
|
|
517
517
|
const fillMatch = matches.find(m => m.renderStrategy === 'fill');
|
|
518
|
-
const color = fillMatch?.color ?? getColorForFile(building);
|
|
518
|
+
const color = fillMatch?.color ?? defaultBuildingColor ?? getColorForFile(building);
|
|
519
519
|
const x = building.position.x - centerOffset.x;
|
|
520
520
|
const z = building.position.z - centerOffset.z;
|
|
521
521
|
const staggerIndex = staggerIndices[index] ?? index;
|
|
@@ -541,6 +541,7 @@ function InstancedBuildings({ buildings, centerOffset, onHover, onClick, hovered
|
|
|
541
541
|
staggerIndices,
|
|
542
542
|
animationConfig.staggerDelay,
|
|
543
543
|
highlightLayers,
|
|
544
|
+
defaultBuildingColor,
|
|
544
545
|
]);
|
|
545
546
|
const minHeight = 0.3;
|
|
546
547
|
const baseOffset = 0.2;
|
|
@@ -817,6 +818,15 @@ export function resetCamera() {
|
|
|
817
818
|
export function moveCameraTo(x, z, size) {
|
|
818
819
|
cameraApi?.moveTo(x, z, size);
|
|
819
820
|
}
|
|
821
|
+
/**
|
|
822
|
+
* Position the camera straight overhead a target at a specific height (flat
|
|
823
|
+
* top-down view). Use to fit the city inside a sub-rect of the canvas — e.g.
|
|
824
|
+
* when overlays cover part of the viewport. Larger `height` = city appears
|
|
825
|
+
* smaller; smaller `height` = zoomed in.
|
|
826
|
+
*/
|
|
827
|
+
export function setCameraFlatView(x, z, height, options) {
|
|
828
|
+
cameraApi?.setFlatView(x, z, height, options);
|
|
829
|
+
}
|
|
820
830
|
/**
|
|
821
831
|
* Set the camera's look-at target (center point for orbiting).
|
|
822
832
|
* Camera maintains its current distance and angles relative to the new target.
|
|
@@ -886,6 +896,18 @@ export function getCameraAngle() {
|
|
|
886
896
|
export function getCameraTilt() {
|
|
887
897
|
return cameraApi?.getCurrentTilt() ?? null;
|
|
888
898
|
}
|
|
899
|
+
/**
|
|
900
|
+
* Bridge for piping the live camera + canvas size out of the R3F Canvas on
|
|
901
|
+
* every frame. Mounted as a child of `<Canvas>` so it has access to the R3F
|
|
902
|
+
* render loop; runs zero work if no callback is provided.
|
|
903
|
+
*/
|
|
904
|
+
function CameraFrameBridge({ onCameraFrame }) {
|
|
905
|
+
const { camera, size } = useThree();
|
|
906
|
+
useFrame(() => {
|
|
907
|
+
onCameraFrame?.(camera, { width: size.width, height: size.height });
|
|
908
|
+
});
|
|
909
|
+
return null;
|
|
910
|
+
}
|
|
889
911
|
const AnimatedCamera = React.memo(function AnimatedCamera({ citySize, isFlat, focusTarget, maxBuildingHeight = 0, cameraControls, onCameraReady, }) {
|
|
890
912
|
// Use selector to only subscribe to camera, not the entire R3F state
|
|
891
913
|
// This prevents re-renders on pointer movement
|
|
@@ -1219,6 +1241,36 @@ const AnimatedCamera = React.memo(function AnimatedCamera({ citySize, isFlat, fo
|
|
|
1219
1241
|
lookZ: z,
|
|
1220
1242
|
});
|
|
1221
1243
|
}, [citySize, api]);
|
|
1244
|
+
// Position the camera directly above a target at a given height (flat /
|
|
1245
|
+
// top-down view). Useful for fitting the city into a visible sub-rect of
|
|
1246
|
+
// the canvas — raise `height` to make the city appear smaller, lower it to
|
|
1247
|
+
// zoom in. Pairs with `setTarget` for pan-only changes.
|
|
1248
|
+
const setFlatView = useCallback((x, z, height, options) => {
|
|
1249
|
+
// MapControls (drag/wheel) mutates camera.position and controls.target
|
|
1250
|
+
// directly without touching our spring. If we don't resync first, a
|
|
1251
|
+
// call here can no-op when the spring's stored values happen to equal
|
|
1252
|
+
// the requested target even though the visible camera is elsewhere.
|
|
1253
|
+
if (controlsRef.current) {
|
|
1254
|
+
api.set({
|
|
1255
|
+
camX: camera.position.x,
|
|
1256
|
+
camY: camera.position.y,
|
|
1257
|
+
camZ: camera.position.z,
|
|
1258
|
+
lookX: controlsRef.current.target.x,
|
|
1259
|
+
lookY: controlsRef.current.target.y,
|
|
1260
|
+
lookZ: controlsRef.current.target.z,
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
1263
|
+
const config = options?.duration ? { duration: options.duration } : undefined;
|
|
1264
|
+
api.start({
|
|
1265
|
+
camX: x,
|
|
1266
|
+
camY: height,
|
|
1267
|
+
camZ: z + 0.001, // tiny offset to avoid gimbal lock when looking straight down
|
|
1268
|
+
lookX: x,
|
|
1269
|
+
lookY: 0,
|
|
1270
|
+
lookZ: z,
|
|
1271
|
+
...(config ? { config } : {}),
|
|
1272
|
+
});
|
|
1273
|
+
}, [api, camera]);
|
|
1222
1274
|
// Set camera target (look-at point), maintaining current distance and angles
|
|
1223
1275
|
const setTarget = useCallback((x, y, z, options) => {
|
|
1224
1276
|
// Get current offset from target
|
|
@@ -1409,6 +1461,7 @@ const AnimatedCamera = React.memo(function AnimatedCamera({ citySize, isFlat, fo
|
|
|
1409
1461
|
cameraApi = {
|
|
1410
1462
|
reset: resetToInitial,
|
|
1411
1463
|
moveTo,
|
|
1464
|
+
setFlatView,
|
|
1412
1465
|
setTarget,
|
|
1413
1466
|
rotateTo,
|
|
1414
1467
|
rotateBy,
|
|
@@ -1422,7 +1475,7 @@ const AnimatedCamera = React.memo(function AnimatedCamera({ citySize, isFlat, fo
|
|
|
1422
1475
|
return () => {
|
|
1423
1476
|
cameraApi = null;
|
|
1424
1477
|
};
|
|
1425
|
-
}, [resetToInitial, moveTo, setTarget, rotateTo, rotateBy, tiltTo, tiltBy, getCurrentPosition, getCurrentTarget, getCurrentAngle, getCurrentTilt]);
|
|
1478
|
+
}, [resetToInitial, moveTo, setFlatView, setTarget, rotateTo, rotateBy, tiltTo, tiltBy, getCurrentPosition, getCurrentTarget, getCurrentAngle, getCurrentTilt]);
|
|
1426
1479
|
// Custom wheel handler for wheel === 'pan'. We disable MapControls' built-in
|
|
1427
1480
|
// zoom (otherwise it competes with our handler) and handle both axes here:
|
|
1428
1481
|
// ctrl/⌘+wheel = zoom (matches trackpad pinch), plain wheel = pan along the
|
|
@@ -1679,7 +1732,7 @@ function SelectionRing({ district, centerOffset, color, borderWidth, growProgres
|
|
|
1679
1732
|
const barH = 0.5;
|
|
1680
1733
|
return (_jsxs("group", { position: [cx, y, cz], children: [_jsxs("mesh", { position: [0, 0, -d / 2], renderOrder: 20, children: [_jsx("boxGeometry", { args: [w + t, barH, t] }), _jsx("meshBasicMaterial", { color: color, transparent: true, opacity: 0.95 })] }), _jsxs("mesh", { position: [0, 0, d / 2], renderOrder: 20, children: [_jsx("boxGeometry", { args: [w + t, barH, t] }), _jsx("meshBasicMaterial", { color: color, transparent: true, opacity: 0.95 })] }), _jsxs("mesh", { position: [-w / 2, 0, 0], renderOrder: 20, children: [_jsx("boxGeometry", { args: [t, barH, d + t] }), _jsx("meshBasicMaterial", { color: color, transparent: true, opacity: 0.95 })] }), _jsxs("mesh", { position: [w / 2, 0, 0], renderOrder: 20, children: [_jsx("boxGeometry", { args: [t, barH, d + t] }), _jsx("meshBasicMaterial", { color: color, transparent: true, opacity: 0.95 })] })] }));
|
|
1681
1734
|
}
|
|
1682
|
-
function CityScene({ cityData, onBuildingHover, onBuildingClick, hoveredBuilding, selectedBuilding, selectedDistrict, selectionStyle, growProgress, animationConfig, highlightLayers, isolationMode, heightScaling, linearScale, flatPatterns, focusDirectory, focusColor, adaptCameraToBuildings = false, elevatedScopePanels, dismissingPanelIds, onPanelDismissed, cameraControls, onCameraReady, }) {
|
|
1735
|
+
function CityScene({ cityData, onBuildingHover, onBuildingClick, hoveredBuilding, selectedBuilding, selectedDistrict, selectionStyle, growProgress, animationConfig, highlightLayers, isolationMode, heightScaling, linearScale, flatPatterns, focusDirectory, focusColor, adaptCameraToBuildings = false, elevatedScopePanels, dismissingPanelIds, onPanelDismissed, cameraControls, defaultBuildingColor, onCameraReady, }) {
|
|
1683
1736
|
const centerOffset = useMemo(() => ({
|
|
1684
1737
|
x: (cityData.bounds.minX + cityData.bounds.maxX) / 2,
|
|
1685
1738
|
z: (cityData.bounds.minZ + cityData.bounds.maxZ) / 2,
|
|
@@ -1883,7 +1936,7 @@ function CityScene({ cityData, onBuildingHover, onBuildingClick, hoveredBuilding
|
|
|
1883
1936
|
// Focus color takes priority, then highlight layer color
|
|
1884
1937
|
const districtColor = (isFocused && buildingFocusColor) ? buildingFocusColor : highlightLayerColor;
|
|
1885
1938
|
return (_jsx(DistrictFloor, { district: district, centerOffset: centerOffset, opacity: 1, highlightColor: districtColor, growProgress: growProgress }, district.path));
|
|
1886
|
-
}), _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 }), growProgress === 0 &&
|
|
1939
|
+
}), _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, defaultBuildingColor: defaultBuildingColor }), _jsx(BuildingIcons, { buildings: cityData.buildings, centerOffset: centerOffset, growProgress: growProgress, heightScaling: heightScaling, linearScale: linearScale, flatPatterns: flatPatterns, highlightLayers: highlightLayers, isolationMode: isolationMode, hasActiveHighlights: activeHighlights }), growProgress === 0 &&
|
|
1887
1940
|
elevatedScopePanels?.map(panel => (_jsx(ElevatedScopePanelMesh, { panel: panel, centerOffset: centerOffset, dismissing: dismissingPanelIds?.has(panel.id) ?? false, onDismissed: onPanelDismissed }, panel.id))), selectedDistrict && (_jsx(SelectionRing, { district: selectedDistrict, centerOffset: centerOffset, color: selectionStyle?.color ?? '#facc15', borderWidth: selectionStyle?.borderWidth ?? 2, growProgress: growProgress }))] }));
|
|
1888
1941
|
}
|
|
1889
1942
|
/**
|
|
@@ -1892,7 +1945,7 @@ function CityScene({ cityData, onBuildingHover, onBuildingClick, hoveredBuilding
|
|
|
1892
1945
|
* Renders CityData as an interactive 3D city where buildings represent files
|
|
1893
1946
|
* and their height corresponds to line count or file size.
|
|
1894
1947
|
*/
|
|
1895
|
-
export function FileCity3D({ cityData, width = '100%', height = 600, onBuildingClick, onBuildingHover, className, style, animation, isGrown: externalIsGrown, onGrowChange, showControls = false, elevatedScopePanels, dismissingPanelIds, onPanelDismissed, highlightLayers: externalHighlightLayers, isolationMode: externalIsolationMode, dimOpacity: _dimOpacity = 0.15, isLoading = false, loadingMessage = 'Loading file city...', emptyMessage = 'No file tree data available', heightScaling = 'linear', linearScale = 1, flatPatterns = DEFAULT_FLAT_PATTERNS, focusDirectory: externalFocusDirectory, focusColor: externalFocusColor, onDirectorySelect: _onDirectorySelect, backgroundColor = '#0f172a', textColor = '#94a3b8', selectedBuilding = null, selectedPath = null, selectionStyle, adaptCameraToBuildings = false, fileColorLayers, cameraControls, }) {
|
|
1948
|
+
export function FileCity3D({ cityData, width = '100%', height = 600, onBuildingClick, onBuildingHover, className, style, animation, isGrown: externalIsGrown, onGrowChange, showControls = false, elevatedScopePanels, dismissingPanelIds, onPanelDismissed, highlightLayers: externalHighlightLayers, isolationMode: externalIsolationMode, dimOpacity: _dimOpacity = 0.15, isLoading = false, loadingMessage = 'Loading file city...', emptyMessage = 'No file tree data available', heightScaling = 'linear', linearScale = 1, flatPatterns = DEFAULT_FLAT_PATTERNS, focusDirectory: externalFocusDirectory, focusColor: externalFocusColor, onDirectorySelect: _onDirectorySelect, backgroundColor = '#0f172a', textColor = '#94a3b8', selectedBuilding = null, selectedPath = null, selectionStyle, adaptCameraToBuildings = false, fileColorLayers, defaultBuildingColor, cameraControls, onCameraFrame, }) {
|
|
1896
1949
|
const [hoveredBuilding, setHoveredBuilding] = useState(null);
|
|
1897
1950
|
const [internalIsGrown, setInternalIsGrown] = useState(false);
|
|
1898
1951
|
const [cameraReady, setCameraReady] = useState(false);
|
|
@@ -2007,7 +2060,7 @@ export function FileCity3D({ cityData, width = '100%', height = 600, onBuildingC
|
|
|
2007
2060
|
background: backgroundColor,
|
|
2008
2061
|
overflow: 'hidden',
|
|
2009
2062
|
...style,
|
|
2010
|
-
}, children: [
|
|
2063
|
+
}, children: [_jsxs(Canvas, { shadows: true, flat // Disables tone mapping for true colors
|
|
2011
2064
|
: true, style: {
|
|
2012
2065
|
position: 'absolute',
|
|
2013
2066
|
top: 0,
|
|
@@ -2016,6 +2069,6 @@ export function FileCity3D({ cityData, width = '100%', height = 600, onBuildingC
|
|
|
2016
2069
|
height: '100%',
|
|
2017
2070
|
opacity: cameraReady ? 1 : 0,
|
|
2018
2071
|
transition: 'opacity 0.1s ease-in',
|
|
2019
|
-
}, children: _jsx(CityScene, { cityData: cityData, onBuildingHover: handleBuildingHover, onBuildingClick: onBuildingClick, hoveredBuilding: hoveredBuilding, selectedBuilding: resolvedSelection.building, selectedDistrict: resolvedSelection.district, selectionStyle: selectionStyle, growProgress: growProgress, animationConfig: animationConfig, highlightLayers: highlightLayers, isolationMode: isolationMode, heightScaling: heightScaling, linearScale: linearScale, flatPatterns: flatPatterns, focusDirectory: focusDirectory, focusColor: focusColor, adaptCameraToBuildings: adaptCameraToBuildings, elevatedScopePanels: elevatedScopePanels, dismissingPanelIds: dismissingPanelIds, onPanelDismissed: onPanelDismissed, cameraControls: cameraControls, onCameraReady: () => setCameraReady(true) }) }), _jsx(InfoPanel, { building: resolvedSelection.building }), showControls && (_jsx(ControlsOverlay, { isFlat: !isGrown, onToggle: handleToggle, onResetCamera: resetCamera, onLookDown: () => tiltCameraTo(0) }))] }));
|
|
2072
|
+
}, children: [_jsx(CityScene, { cityData: cityData, onBuildingHover: handleBuildingHover, onBuildingClick: onBuildingClick, hoveredBuilding: hoveredBuilding, selectedBuilding: resolvedSelection.building, selectedDistrict: resolvedSelection.district, selectionStyle: selectionStyle, growProgress: growProgress, animationConfig: animationConfig, highlightLayers: highlightLayers, isolationMode: isolationMode, heightScaling: heightScaling, linearScale: linearScale, flatPatterns: flatPatterns, focusDirectory: focusDirectory, focusColor: focusColor, adaptCameraToBuildings: adaptCameraToBuildings, elevatedScopePanels: elevatedScopePanels, dismissingPanelIds: dismissingPanelIds, onPanelDismissed: onPanelDismissed, cameraControls: cameraControls, defaultBuildingColor: defaultBuildingColor, onCameraReady: () => setCameraReady(true) }), onCameraFrame && _jsx(CameraFrameBridge, { onCameraFrame: onCameraFrame })] }), _jsx(InfoPanel, { building: resolvedSelection.building }), showControls && (_jsx(ControlsOverlay, { isFlat: !isGrown, onToggle: handleToggle, onResetCamera: resetCamera, onLookDown: () => tiltCameraTo(0) }))] }));
|
|
2020
2073
|
}
|
|
2021
2074
|
export default FileCity3D;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* FileCity3D - 3D visualization component
|
|
3
3
|
*/
|
|
4
|
-
export { FileCity3D, resetCamera, getCameraAngle, getCameraTarget, getCameraTilt, rotateCameraTo, rotateCameraBy, tiltCameraTo, tiltCameraBy, moveCameraTo, setCameraTarget, DEFAULT_FLAT_PATTERNS, DEFAULT_CAMERA_CONTROLS, } from './FileCity3D';
|
|
5
|
-
export type { FileCity3DProps, AnimationConfig, HighlightLayer, LayerItem, LayerRenderStrategy, IsolationMode, HeightScaling, FlatPattern, ElevatedScopePanel, SelectionStyle, CityData, CityBuilding, CityDistrict, CameraControlsConfig, MouseDragAction, TouchOneAction, TouchTwoAction, WheelAction, } from './FileCity3D';
|
|
4
|
+
export { FileCity3D, resetCamera, getCameraAngle, getCameraPosition, getCameraTarget, getCameraTilt, rotateCameraTo, rotateCameraBy, tiltCameraTo, tiltCameraBy, moveCameraTo, setCameraFlatView, setCameraTarget, DEFAULT_FLAT_PATTERNS, DEFAULT_CAMERA_CONTROLS, } from './FileCity3D';
|
|
5
|
+
export type { FileCity3DProps, AnimationConfig, HighlightLayer, LayerItem, LayerRenderStrategy, IsolationMode, HeightScaling, FlatPattern, ElevatedScopePanel, SelectionStyle, CityData, CityBuilding, CityDistrict, CameraControlsConfig, MouseDragAction, TouchOneAction, TouchTwoAction, WheelAction, OnCameraFrame, } 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,EACL,UAAU,EACV,WAAW,EACX,cAAc,EACd,eAAe,EACf,aAAa,EACb,cAAc,EACd,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,qBAAqB,EACrB,uBAAuB,GACxB,MAAM,cAAc,CAAC;AACtB,YAAY,EACV,eAAe,EACf,eAAe,EACf,cAAc,EACd,SAAS,EACT,mBAAmB,EACnB,aAAa,EACb,aAAa,EACb,WAAW,EACX,kBAAkB,EAClB,cAAc,EACd,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,oBAAoB,EACpB,eAAe,EACf,cAAc,EACd,cAAc,EACd,WAAW,
|
|
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,iBAAiB,EACjB,eAAe,EACf,aAAa,EACb,cAAc,EACd,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,iBAAiB,EACjB,eAAe,EACf,qBAAqB,EACrB,uBAAuB,GACxB,MAAM,cAAc,CAAC;AACtB,YAAY,EACV,eAAe,EACf,eAAe,EACf,cAAc,EACd,SAAS,EACT,mBAAmB,EACnB,aAAa,EACb,aAAa,EACb,WAAW,EACX,kBAAkB,EAClB,cAAc,EACd,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,oBAAoB,EACpB,eAAe,EACf,cAAc,EACd,cAAc,EACd,WAAW,EACX,aAAa,GACd,MAAM,cAAc,CAAC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* FileCity3D - 3D visualization component
|
|
3
3
|
*/
|
|
4
|
-
export { FileCity3D, resetCamera, getCameraAngle, getCameraTarget, getCameraTilt, rotateCameraTo, rotateCameraBy, tiltCameraTo, tiltCameraBy, moveCameraTo, setCameraTarget, DEFAULT_FLAT_PATTERNS, DEFAULT_CAMERA_CONTROLS, } from './FileCity3D';
|
|
4
|
+
export { FileCity3D, resetCamera, getCameraAngle, getCameraPosition, getCameraTarget, getCameraTilt, rotateCameraTo, rotateCameraBy, tiltCameraTo, tiltCameraBy, moveCameraTo, setCameraFlatView, setCameraTarget, DEFAULT_FLAT_PATTERNS, DEFAULT_CAMERA_CONTROLS, } from './FileCity3D';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@principal-ai/file-city-react",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.45",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "React components for File City visualization",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"@principal-ade/industry-theme": "^0.1.19",
|
|
31
31
|
"@principal-ai/file-city-builder": "^0.4.5",
|
|
32
32
|
"@principal-ai/principal-view-core": "^0.28.5",
|
|
33
|
+
"@principal-ai/principal-view-react": "^0.15.6",
|
|
33
34
|
"@react-spring/three": "^10.0.3",
|
|
34
35
|
"@react-three/drei": "^10.0.6",
|
|
35
36
|
"@react-three/fiber": "^9.1.2",
|
|
@@ -49,6 +49,24 @@ export interface SelectionStyle {
|
|
|
49
49
|
borderWidth?: number;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Per-frame camera callback signature. Fires once per R3F render frame from
|
|
54
|
+
* inside the Canvas, so projection done in the callback is in lockstep with
|
|
55
|
+
* the city render — useful for HTML/SVG overlays that need to track world
|
|
56
|
+
* positions (e.g. leader lines anchored to buildings).
|
|
57
|
+
*
|
|
58
|
+
* The callback receives the live `THREE.Camera` and the current canvas
|
|
59
|
+
* size in CSS pixels. To project a world point to canvas-local pixels:
|
|
60
|
+
*
|
|
61
|
+
* const v = new THREE.Vector3(x, y, z).project(camera);
|
|
62
|
+
* const px = (v.x * 0.5 + 0.5) * size.width;
|
|
63
|
+
* const py = (v.y * -0.5 + 0.5) * size.height;
|
|
64
|
+
*/
|
|
65
|
+
export type OnCameraFrame = (
|
|
66
|
+
camera: THREE.Camera,
|
|
67
|
+
size: { width: number; height: number },
|
|
68
|
+
) => void;
|
|
69
|
+
|
|
52
70
|
/** What to do with non-highlighted buildings */
|
|
53
71
|
export type IsolationMode =
|
|
54
72
|
| 'none' // Show all buildings normally
|
|
@@ -718,7 +736,7 @@ function BorderHighlights({
|
|
|
718
736
|
return (
|
|
719
737
|
<instancedMesh ref={meshRef} args={[undefined, undefined, borderEdgeData.length]} frustumCulled={false}>
|
|
720
738
|
<boxGeometry args={[1, 1, 1]} />
|
|
721
|
-
<meshBasicMaterial transparent opacity={0.9}
|
|
739
|
+
<meshBasicMaterial transparent opacity={0.9} />
|
|
722
740
|
</instancedMesh>
|
|
723
741
|
);
|
|
724
742
|
}
|
|
@@ -743,6 +761,7 @@ interface InstancedBuildingsProps {
|
|
|
743
761
|
focusDirectory: string | null;
|
|
744
762
|
highlightLayers: HighlightLayer[];
|
|
745
763
|
isolationMode: IsolationMode;
|
|
764
|
+
defaultBuildingColor?: string;
|
|
746
765
|
}
|
|
747
766
|
|
|
748
767
|
// Helper to check if a path is inside a directory
|
|
@@ -767,6 +786,7 @@ function InstancedBuildings({
|
|
|
767
786
|
focusDirectory,
|
|
768
787
|
highlightLayers,
|
|
769
788
|
isolationMode,
|
|
789
|
+
defaultBuildingColor,
|
|
770
790
|
}: InstancedBuildingsProps) {
|
|
771
791
|
const meshRef = useRef<THREE.InstancedMesh>(null);
|
|
772
792
|
const startTimeRef = useRef<number | null>(null);
|
|
@@ -852,7 +872,7 @@ function InstancedBuildings({
|
|
|
852
872
|
// Get all layer matches and find first fill match for building color
|
|
853
873
|
const matches = getLayerMatchesForPath(building.path, highlightLayers);
|
|
854
874
|
const fillMatch = matches.find(m => m.renderStrategy === 'fill');
|
|
855
|
-
const color = fillMatch?.color ?? getColorForFile(building);
|
|
875
|
+
const color = fillMatch?.color ?? defaultBuildingColor ?? getColorForFile(building);
|
|
856
876
|
|
|
857
877
|
const x = building.position.x - centerOffset.x;
|
|
858
878
|
const z = building.position.z - centerOffset.z;
|
|
@@ -881,6 +901,7 @@ function InstancedBuildings({
|
|
|
881
901
|
staggerIndices,
|
|
882
902
|
animationConfig.staggerDelay,
|
|
883
903
|
highlightLayers,
|
|
904
|
+
defaultBuildingColor,
|
|
884
905
|
]);
|
|
885
906
|
|
|
886
907
|
const minHeight = 0.3;
|
|
@@ -1432,6 +1453,7 @@ function touchTwoAction(action: TouchTwoAction): number | undefined {
|
|
|
1432
1453
|
interface CameraApi {
|
|
1433
1454
|
reset: () => void;
|
|
1434
1455
|
moveTo: (x: number, z: number, size?: number) => void;
|
|
1456
|
+
setFlatView: (x: number, z: number, height: number, options?: RotateOptions) => void;
|
|
1435
1457
|
setTarget: (x: number, y: number, z: number, options?: RotateOptions) => void;
|
|
1436
1458
|
rotateTo: (angleOrDirection: number | 'north' | 'south' | 'east' | 'west', options?: RotateOptions) => void;
|
|
1437
1459
|
rotateBy: (degrees: number, options?: RotateOptions) => void;
|
|
@@ -1453,6 +1475,21 @@ export function moveCameraTo(x: number, z: number, size?: number) {
|
|
|
1453
1475
|
cameraApi?.moveTo(x, z, size);
|
|
1454
1476
|
}
|
|
1455
1477
|
|
|
1478
|
+
/**
|
|
1479
|
+
* Position the camera straight overhead a target at a specific height (flat
|
|
1480
|
+
* top-down view). Use to fit the city inside a sub-rect of the canvas — e.g.
|
|
1481
|
+
* when overlays cover part of the viewport. Larger `height` = city appears
|
|
1482
|
+
* smaller; smaller `height` = zoomed in.
|
|
1483
|
+
*/
|
|
1484
|
+
export function setCameraFlatView(
|
|
1485
|
+
x: number,
|
|
1486
|
+
z: number,
|
|
1487
|
+
height: number,
|
|
1488
|
+
options?: RotateOptions,
|
|
1489
|
+
) {
|
|
1490
|
+
cameraApi?.setFlatView(x, z, height, options);
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1456
1493
|
/**
|
|
1457
1494
|
* Set the camera's look-at target (center point for orbiting).
|
|
1458
1495
|
* Camera maintains its current distance and angles relative to the new target.
|
|
@@ -1537,6 +1574,19 @@ export function getCameraTilt() {
|
|
|
1537
1574
|
return cameraApi?.getCurrentTilt() ?? null;
|
|
1538
1575
|
}
|
|
1539
1576
|
|
|
1577
|
+
/**
|
|
1578
|
+
* Bridge for piping the live camera + canvas size out of the R3F Canvas on
|
|
1579
|
+
* every frame. Mounted as a child of `<Canvas>` so it has access to the R3F
|
|
1580
|
+
* render loop; runs zero work if no callback is provided.
|
|
1581
|
+
*/
|
|
1582
|
+
function CameraFrameBridge({ onCameraFrame }: { onCameraFrame?: OnCameraFrame }) {
|
|
1583
|
+
const { camera, size } = useThree();
|
|
1584
|
+
useFrame(() => {
|
|
1585
|
+
onCameraFrame?.(camera, { width: size.width, height: size.height });
|
|
1586
|
+
});
|
|
1587
|
+
return null;
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1540
1590
|
const AnimatedCamera = React.memo(function AnimatedCamera({
|
|
1541
1591
|
citySize,
|
|
1542
1592
|
isFlat,
|
|
@@ -1934,6 +1984,40 @@ const AnimatedCamera = React.memo(function AnimatedCamera({
|
|
|
1934
1984
|
});
|
|
1935
1985
|
}, [citySize, api]);
|
|
1936
1986
|
|
|
1987
|
+
// Position the camera directly above a target at a given height (flat /
|
|
1988
|
+
// top-down view). Useful for fitting the city into a visible sub-rect of
|
|
1989
|
+
// the canvas — raise `height` to make the city appear smaller, lower it to
|
|
1990
|
+
// zoom in. Pairs with `setTarget` for pan-only changes.
|
|
1991
|
+
const setFlatView = useCallback(
|
|
1992
|
+
(x: number, z: number, height: number, options?: RotateOptions) => {
|
|
1993
|
+
// MapControls (drag/wheel) mutates camera.position and controls.target
|
|
1994
|
+
// directly without touching our spring. If we don't resync first, a
|
|
1995
|
+
// call here can no-op when the spring's stored values happen to equal
|
|
1996
|
+
// the requested target even though the visible camera is elsewhere.
|
|
1997
|
+
if (controlsRef.current) {
|
|
1998
|
+
api.set({
|
|
1999
|
+
camX: camera.position.x,
|
|
2000
|
+
camY: camera.position.y,
|
|
2001
|
+
camZ: camera.position.z,
|
|
2002
|
+
lookX: controlsRef.current.target.x,
|
|
2003
|
+
lookY: controlsRef.current.target.y,
|
|
2004
|
+
lookZ: controlsRef.current.target.z,
|
|
2005
|
+
});
|
|
2006
|
+
}
|
|
2007
|
+
const config = options?.duration ? { duration: options.duration } : undefined;
|
|
2008
|
+
api.start({
|
|
2009
|
+
camX: x,
|
|
2010
|
+
camY: height,
|
|
2011
|
+
camZ: z + 0.001, // tiny offset to avoid gimbal lock when looking straight down
|
|
2012
|
+
lookX: x,
|
|
2013
|
+
lookY: 0,
|
|
2014
|
+
lookZ: z,
|
|
2015
|
+
...(config ? { config } : {}),
|
|
2016
|
+
});
|
|
2017
|
+
},
|
|
2018
|
+
[api, camera],
|
|
2019
|
+
);
|
|
2020
|
+
|
|
1937
2021
|
// Set camera target (look-at point), maintaining current distance and angles
|
|
1938
2022
|
const setTarget = useCallback((x: number, y: number, z: number, options?: RotateOptions) => {
|
|
1939
2023
|
// Get current offset from target
|
|
@@ -2167,6 +2251,7 @@ const AnimatedCamera = React.memo(function AnimatedCamera({
|
|
|
2167
2251
|
cameraApi = {
|
|
2168
2252
|
reset: resetToInitial,
|
|
2169
2253
|
moveTo,
|
|
2254
|
+
setFlatView,
|
|
2170
2255
|
setTarget,
|
|
2171
2256
|
rotateTo,
|
|
2172
2257
|
rotateBy,
|
|
@@ -2180,7 +2265,7 @@ const AnimatedCamera = React.memo(function AnimatedCamera({
|
|
|
2180
2265
|
return () => {
|
|
2181
2266
|
cameraApi = null;
|
|
2182
2267
|
};
|
|
2183
|
-
}, [resetToInitial, moveTo, setTarget, rotateTo, rotateBy, tiltTo, tiltBy, getCurrentPosition, getCurrentTarget, getCurrentAngle, getCurrentTilt]);
|
|
2268
|
+
}, [resetToInitial, moveTo, setFlatView, setTarget, rotateTo, rotateBy, tiltTo, tiltBy, getCurrentPosition, getCurrentTarget, getCurrentAngle, getCurrentTilt]);
|
|
2184
2269
|
|
|
2185
2270
|
// Custom wheel handler for wheel === 'pan'. We disable MapControls' built-in
|
|
2186
2271
|
// zoom (otherwise it competes with our handler) and handle both axes here:
|
|
@@ -2698,6 +2783,7 @@ interface CitySceneProps {
|
|
|
2698
2783
|
dismissingPanelIds?: ReadonlySet<string>;
|
|
2699
2784
|
onPanelDismissed?: (id: string) => void;
|
|
2700
2785
|
cameraControls?: CameraControlsConfig;
|
|
2786
|
+
defaultBuildingColor?: string;
|
|
2701
2787
|
}
|
|
2702
2788
|
|
|
2703
2789
|
function CityScene({
|
|
@@ -2722,6 +2808,7 @@ function CityScene({
|
|
|
2722
2808
|
dismissingPanelIds,
|
|
2723
2809
|
onPanelDismissed,
|
|
2724
2810
|
cameraControls,
|
|
2811
|
+
defaultBuildingColor,
|
|
2725
2812
|
onCameraReady,
|
|
2726
2813
|
}: CitySceneProps & { onCameraReady?: () => void }) {
|
|
2727
2814
|
const centerOffset = useMemo(
|
|
@@ -3028,6 +3115,7 @@ function CityScene({
|
|
|
3028
3115
|
focusDirectory={buildingFocusDirectory}
|
|
3029
3116
|
highlightLayers={highlightLayers}
|
|
3030
3117
|
isolationMode={isolationMode}
|
|
3118
|
+
defaultBuildingColor={defaultBuildingColor}
|
|
3031
3119
|
/>
|
|
3032
3120
|
|
|
3033
3121
|
<BuildingIcons
|
|
@@ -3144,6 +3232,15 @@ export interface FileCity3DProps {
|
|
|
3144
3232
|
/** Base file type color layers (resolved with highlightLayers) */
|
|
3145
3233
|
fileColorLayers?: HighlightLayer[];
|
|
3146
3234
|
|
|
3235
|
+
/**
|
|
3236
|
+
* Override the per-building color fallback. When unset (default), buildings
|
|
3237
|
+
* not matched by a fill highlight layer are colored by file extension via
|
|
3238
|
+
* the built-in file-type palette. Set to a CSS color (e.g. `'#475569'`) to
|
|
3239
|
+
* render unmatched buildings in a neutral tone — useful for debug stories
|
|
3240
|
+
* that want to isolate highlight-layer rendering.
|
|
3241
|
+
*/
|
|
3242
|
+
defaultBuildingColor?: string;
|
|
3243
|
+
|
|
3147
3244
|
/**
|
|
3148
3245
|
* Translucent slabs rendered above the city showing scope coverage as
|
|
3149
3246
|
* elevated planes over the directories they own.
|
|
@@ -3173,6 +3270,15 @@ export interface FileCity3DProps {
|
|
|
3173
3270
|
* Memoize this object to avoid unnecessary camera re-mounts.
|
|
3174
3271
|
*/
|
|
3175
3272
|
cameraControls?: CameraControlsConfig;
|
|
3273
|
+
|
|
3274
|
+
/**
|
|
3275
|
+
* Fires once per R3F render frame with the live camera and canvas size.
|
|
3276
|
+
* Use to project world points to canvas pixels for HTML/SVG overlays that
|
|
3277
|
+
* need to track buildings as the camera pans / zooms / rotates.
|
|
3278
|
+
*
|
|
3279
|
+
* Memoize the callback to avoid re-mounting the bridge.
|
|
3280
|
+
*/
|
|
3281
|
+
onCameraFrame?: OnCameraFrame;
|
|
3176
3282
|
}
|
|
3177
3283
|
|
|
3178
3284
|
/**
|
|
@@ -3215,7 +3321,9 @@ export function FileCity3D({
|
|
|
3215
3321
|
selectionStyle,
|
|
3216
3322
|
adaptCameraToBuildings = false,
|
|
3217
3323
|
fileColorLayers,
|
|
3324
|
+
defaultBuildingColor,
|
|
3218
3325
|
cameraControls,
|
|
3326
|
+
onCameraFrame,
|
|
3219
3327
|
}: FileCity3DProps) {
|
|
3220
3328
|
const [hoveredBuilding, setHoveredBuilding] = useState<CityBuilding | null>(null);
|
|
3221
3329
|
const [internalIsGrown, setInternalIsGrown] = useState(false);
|
|
@@ -3402,8 +3510,10 @@ export function FileCity3D({
|
|
|
3402
3510
|
dismissingPanelIds={dismissingPanelIds}
|
|
3403
3511
|
onPanelDismissed={onPanelDismissed}
|
|
3404
3512
|
cameraControls={cameraControls}
|
|
3513
|
+
defaultBuildingColor={defaultBuildingColor}
|
|
3405
3514
|
onCameraReady={() => setCameraReady(true)}
|
|
3406
3515
|
/>
|
|
3516
|
+
{onCameraFrame && <CameraFrameBridge onCameraFrame={onCameraFrame} />}
|
|
3407
3517
|
</Canvas>
|
|
3408
3518
|
<InfoPanel building={resolvedSelection.building} />
|
|
3409
3519
|
{showControls && (
|
|
@@ -6,6 +6,7 @@ export {
|
|
|
6
6
|
FileCity3D,
|
|
7
7
|
resetCamera,
|
|
8
8
|
getCameraAngle,
|
|
9
|
+
getCameraPosition,
|
|
9
10
|
getCameraTarget,
|
|
10
11
|
getCameraTilt,
|
|
11
12
|
rotateCameraTo,
|
|
@@ -13,6 +14,7 @@ export {
|
|
|
13
14
|
tiltCameraTo,
|
|
14
15
|
tiltCameraBy,
|
|
15
16
|
moveCameraTo,
|
|
17
|
+
setCameraFlatView,
|
|
16
18
|
setCameraTarget,
|
|
17
19
|
DEFAULT_FLAT_PATTERNS,
|
|
18
20
|
DEFAULT_CAMERA_CONTROLS,
|
|
@@ -36,4 +38,5 @@ export type {
|
|
|
36
38
|
TouchOneAction,
|
|
37
39
|
TouchTwoAction,
|
|
38
40
|
WheelAction,
|
|
41
|
+
OnCameraFrame,
|
|
39
42
|
} from './FileCity3D';
|