From d3058592d1147bf1527826a3725e05ee34181f1e Mon Sep 17 00:00:00 2001 From: Alvaro Date: Thu, 3 Oct 2024 14:10:36 +0200 Subject: [PATCH] Allow zoom on click on markers --- src/components/globe/camera.tsx | 24 ++++++++++++--- src/components/globe/globe.tsx | 10 ++++-- src/components/globe/index.tsx | 36 ++++++++++++---------- src/components/globe/marker.tsx | 52 ++++++++++++++++++++++++++++++++ src/components/globe/markers.tsx | 24 --------------- src/lib/globe-utils.ts | 15 +++++++++ 6 files changed, 115 insertions(+), 46 deletions(-) create mode 100644 src/components/globe/marker.tsx delete mode 100644 src/components/globe/markers.tsx diff --git a/src/components/globe/camera.tsx b/src/components/globe/camera.tsx index 652c026..27dc9d6 100644 --- a/src/components/globe/camera.tsx +++ b/src/components/globe/camera.tsx @@ -1,14 +1,30 @@ +import { useState } from "react"; import { CameraControls } from "@react-three/drei"; -import { useThree } from "@react-three/fiber" +import { useThree, useFrame } from "@react-three/fiber" import { useRef } from "react"; +import { convertLatLonToGlobalPosition } from "@/lib/globe-utils"; +import type { MarkerType } from "./marker"; -export const Camera = () => { +export const Camera = ({ marker }: { + marker: MarkerType | undefined +}) => { const cameraControlsRef = useRef(null!); + const [currentMarker, setCurrentMarker] = useState(null); + const [resetControls, setResetControls] = useState(true); useThree(({ controls }) => { if (controls) { - cameraControlsRef.current.setPosition(0, 1, 4); - cameraControlsRef.current.setTarget(0, 0, 0); + if (marker !== undefined && currentMarker !== marker.id) { + console.log('set', ...convertLatLonToGlobalPosition(marker.lat, marker.lng, 2), cameraControlsRef.current.camera) + cameraControlsRef.current.setPosition(...convertLatLonToGlobalPosition(marker.lat, marker.lng, 2), true); + setCurrentMarker(marker.id); + } + + if (resetControls) { + cameraControlsRef.current.setPosition(0, 1, 4); + cameraControlsRef.current.setTarget(0, 0, 0); + setResetControls(false); + } } }); diff --git a/src/components/globe/globe.tsx b/src/components/globe/globe.tsx index c019efa..beccc46 100644 --- a/src/components/globe/globe.tsx +++ b/src/components/globe/globe.tsx @@ -1,6 +1,7 @@ import { useTexture, useVideoTexture } from "@react-three/drei"; import { Suspense, useRef } from "react"; import { Mesh } from "three"; +import { useFrame } from '@react-three/fiber'; function VideoMaterial({ url }: { url: string }) { const texture = useVideoTexture(url) @@ -12,14 +13,19 @@ function FallbackMaterial({ url }: { url: string }) { return } -export const Globe = () => { +export const Globe = ({ rotate = false }) => { const sphereMeshRef = useRef(null!); + useFrame(() => { + if (rotate && sphereMeshRef.current) { + sphereMeshRef.current.rotation.y += 0.01; + } + }); + return ( }> diff --git a/src/components/globe/index.tsx b/src/components/globe/index.tsx index bade125..fef9282 100644 --- a/src/components/globe/index.tsx +++ b/src/components/globe/index.tsx @@ -1,21 +1,24 @@ 'use client'; - -import { Canvas } from '@react-three/fiber' +import { useState } from 'react'; +import { Canvas, useFrame } from '@react-three/fiber' import { Camera } from './camera'; import { Globe } from './globe'; -import { Marker } from './markers'; +import Marker from './marker'; +import type { MarkerType } from './marker'; -const markers = [ - { label: "Paris", lat: 48.8575, lng: 2.3514 }, - { label: "Los Angeles", lat: 34.0522, lng: -118.2437 }, - { label: "Tokio", lat: 35.6895, lng: 139.6917 }, - { label: "New York", lat: 40.7128, lng: -74.0060 }, - { label: "São Paulo", lat: -23.5505, lng: -46.6333 }, - { label: "Sydney", lat: -33.8688, lng: 151.2093 }, - { label: "Cape Town", lat: -33.9249, lng: 18.4241 }, +const markers: MarkerType[] = [ + { id: "Paris", lat: 48.8575, lng: 2.3514 }, + { id: "Los Angeles", lat: 34.0522, lng: -118.2437 }, + { id: "Tokio", lat: 35.6895, lng: 139.6917 }, + { id: "New York", lat: 40.7128, lng: -74.0060 }, + { id: "São Paulo", lat: -23.5505, lng: -46.6333 }, + { id: "Sydney", lat: -33.8688, lng: 151.2093 }, + { id: "Cape Town", lat: -33.9249, lng: 18.4241 }, ]; export default function Globe3d() { + const [selectedMarker, setSelectedMarker] = useState(null); + const marker = selectedMarker ? markers.find(m => m.id === selectedMarker) : undefined; return ( <>
- + - - + + {/* */} - {markers.map((marker, index) => ( - + + Click to explore the phenomenon + ))}
diff --git a/src/components/globe/marker.tsx b/src/components/globe/marker.tsx new file mode 100644 index 0000000..d330b22 --- /dev/null +++ b/src/components/globe/marker.tsx @@ -0,0 +1,52 @@ +import { Group, Vector3 } from 'three' +import { useState, useRef } from 'react' +import { useFrame } from '@react-three/fiber' +import { Html } from '@react-three/drei' +import { convertLatLonToVec3 } from "@/lib/globe-utils"; + +function Marker({ id, lat, lng, children, setSelectedMarker, ...props }: { + id: string + lat: number + lng: number + setSelectedMarker: (id: string) => void, + children: React.ReactNode +}) { + const ref = useRef(null) + // This holds the local occluded state + const [isOccluded, setOccluded] = useState() + const [isInRange, setInRange] = useState() + const isVisible = isInRange && !isOccluded + // Test distance + const vec = new Vector3() + useFrame((state) => { + if (!ref.current) return + const range = state.camera.position.distanceTo(ref.current.getWorldPosition(vec)) <= 10 + if (range !== isInRange) setInRange(range) + }) + return ( + + +
setSelectedMarker(id)} + >
+ {!!children &&
{children}
} + +
+ ) +} + +export type MarkerType = { + id: string + lat: number + lng: number +}; + +export default Marker; \ No newline at end of file diff --git a/src/components/globe/markers.tsx b/src/components/globe/markers.tsx deleted file mode 100644 index cecd938..0000000 --- a/src/components/globe/markers.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { useRef } from "react"; -import { Mesh } from "three"; -import { convertLatLonToVec3 } from "@/lib/globe-utils"; - -export interface MarkerProps { - lat: number; - lng: number; -} - -export const Marker = ({ lat, lng }: MarkerProps) => { - const sphereMeshRef = useRef(null!); - - console.log(convertLatLonToVec3(lat, lng, 1)); - - return ( - - - - - ) -} \ No newline at end of file diff --git a/src/lib/globe-utils.ts b/src/lib/globe-utils.ts index f68b2ce..2f52644 100644 --- a/src/lib/globe-utils.ts +++ b/src/lib/globe-utils.ts @@ -10,3 +10,18 @@ export const convertLatLonToVec3 = (lat: number, lon: number, radius: number) => return new Vector3(x, y, z); }; + +export const convertLatLonToGlobalPosition = ( + lat: number, + lon: number, + radius: number, +): [number, number, number] => { + const phi = (90 - lat) * (Math.PI / 180); // Convert latitude to radians + const theta = (lon + 180) * (Math.PI / 180); // Convert longitude to radians + + const x = -(radius * Math.sin(phi) * Math.cos(theta)); + const y = radius * Math.cos(phi); + const z = radius * Math.sin(phi) * Math.sin(theta); + + return [x, y, z]; +};