1/* NADIR â utilidades geográficas: conversión lon/lat â esfera y decodificación 2 de las polilÃneas vectoriales (costas, fronteras). Depende solo de THREE. 3 Expone NADIR.geo. */ 4(function(){ 5"use strict"; 6 7const DEG = Math.PI/180; 8 9/* lon/lat -> vector. Coincide con el UV por defecto de THREE.SphereGeometry. */ 10function ll2v(lon, lat, r){ 11 r = r === undefined ? 1 : r; 12 const cl = Math.cos(lat*DEG); 13 return new THREE.Vector3(r*cl*Math.cos(lon*DEG), r*Math.sin(lat*DEG), -r*cl*Math.sin(lon*DEG)); 14} 15 16/* Inverso de ll2v: vector -> {lat, lon} en grados. Misma convención, de modo 17 que v2ll(ll2v(lon,lat,r)) devuelve el punto de partida. */ 18function v2ll(v){ 19 const n = v.clone().normalize(); 20 return { 21 lat: Math.asin(Math.max(-1, Math.min(1, n.y)))/DEG, 22 lon: Math.atan2(-n.z, n.x)/DEG 23 }; 24} 25 26/* Marco local en un punto de la esfera: N hacia arriba (radial), `north` y 27 `east` tangentes a la superficie. Lo usan la inclinación de la cámara 28 (nadir-orbit.js) y el rumbo de la ruta (nadir-anim.js): sin un marco común 29 no hay forma de decir "inclÃnate HACIA la dirección de viaje". */ 30function frame(v){ 31 const N = v.clone().normalize(); 32 const north = new THREE.Vector3(0,1,0).addScaledVector(N, -N.y); 33 if(north.lengthSq() < 1e-8) north.set(1,0,0); else north.normalize(); // sobre un polo 34 return {N, north, east: new THREE.Vector3().crossVectors(north, N)}; 35} 36 37/* ---------- polilÃneas vectoriales ---------- 38 Las fronteras pasaron a Int32 el 5 septiembre 2026: en Int16 la coordenada 39 tenÃa que ir a 1/100 de grado para que ±180° cupieran en ±32.767, y eso son 40 escalones de 1,1 km â más gruesos que un pÃxel del vÃdeo a altura de crucero. 41 A 1/10000 de grado son 11 m. Quien lo escribe es tools/build-borders.mjs. 42 HUBO UN decodeI16(). SobrevivÃa solo porque COAST_* seguÃa en el formato 43 antiguo, y se fue con las costas el 10 de septiembre de 2026. Estaba en el 44 objeto que se publica al final, asà que esto ES un cambio del contrato de 45 NADIR.geo â no tenÃa ningún consumidor. */ 46function decodeI32(b64){ 47 const bin = atob(b64), n = bin.length, bytes = new Uint8Array(n); 48 for(let i=0;i<n;i++) bytes[i] = bin.charCodeAt(i); 49 return new Int32Array(bytes.buffer); 50} 51/* Convierte las polilÃneas empaquetadas en una CINTA de ancho en pantalla: 4 52 vértices y 6 Ãndices por tramo. Aquà no se ensancha nada â solo se colocan 53 los dos extremos de cada tramo y se marca a qué lado y a qué punta va cada 54 vértice; el desplazamiento lo hace VERT_BORDER (nadir-shaders.js), porque 55 depende de la proyección de cada fotograma. 56 57 Devuelve la GEOMETRÃA, no una malla. El material y sus uniforms se montan en 58 nadir-globe.js, que es donde vive `U`: asà este archivo sigue dependiendo 59 solo de THREE y no del orden de carga de los shaders. 60 61 Antes esto era buildLines(), que devolvÃa un THREE.LineSegments. Se cambió el 62 5 septiembre 2026: `linewidth` no funciona en WebGL, asà que aquello eran 63 siempre lÃneas de un pÃxel, duras y sin suavizar, que en el vÃdeo hervÃan. 64 65 Dos detalles heredados que NO son opcionales: 66 · el corte del antimeridiano (`|dlon| > 180`), o Rusia cruzarÃa el mapa 67 entera de lado a lado por una lÃnea recta; 68 · la densificación a `step` grados, porque un tramo se interpola recto en 69 lon/lat y sobre la esfera eso es una cuerda: sin trocear, un trozo largo de 70 paralelo se meterÃa por debajo de la superficie. */ 71/* Recorre los tramos que hay que dibujar y, si se le da `emit`, los entrega uno 72 a uno. Existe para que CONTAR y RELLENAR usen exactamente el mismo recorrido: 73 son dos pasadas sobre los mismos datos y, escritas por separado, la primera 74 vez que alguien tocara una y no la otra el buffer saldrÃa corto y la mitad de 75 las fronteras desaparecerÃa sin ningún error. 76 77 Dos reglas que no son opcionales: 78 · el corte del antimeridiano (`|dlon| > 180`), o Rusia cruzarÃa el globo de 79 lado a lado por una recta; 80 · la densificación a `step` grados, porque el tramo se interpola recto en 81 lon/lat y sobre la esfera eso es una cuerda: sin trocear, un trozo largo de 82 paralelo se hundirÃa por debajo de la superficie. */ 83function walkSegments(raw, lens, scale, step, emit){ 84 let k = 0, n = 0; 85 for(let r=0;r<lens.length;r++){ 86 const len = lens[r]; 87 let plon = raw[k*2]/scale, plat = raw[k*2+1]/scale; k++; 88 for(let i=1;i<len;i++){ 89 const lon = raw[k*2]/scale, lat = raw[k*2+1]/scale; k++; 90 const dlon = lon - plon; 91 if(Math.abs(dlon) > 180){ plon = lon; plat = lat; continue; } 92 const steps = Math.max(1, Math.ceil(Math.max(Math.abs(dlon), Math.abs(lat-plat))/step)); 93 let alon = plon, alat = plat; 94 for(let s=1;s<=steps;s++){ 95 const t = s/steps; 96 const blon = plon + dlon*t, blat = plat + (lat-plat)*t; 97 if(emit) emit(n, alon, alat, blon, blat); 98 n++; 99 alon = blon; alat = blat; 100 } 101 plon = lon; plat = lat; 102 } 103 } 104 return n; 105} 106 107/* ll2v() sin crear el Vector3: escribe directamente en el array. Con 77.000 108 puntos daba igual; con los 309.000 tramos de las fronteras a 1:10m son más de 109 600.000 objetos que nacen y mueren durante la carga, y eso se nota. La 110 fórmula es la misma que ll2v(), letra por letra â si una cambia, cambian las 111 dos o la cinta deja de casar con el globo. */ 112function writeLL(arr, o, lon, lat, r){
113 const cl = Math.cos(lat*DEG); 114 arr[o] = r*cl*Math.cos(lon*DEG); 115 arr[o+1] = r*Math.sin(lat*DEG); 116 arr[o+2] = -r*cl*Math.sin(lon*DEG); 117} 118 119/* Normaliza el argumento `radius` de la cinta: vale un número âcinta a altura 120 fija sobre la esferaâ o una función (lon,lat)->radio, que es como las 121 fronteras se posan sobre el relieve (ver bordRadius() en nadir-globe.js). 122 123 La memoria de UN solo punto no es un lujo. El final de un tramo es el 124 principio del siguiente, asà que sin ella cada punto de la frontera se 125 consultarÃa dos veces, y cada consulta son cuatro muestras bilineales de la 126 textura de relieve: 620.000 llamadas en vez de 310.000. */ 127function radiusFn(radius){ 128 if(typeof radius !== "function") return () => radius; 129 let plon = NaN, plat = NaN, pr = 0; 130 return (lon, lat) => { 131 if(lon !== plon || lat !== plat){ plon = lon; plat = lat; pr = radius(lon, lat); } 132 return pr; 133 }; 134} 135 136/* Escribe las posiciones de los dos extremos de cada tramo. Está fuera de 137 buildLineRibbon() porque se ejecuta DOS veces âal construir y al volver a 138 posar la cintaâ y las dos pasadas tienen que recorrer los tramos exactamente 139 igual; de ahà que las dos vayan por walkSegments. 140 141 Los cuatro vértices de un tramo comparten posición: lo único que los 142 distingue es aSide/aEnd. Por eso la conversión lon/lat->XYZ se hace una vez 143 por punto y se copia â escribirla cuatro veces eran 2,5 millones de llamadas 144 a writeLL, con cuatro trigonometrÃas cada una, solo para repetir el mismo 145 resultado. */ 146function fillRibbon(raw, lens, scale, step, radius, pos, oth){ 147 const rad = radiusFn(radius); 148 let ax = 0, ay = 0, az = 0, bx = 0, by = 0, bz = 0; 149 const tmp = [0, 0, 0]; 150 walkSegments(raw, lens, scale, step, (i, alon, alat, blon, blat) => { 151 writeLL(tmp, 0, alon, alat, rad(alon, alat)); ax = tmp[0]; ay = tmp[1]; az = tmp[2]; 152 writeLL(tmp, 0, blon, blat, rad(blon, blat)); bx = tmp[0]; by = tmp[1]; bz = tmp[2]; 153 for(let v=0;v<4;v++){ 154 const o = (i*4+v)*3; 155 pos[o] = ax; pos[o+1] = ay; pos[o+2] = az; 156 oth[o] = bx; oth[o+1] = by; oth[o+2] = bz; 157 } 158 }); 159} 160 161/* Convierte las polilÃneas empaquetadas en una CINTA de ancho en pantalla: 4 162 vértices y 6 Ãndices por tramo. Aquà no se ensancha nada â solo se colocan 163 los dos extremos de cada tramo y se marca a qué lado y a qué punta va cada 164 vértice; el desplazamiento lo hace VERT_BORDER (nadir-shaders.js), porque 165 depende de la proyección de cada fotograma. 166 167 Devuelve la GEOMETRÃA, no una malla. El material y sus uniforms se montan en 168 nadir-globe.js, que es donde vive `U`: asà este archivo sigue dependiendo 169 solo de THREE y no del orden de carga de los shaders. 170 171 Antes esto era buildLines(), que devolvÃa un THREE.LineSegments. Se cambió el 172 5 septiembre 2026: `linewidth` no funciona en WebGL, asà que aquello eran 173 siempre lÃneas de un pÃxel, duras y sin suavizar, que en el vÃdeo hervÃan. 174 175 `radius` puede ser un número o una función (lon,lat)->radio: las fronteras 176 pasan una función para posarse sobre el terreno desplazado en vez de quedarse 177 a una altura fija que las montañas se tragan. Ver radiusFn/fillRibbon. */ 178function buildLineRibbon(b64, lens, radius, step, scale){ 179 const raw = decodeI32(b64); 180 const n = walkSegments(raw, lens, scale, step, null); // 1ª pasada: cuántos son 181 182 const pos = new Float32Array(n*4*3), oth = new Float32Array(n*4*3); 183 const side = new Float32Array(n*4), end = new Float32Array(n*4); 184 /* Uint16 solo direcciona 65.535 vértices; con las fronteras a 1:10m hacen 185 falta más de 300.000. Se decide por el tamaño real y no por una constante 186 que nadie revisarÃa â y three.js habilita OES_element_index_uint solo. */ 187 const idx = n*4 > 65535 ? new Uint32Array(n*6) : new Uint16Array(n*6); 188 const S = [-1, 1, -1, 1], E = [0, 0, 1, 1]; 189 190 /* Lo que NO depende de dónde esté la cinta: de qué lado y a qué punta va cada 191 vértice, y los dos triángulos del tramo. Se escribe una sola vez en la vida 192 de la geometrÃa; las posiciones van aparte porque se rehacen cada vez que 193 la cinta se vuelve a posar (fillRibbon / drapeLineRibbon). */ 194 for(let i=0;i<n;i++){ 195 for(let v=0;v<4;v++){ side[i*4+v] = S[v]; end[i*4+v] = E[v]; } 196 const q = i*4, o = i*6; 197 idx[o+0] = q; idx[o+1] = q+1; idx[o+2] = q+2; 198 idx[o+3] = q+1; idx[o+4] = q+3; idx[o+5] = q+2; 199 } 200 fillRibbon(raw, lens, scale, step, radius, pos, oth); 201 202 const g = new THREE.BufferGeometry(); 203 g.setAttribute("position", new THREE.BufferAttribute(pos, 3)); 204 g.setAttribute("aOther", new THREE.BufferAttribute(oth, 3)); 205 g.setAttribute("aSide", new THREE.BufferAttribute(side, 1)); 206 g.setAttribute("aEnd", new THREE.BufferAttribute(end, 1)); 207 g.setIndex(new THREE.BufferAttribute(idx, 1)); 208 /* Lo que hace falta para volver a posarla sin reconstruirla. Se guardan los 209 enteros ya decodificados y no el base64: asà drapeLineRibbon() no repite el 210 atob de 800 KB cada vez que se mueve el relieve. */
211 g.userData.ribbon = {raw, lens, scale, step}; 212 return g; 213} 214 215/* Vuelve a posar una cinta YA CONSTRUIDA a otro radio. No reconstruye nada: 216 reescribe las posiciones en su sitio y marca los dos atributos como sucios, 217 asà que no hay geometrÃa nueva ni buffers nuevos en la GPU. 218 219 Existe porque el relieve llega tarde: la textura tarda en cargar y hasta que 220 llega heightAt() vale 0, de modo que las fronteras nacen pegadas a una esfera 221 lisa. Cuando llega ây cada vez que el panel de dev cambia la exageraciónâ hay 222 que volver a posarlas. Ver redrapeBorders() en nadir-globe.js. */ 223function drapeLineRibbon(g, radius){ 224 const r = g.userData.ribbon; 225 if(!r) return; 226 const pos = g.getAttribute("position"), oth = g.getAttribute("aOther"); 227 fillRibbon(r.raw, r.lens, r.scale, r.step, radius, pos.array, oth.array); 228 pos.needsUpdate = true; oth.needsUpdate = true; 229} 230 231window.NADIR = window.NADIR || {}; 232window.NADIR.geo = {DEG, ll2v, v2ll, frame, decodeI32, buildLineRibbon, drapeLineRibbon}; 233})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.