PageSourceSearch

https://www.guidemapp.io/nadir/nadir-geo.js

js guidemapp.io collected 2026-10-02 01:58:46 UTC 11,492 bytes, 233 lines download raw bytes

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.