11"use client" ;
2+
23import * as THREE from "three" ;
3- import { Canvas , useFrame , useThree } from "@react-three/fiber" ;
4+ import { Canvas , useFrame } from "@react-three/fiber" ;
45import { OrbitControls , Stars , useTexture } from "@react-three/drei" ;
56import { useMemo , useRef } from "react" ;
67
78/** Props mínimas que ya usabas */
89export type Center = { lat : number ; lon : number } ;
910export type RingSet = { p ?: number [ ] ; s ?: number [ ] } ; // kilómetros (puede venir vacío)
1011
11- type GlobeProps = {
12- center : Center ;
13- rings ?: RingSet | null ;
14- } ;
12+ type GlobeProps = { center ?: Center | null ; rings ?: RingSet | null } ;
1513
1614/* ------------------------- utilidades geo ------------------------- */
1715const d2r = ( d : number ) => ( d * Math . PI ) / 180 ;
@@ -50,8 +48,7 @@ function destinationPoint(lat: number, lon: number, bearingDeg: number, angDistR
5048/* --------------------------- materiales -------------------------- */
5149
5250function Atmosphere ( ) {
53- // shader muy simple de “rim light” para la atmósfera
54- const materialRef = useRef < THREE . ShaderMaterial > ( null ! ) ;
51+ // shader muy simple de “rim light”
5552 const vertexShader = /* glsl */ `
5653 varying vec3 vNormal;
5754 void main() {
@@ -70,7 +67,6 @@ function Atmosphere() {
7067 < mesh scale = { 1.018 } >
7168 < sphereGeometry args = { [ 1 , 64 , 64 ] } />
7269 < shaderMaterial
73- ref = { materialRef }
7470 vertexShader = { vertexShader }
7571 fragmentShader = { fragmentShader }
7672 blending = { THREE . AdditiveBlending }
@@ -87,21 +83,18 @@ function Graticule({ color = "#2dd4bf", alpha = 0.25 }) {
8783 const lines = useMemo ( ( ) => {
8884 const group : JSX . Element [ ] = [ ] ;
8985 const mat = new THREE . LineBasicMaterial ( { color, transparent : true , opacity : alpha } ) ;
86+
9087 // paralelos cada 30°
9188 for ( let lat = - 60 ; lat <= 60 ; lat += 30 ) {
9289 const pts : THREE . Vector3 [ ] = [ ] ;
93- for ( let lon = - 180 ; lon <= 180 ; lon += 3 ) {
94- pts . push ( latLonToVector3 ( 1.001 , lat , lon ) ) ;
95- }
90+ for ( let lon = - 180 ; lon <= 180 ; lon += 3 ) pts . push ( latLonToVector3 ( 1.001 , lat , lon ) ) ;
9691 const geo = new THREE . BufferGeometry ( ) . setFromPoints ( pts ) ;
9792 group . push ( < line key = { `par-${ lat } ` } geometry = { geo } material = { mat } /> ) ;
9893 }
9994 // meridianos cada 30°
10095 for ( let lon = - 150 ; lon <= 180 ; lon += 30 ) {
10196 const pts : THREE . Vector3 [ ] = [ ] ;
102- for ( let lat = - 90 ; lat <= 90 ; lat += 3 ) {
103- pts . push ( latLonToVector3 ( 1.001 , lat , lon ) ) ;
104- }
97+ for ( let lat = - 90 ; lat <= 90 ; lat += 3 ) pts . push ( latLonToVector3 ( 1.001 , lat , lon ) ) ;
10598 const geo = new THREE . BufferGeometry ( ) . setFromPoints ( pts ) ;
10699 group . push ( < line key = { `mer-${ lon } ` } geometry = { geo } material = { mat } /> ) ;
107100 }
@@ -143,7 +136,7 @@ function Stand() {
143136/* ----------------------------- Tierra ----------------------------- */
144137
145138function EarthBall ( ) {
146- // Carga de texturas (las opcionales no son obligatorias)
139+ // Carga de texturas desde /public/textures
147140 const [ colorMap , normalMap , specMap ] = useTexture (
148141 [
149142 "/textures/earth_political_4k.jpg" ,
@@ -153,17 +146,18 @@ function EarthBall() {
153146 ( txs ) => txs . forEach ( ( t ) => ( t . anisotropy = 8 ) )
154147 ) ;
155148
149+ // El mapa "specular" es para Phong, no para Standard.
150+ const hasNormal = ( normalMap as any ) ?. image ;
151+ const hasSpec = ( specMap as any ) ?. image ;
152+
156153 return (
157154 < mesh >
158155 < sphereGeometry args = { [ 1 , 128 , 128 ] } />
159- < meshStandardMaterial
160- map = { colorMap }
161- normalMap = { ( normalMap as any ) ?. image ? ( normalMap as THREE . Texture ) : undefined }
162- metalnessMap = { ( specMap as any ) ?. image ? ( specMap as THREE . Texture ) : undefined }
163- roughnessMap = { ( specMap as any ) ?. image ? ( specMap as THREE . Texture ) : undefined }
164- metalness = { 0.1 }
165- roughness = { 0.9 }
166- envMapIntensity = { 0.7 }
156+ < meshPhongMaterial
157+ map = { colorMap as THREE . Texture }
158+ normalMap = { hasNormal ? ( normalMap as THREE . Texture ) : undefined }
159+ specularMap = { hasSpec ? ( specMap as THREE . Texture ) : undefined }
160+ shininess = { 12 }
167161 />
168162 </ mesh >
169163 ) ;
@@ -210,41 +204,46 @@ function RingCircle({
210204 const geo = useMemo ( ( ) => new THREE . BufferGeometry ( ) . setFromPoints ( points ) , [ points ] ) ;
211205 return (
212206 < line geometry = { geo } >
213- < lineBasicMaterial color = { color } linewidth = { 2 } />
207+ < lineBasicMaterial color = { color } />
214208 </ line >
215209 ) ;
216210}
217211
218- /* ---------------------------- Controls --------- ------------------- */
212+ /* ------------------- World: vive DENTRO del Canvas ------------------- */
219213
220- function Controls ( ) {
221- const { camera } = useThree ( ) ;
222- // ligera inclinación estilo “eje terrestre”
223- const group = useRef < THREE . Group > ( null ! ) ;
224- useFrame ( ( ) => {
225- if ( ! group . current ) return ;
226- group . current . rotation . z = d2r ( 23.4 ) ;
214+ function World ( { center, rings } : { center : Center ; rings ?: RingSet | null } ) {
215+ const worldRef = useRef < THREE . Group > ( null ! ) ;
216+
217+ // Animación: tilt + rotación suave
218+ useFrame ( ( _ , delta ) => {
219+ if ( ! worldRef . current ) return ;
220+ worldRef . current . rotation . z = d2r ( 23.4 ) ;
221+ worldRef . current . rotation . y += delta * 0.05 ;
227222 } ) ;
223+
228224 return (
229- < >
230- < group ref = { group } />
231- < OrbitControls
232- enableDamping
233- dampingFactor = { 0.08 }
234- minDistance = { 1.6 }
235- maxDistance = { 6 }
236- rotateSpeed = { 0.8 }
237- zoomSpeed = { 0.9 }
238- enablePan = { false }
239- target = { [ 0 , 0 , 0 ] }
240- />
241- </ >
225+ < group ref = { worldRef } >
226+ < EarthBall />
227+ < Atmosphere />
228+ < Graticule />
229+ < EquatorNeon />
230+ < Pin lat = { center . lat } lon = { center . lon } />
231+ { rings ?. p ?. map ( ( r , i ) => (
232+ < RingCircle key = { `p- ${ i } ` } center = { center } radiusKm = { r } color = "#7cf9f1" />
233+ ) ) }
234+ { rings ?. s ?. map ( ( r , i ) => (
235+ < RingCircle key = { `s- ${ i } ` } center = { center } radiusKm = { r } color = "#ff8ec9" />
236+ ) ) }
237+ </ group >
242238 ) ;
243239}
244240
245241/* ============================ GLOBE ============================ */
246242
247243export default function Globe ( { center, rings } : GlobeProps ) {
244+ // 🔒 Si aún no hay center (hidratación/primer render), no dibujes nada
245+ if ( ! center ) return null ;
246+
248247 return (
249248 < Canvas
250249 camera = { { position : [ 0 , 0 , 3.1 ] , fov : 42 } }
@@ -258,28 +257,24 @@ export default function Globe({ center, rings }: GlobeProps) {
258257 < pointLight position = { [ - 4 , - 3 , - 4 ] } intensity = { 0.5 } />
259258
260259 { /* Estrellas sutiles */ }
261- < Stars radius = { 80 } depth = { 35 } count = { 2000 } factor = { 2 } fade speed = { 0.2 } />
262-
263- { /* Globo */ }
264- < group >
265- < EarthBall />
266- < Atmosphere />
267- < Graticule />
268- < EquatorNeon />
269- < Pin lat = { center . lat } lon = { center . lon } />
270- { /* Anillos P/S si existen */ }
271- { rings ?. p ?. map ( ( r , i ) => (
272- < RingCircle key = { `p-${ i } ` } center = { center } radiusKm = { r } color = "#7cf9f1" />
273- ) ) }
274- { rings ?. s ?. map ( ( r , i ) => (
275- < RingCircle key = { `s-${ i } ` } center = { center } radiusKm = { r } color = "#ff8ec9" />
276- ) ) }
277- </ group >
260+ < Stars radius = { 80 } depth = { 35 } count = { 1600 } factor = { 2 } fade speed = { 0.2 } />
261+
262+ { /* Globo + elementos */ }
263+ < World center = { center } rings = { rings } />
278264
279265 { /* Soporte / base (decorativo) */ }
280266 < Stand />
281267
282- < Controls />
268+ < OrbitControls
269+ enableDamping
270+ dampingFactor = { 0.08 }
271+ minDistance = { 1.6 }
272+ maxDistance = { 6 }
273+ rotateSpeed = { 0.8 }
274+ zoomSpeed = { 0.9 }
275+ enablePan = { false }
276+ target = { [ 0 , 0 , 0 ] }
277+ />
283278 </ Canvas >
284279 ) ;
285280}
0 commit comments