import { useEffect, useState } from "react"; import { Canvas, extend, ReactThreeFiber, useThree } from "@react-three/fiber"; import { useAreaStore } from "@/state/areaStore"; import { Html, Sky, Environment, Line } from "@react-three/drei"; import * as THREE from "three"; import { useActionStore } from "@/state/exportStore"; import { GLTFExporter } from "three/examples/jsm/Addons.js"; import Car from "./Car"; import instanceFleet from "@/api/axios"; function Building({ shape, extrudeSettings, tags }) { const [hovered, setHovered] = useState(false); const [clicked, setClicked] = useState(false); const [hoverPos, setHoverPos] = (useState < THREE.Vector3) | (null > null); return ( { setHovered(true); e.stopPropagation(); }} onPointerOut={(e) => { setHovered(false); e.stopPropagation(); }} onPointerMove={(e) => { setHoverPos(e.point.clone()); e.stopPropagation(); }} onClick={(e) => { setClicked(!clicked); e.stopPropagation(); }} rotation={[-Math.PI / 2, 0, 0]} > {(hovered || clicked) && hoverPos && (
{JSON.stringify(tags)}
)}
); } function Roads({ area }) { const [roads, setRoads] = useState([]); if (!area || area.length < 2) return null; const refLat = (area[1].lat + area[0].lat) / 2; const refLng = (area[1].lng + area[0].lng) / 2; const scale = 51000; function project(lat, lng) { const x = (lng - refLng) * scale; const y = (lat - refLat) * scale; return new THREE.Vector2(x, y); } useEffect(() => { const south = area[1].lat; const west = area[1].lng; const north = area[0].lat; const east = area[0].lng; const query = `[out:json][timeout:25];(way["highway"](${south},${west},${north},${east}););out body geom;`; fetch("https://overpass-api.de/api/interpreter", { method: "POST", body: query, headers: { "Content-Type": "application/x-www-form-urlencoded" }, }) .then((response) => response.json()) .then((data) => { setRoads(data.elements); }) .catch((err) => console.error(err)); }, [area]); return ( <> {roads.map((road, index) => { if (!road.geometry || road.geometry.length < 2) return null; const points = road.geometry.map((pt) => { const v = project(pt.lat, pt.lon); return new THREE.Vector3(v.x, 0.1, -v.y); }); const lineGeometry = new THREE.BufferGeometry().setFromPoints(points); return ( ); })} ); } export function Export() { const { scene } = useThree(); const action = useActionStore((state) => state.action); const fleetSpaceId = useActionStore((state) => state.fleetSpaceId); const exportType = useActionStore((state) => state.exportType); const setAction = useActionStore((state) => state.setAction); useEffect(() => { if (action === true) { setAction(false); exportGLB(); } }, [action, setAction, scene]); const uploadFleet = async (blob) => { const formData = new FormData(); formData.append("object", blob, "box3d.glb"); formData.append("title", "New Object"); formData.append("description", ""); formData.append("spaceId", fleetSpaceId); await instanceFleet.post("space/file/mesh", formData, { headers: { "Content-Type": "multipart/form-data", }, }); }; const exportGLB = () => { const sceneClone = scene.clone(true); sceneClone.traverse((child) => { if (child.userData && child.userData.skipExport === true) child.parent?.remove(child); if (child.isHtml === true) child.parent?.remove(child); }); const exporter = new GLTFExporter(); const options = { binary: true, embedImages: true }; exporter.parse( sceneClone, (result) => { if (result instanceof ArrayBuffer) { const blob = new Blob([result], { type: "model/gltf-binary" }); if (exportType == "glb") { const link = document.createElement("a"); link.style.display = "none"; document.body.appendChild(link); link.href = URL.createObjectURL(blob); link.download = "scene.glb"; link.click(); document.body.removeChild(link); } if (exportType == "fleet") { uploadFleet(blob); } } else { console.error("GLB export failed: unexpected result", result); } }, (error) => { console.error("An error occurred during export", error); }, options ); }; return null; } export function Space() { const areas = useAreaStore((state) => state.areas); const [realCenter, setRealCenter] = useState(); const center = useAreaStore((state) => state.center); const refLat = (center[1].lat + center[0].lat) / 2; const refLng = (center[1].lng + center[0].lng) / 2; const scale = 51000; function project(lat, lng) { const x = (lng - refLng) * scale; const y = (lat - refLat) * scale; return new THREE.Vector2(x, y); } useEffect(() => { setRealCenter(center); }, [areas]); const areaData = () => { const result = []; areas.forEach((bld) => { if (!bld.geometry || bld.geometry.length < 3) return; const shapePoints = bld.geometry.map((pt) => project(pt.lat, pt.lng)); if (!shapePoints[0].equals(shapePoints[shapePoints.length - 1])) shapePoints.push(shapePoints[0]); const shape = new THREE.Shape(shapePoints); let heightValue = parseFloat(bld.tags.height || ""); const heightLevels = parseFloat(bld.tags["building:levels"] || ""); if (isNaN(heightValue)) heightValue = 10; if (!isNaN(heightLevels)) heightValue = heightLevels * 2.2; const extrudeSettings = { steps: 1, depth: heightValue, bevelEnabled: false, }; result.push({ shape, extrudeSettings, tags: bld.tags }); }); return result; }; const buildingsData = areaData(); return ( {buildingsData.map((item, index) => ( ))} ); }