gendesign/frontend/src/components/site-finder/ptica/massing/MassingViewer.tsx
Light1YT 5779e512be feat(ptica): interactive 3D building-massing module (Three.js) [PR#5]
Port the prototype massing.js to a client-only React component (MassingScene)
behind a dynamic(ssr:false) wrapper (MassingViewer), wired into the future3d +
insolation drawers, replacing the СКОРО SoonCards.

Generative mass from parcel area (real geometry_suitability.area_ha) × КСИТ-цель
3.5 (labelled regulation-default · НСПД — honest, real max_far not in /analyze),
OrbitControls (orbit/zoom/auto-rotate), time-of-day sun with shadows (insolation
preview), live GFA/КСИТ-факт metrics (green ≤3.5 / amber over). Full renderer/
scene/RAF dispose on drawer close; graceful WebGL-unavailable fallback.

Adds three@0.184.0 + @types/three (package.json + package-lock.json in sync).
SSR-isolated (three imported only in MassingScene). TS strict, no any.
2026-06-20 16:27:36 +05:00

35 lines
1.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
/**
* MassingViewer — the only entry point the drawer imports for the 3D massing
* sandbox. Loads MassingScene (Three.js) client-side only via
* `dynamic(ssr:false)` so WebGL/Three.js never run on the server, with a dark
* skeleton while the chunk loads. The drawer gates its children behind
* `{open && ...}`, so the scene mounts on open and the MassingScene effect
* cleanup disposes the renderer/scene on close.
*/
import dynamic from "next/dynamic";
import styles from "@/app/site-finder/analysis/[cad]/ptica/ptica.module.css";
import type { MassingSceneProps } from "@/components/site-finder/ptica/massing/MassingScene";
const MassingScene = dynamic(
() => import("@/components/site-finder/ptica/massing/MassingScene"),
{
ssr: false,
loading: () => (
<div className={styles.massingViewport}>
<div className={styles.massingSkeleton} aria-hidden="true">
Загрузка 3D-сцены
</div>
</div>
),
},
);
export function MassingViewer(props: MassingSceneProps): React.JSX.Element {
return <MassingScene {...props} />;
}
export default MassingViewer;