@rr0/ufoathome 0.149.0 → 0.150.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,41 @@
1
+ import{a6 as F,ab as P,an as H,ao as K,ar as w,at as N,a9 as U,au as k}from"./rr0-sighting-editor.mjs";class b{static HOLLOW_M=1;static COVERED_M=3;static WINDOW_M=30;static SAMPLE_M=4;static MAX_GRADE={rail:.025,highSpeed:.035,subway:.04,light_rail:.06,tram:.08,funicular:1/0};static heights(t,s,e){const n=t.map(a=>a.level===0),o=t.map((a,i)=>n[i]?b.targets(a,s,e):[]),c=t.map((a,i)=>n[i]?b.samples(a,o[i]):[]),r=new Map;t.forEach((a,i)=>{if(n[i])for(const h of b.ends(a)){const u=b.key(h),p=r.get(u);p?p.push(i):r.set(u,[i])}});const M=a=>{const i=r.get(b.key(a));if(i)return b.median(i.flatMap(h=>c[h]),a)};return t.map((a,i)=>{if(n[i]){const h=[...new Set([i,...b.ends(a).flatMap(d=>r.get(b.key(d))??[])])].flatMap(d=>c[d]),u=a.centre.map((d,O)=>b.median(h,d)??o[i][O]),[p,A]=b.ends(a);return u[0]=M(p)??u[0],u[u.length-1]=M(A)??u[u.length-1],b.graded(u,a.along,a.highSpeed?b.MAX_GRADE.highSpeed:b.MAX_GRADE[a.kind])}return b.carried(a,s,e,M)})}static targets(t,s,e){return t.centre.map(n=>{const o=s(n.x,n.z);if(e===void 0||n.altitudeM===void 0)return o;const c=n.altitudeM+e;return c-o>b.HOLLOW_M||o-c>b.COVERED_M?c:o})}static samples(t,s){const e=[];let n=0;return t.centre.forEach((o,c)=>{t.along[c]+1e-6<n&&c<t.centre.length-1||(e.push({x:o.x,z:o.z,y:s[c]}),n=t.along[c]+b.SAMPLE_M)}),e}static median(t,s){const e=[];for(const o of t)Math.hypot(o.x-s.x,o.z-s.z)<=b.WINDOW_M&&e.push(o.y);if(e.length===0)return;e.sort((o,c)=>o-c);const n=e.length>>1;return e.length%2===1?e[n]:(e[n-1]+e[n])/2}static graded(t,s,e){if(!Number.isFinite(e)||t.length<3)return t;const n=[...t],o=n.length-1,c=s[o];if(Math.abs(n[o]-n[0])>e*c)return s.map(r=>n[0]+(n[o]-n[0])*r/c);for(let r=1;r<o;r++){const M=e*(s[r]-s[r-1]);n[r]=Math.min(n[r-1]+M,Math.max(n[r-1]-M,n[r]))}for(let r=o-1;r>0;r--){const M=e*(s[r+1]-s[r]);n[r]=Math.min(n[r+1]+M,Math.max(n[r+1]-M,n[r]))}return n}static carried(t,s,e,n){const[o,c]=b.ends(t),r=t.along[t.along.length-1],M=n(o),a=n(c);if(e!==void 0&&t.centre.every(u=>u.altitudeM!==void 0)){const u=t.centre.map(d=>d.altitudeM+e),p=M!==void 0?M-u[0]:0,A=a!==void 0?a-u[u.length-1]:0;return u.map((d,O)=>d+p+(A-p)*t.along[O]/r)}const i=M??s(o.x,o.z),h=a??s(c.x,c.z);return t.along.map(u=>i+(h-i)*u/r)}static ends(t){return[t.centre[0],t.centre[t.centre.length-1]]}static key(t){return`${Math.round(t.x*2)},${Math.round(t.z*2)}`}}class B{positions=[];indices=[];along=[];get vertexCount(){return this.positions.length/3}strip(t,s,e){const n=this.vertexCount;for(let o=0;o<t.length;o++)this.positions.push(t[o].x,t[o].y,t[o].z,s[o].x,s[o].y,s[o].z),e&&this.along.push(e[o],e[o]);for(let o=0;o<t.length-1;o++){const c=n+o*2;this.indices.push(c,c+2,c+1,c+1,c+2,c+3)}}beam(t,s,e,n=!0){const o=s.x-t.x,c=s.y-t.y,r=s.z-t.z,M=Math.hypot(o,c,r);if(M<1e-6)return;const a={x:o/M,y:c/M,z:r/M},i=Math.abs(a.y)>.9?{x:1,y:0,z:0}:{x:0,y:1,z:0},h=a.y*i.z-a.z*i.y,u=a.z*i.x-a.x*i.z,p=a.x*i.y-a.y*i.x,A=Math.hypot(h,u,p),d={x:h/A*e/2,y:u/A*e/2,z:p/A*e/2},O={x:a.y*d.z-a.z*d.y,y:a.z*d.x-a.x*d.z,z:a.x*d.y-a.y*d.x},g=(f,x,E)=>({x:f.x+d.x*x+O.x*E,y:f.y+d.y*x+O.y*E,z:f.z+d.z*x+O.z*E}),_=[[1,1],[-1,1],[-1,-1],[1,-1]];for(let f=0;f<4;f++){const[x,E]=_[f],[z,v]=_[(f+1)%4];this.quad(g(t,x,E),g(t,z,v),g(s,z,v),g(s,x,E))}n&&(this.quad(...[..._].reverse().map(([f,x])=>g(t,f,x))),this.quad(..._.map(([f,x])=>g(s,f,x))))}quad(t,s,e,n){const o=this.vertexCount;this.positions.push(t.x,t.y,t.z,s.x,s.y,s.z,e.x,e.y,e.z,n.x,n.y,n.z),this.indices.push(o,o+1,o+2,o,o+2,o+3)}}class l{static STEP_M=6;static BALLAST_HEIGHT_M=.5;static BALLAST_SLOPE=1.5;static BALLAST_SHOULDER_M=.45;static DECK_DEPTH_M=.8;static BANK_SLOPE=1.5;static SLEEPER_LENGTH_M=2.4;static SLEEPER_WIDTH_M=.29;static SLEEPER_SPACING_M=.6;static RAIL_HEAD_M=.072;static RAIL_HEIGHT_M=.18;static TRAM_RAIL_HEIGHT_M=.03;static LIFT_M=.06;static CATENARY={rail:{contactM:5.5,systemM:1.4,spanM:54,staggerM:.2},highSpeed:{contactM:5.08,systemM:1.4,spanM:63,staggerM:.2},service:{contactM:5.5,systemM:1.2,spanM:45,staggerM:.2},tram:{contactM:6,systemM:0,spanM:30,staggerM:.2}};static MESSENGER_SAG_M=.55;static MAST_SIDE_M=.26;static MAST_OFFSET_M=3.1;static MAST_ABOVE_M=.6;static CONTACT_DIAMETER_M=.012;static MESSENGER_DIAMETER_M=.011;static ARM_SIDE_M=.06;static TRACK_SPACING_M={rail:3.7,highSpeed:4.5,tram:3.1,subway:3.4,light_rail:3.4,funicular:3.2};static BALLAST_ALBEDO=[.17,.16,.15];static BANK_ALBEDO=[.085,.09,.05];static DECK_ALBEDO=[.26,.255,.24];static CONCRETE_ALBEDO=[.36,.35,.33];static WOOD_ALBEDO=[.07,.055,.045];static RUST_ALBEDO=[.085,.068,.058];static STEEL_ALBEDO=[.3,.31,.32];static COPPER_ALBEDO=[.09,.085,.07];static LINE_REACH_M=20;group=new F;stepM=l.STEP_M;drawn=0;laid=[];ballastMaterial=l.pebbled(l.lambert(l.BALLAST_ALBEDO,!0));earthMaterial=l.lambert(l.BANK_ALBEDO,!0);deckMaterial=l.lambert(l.DECK_ALBEDO,!1);concreteSleepers=l.sleeperMaterial(l.CONCRETE_ALBEDO);woodSleepers=l.sleeperMaterial(l.WOOD_ALBEDO);rustMaterial=l.lambert(l.RUST_ALBEDO,!0);headMaterial=new P({color:new H(.62,.62,.62),metalness:1,roughness:.28,polygonOffset:!0,polygonOffsetFactor:-2,polygonOffsetUnits:-2});steelMaterial=l.lambert(l.STEEL_ALBEDO,!1);wireMaterial=l.lambert(l.COPPER_ALBEDO,!1);constructor(){this.group.name="railways"}get count(){return this.drawn}setGroundSpacing(t){this.stepM=Number.isFinite(t)&&t>0?Math.min(l.STEP_M,Math.max(1.5,t/2)):l.STEP_M}set(t,s,e,n,o=1/0){this.clear();const r=t.map(p=>({line:p,path:p.points.map(A=>({...K(A.lat,A.lng,s,e),altitudeM:A.altitudeM}))})).filter(({line:p,path:A})=>p.level>=0&&l.within(A,o)),M=l.altitudeOffset(r,n),a=r.flatMap(({line:p,path:A})=>{const d=this.layoutOf(p,A);return d?[{line:p,layout:d}]:[]}),i=b.heights(a.map(({layout:p})=>p),n,M),h={ballast:new B,earth:new B,deck:new B,concrete:new B,wood:new B,rust:new B,head:new B,steel:new B,wire:new B};a.forEach(({line:p,layout:A},d)=>{this.lay(p,{...A,y:i[d]},n,h),this.laid.push({line:p,centre:A.centre}),this.drawn++});const u=[[h.ballast,this.ballastMaterial,"ballast",!1],[h.earth,this.earthMaterial,"embankments",!1],[h.deck,this.deckMaterial,"decks",!0],[h.concrete,this.concreteSleepers,"sleepers",!1],[h.wood,this.woodSleepers,"sleepers wood",!1],[h.rust,this.rustMaterial,"rails",!1],[h.head,this.headMaterial,"rail heads",!1],[h.steel,this.steelMaterial,"catenary masts",!0],[h.wire,this.wireMaterial,"catenary wires",!1]];for(const[p,A,d,O]of u){const g=l.meshOf(p,A);g&&(g.name=`railways ${d}`,g.castShadow=O,g.receiveShadow=!0,this.group.add(g))}}clear(){for(const t of[...this.group.children])this.group.remove(t),t.geometry?.dispose();this.drawn=0,this.laid=[]}lineAt(t,s){let e;for(const{line:n,centre:o}of this.laid)for(let c=0;c<o.length-1;c++){const r=o[c],M=o[c+1],a=M.x-r.x,i=M.z-r.z,h=a*a+i*i,u=h===0?0:Math.max(0,Math.min(1,((t-r.x)*a+(s-r.z)*i)/h)),p=Math.hypot(t-(r.x+a*u),s-(r.z+i*u));p<=l.LINE_REACH_M&&(!e||p<e.distance)&&(e={line:n,distance:p})}return e?.line}mayMeet(t,s,e,n){const o=Math.hypot(s.x,s.z);if(o<1e-6)return this.lineAt(t.x,t.z)!==void 0;const c=s.x/o,r=s.z/o,M=l.LINE_REACH_M;for(const{centre:a}of this.laid)for(let i=0;i<a.length-1;i++){const h=a[i],u=a[i+1];for(const p of[0,.5,1]){const A=h.x+(u.x-h.x)*p,d=h.z+(u.z-h.z)*p,O=(A-t.x)*c+(d-t.z)*r;if(O<0)continue;const g=Math.abs((A-t.x)*r-(d-t.z)*c),_=Math.hypot(u.x-h.x,u.z-h.z)/2;if(g>M+_)continue;if(t.y+s.y*(O/o)<=e(A,d)+n)return!0}}return!1}contactHeightM(t){if(!(t.electrification!=="overhead"||t.kind==="subway"||t.kind==="funicular"))return l.catenaryOf(t).contactM}static catenaryOf(t){return t.kind==="tram"?l.CATENARY.tram:t.highSpeed?l.CATENARY.highSpeed:t.service?l.CATENARY.service:l.CATENARY.rail}dispose(){this.clear();for(const t of[this.ballastMaterial,this.earthMaterial,this.deckMaterial,this.concreteSleepers,this.woodSleepers,this.rustMaterial,this.headMaterial,this.steelMaterial,this.wireMaterial])t.dispose()}static within(t,s){if(!Number.isFinite(s))return!0;const e=s*1.1+100;return t.some((n,o)=>Math.hypot(n.x,n.z)<=e||o>0&&Math.hypot((n.x+t[o-1].x)/2,(n.z+t[o-1].z)/2)<=e)}static altitudeOffset(t,s){const e=[];for(const{line:n,path:o}of t)if(n.level===0)for(const c of o)c.altitudeM!==void 0&&e.push(s(c.x,c.z)-c.altitudeM);if(!(e.length<5))return e.sort((n,o)=>n-o),e[Math.floor(e.length/2)]}layoutOf(t,s){const e=[];for(let c=0;c<s.length-1;c++){const r=s[c],M=s[c+1],a=Math.max(1,Math.ceil(Math.hypot(M.x-r.x,M.z-r.z)/this.stepM));for(let i=0;i<a;i++){const h=i/a,u=r.altitudeM!==void 0&&M.altitudeM!==void 0?r.altitudeM+(M.altitudeM-r.altitudeM)*h:void 0;e.push({x:r.x+(M.x-r.x)*h,z:r.z+(M.z-r.z)*h,altitudeM:u})}}if(e.push(s[s.length-1]),e.length<2)return;const n=[0];for(let c=1;c<e.length;c++)n.push(n[c-1]+Math.hypot(e[c].x-e[c-1].x,e[c].z-e[c-1].z));if(n[n.length-1]<.5)return;const o=e.map((c,r)=>{const M=e[Math.max(0,r-1)],a=e[Math.min(e.length-1,r+1)],i=a.x-M.x,h=a.z-M.z,u=Math.hypot(i,h);return u<1e-6?{x:1,z:0}:{x:i/u,z:h/u}});return{kind:t.kind,...t.highSpeed?{highSpeed:!0}:{},level:t.level,centre:e,along:n,dir:o}}lay(t,s,e,n){const o=t.highSpeed?l.TRACK_SPACING_M.highSpeed:l.TRACK_SPACING_M[t.kind],c=Math.min(8,Math.max(1,t.tracks)),r=Array.from({length:c},(g,_)=>(_-(c-1)/2)*o),M=t.kind==="tram",a=M?0:l.BALLAST_HEIGHT_M,i=l.LIFT_M,h=(g,_,f)=>({x:s.centre[g].x-s.dir[g].z*_,y:s.y[g]+f,z:s.centre[g].z+s.dir[g].x*_}),u=s.centre.map((g,_)=>_);if(!M){const g=l.SLEEPER_LENGTH_M/2+l.BALLAST_SHOULDER_M+(r[r.length-1]??0),_=u.map(x=>h(x,g,a+i)),f=u.map(x=>h(x,-g,a+i));if(n.ballast.strip(_,f),t.level===0){const x=g+a*l.BALLAST_SLOPE;for(const E of[1,-1]){const z=[],v=[];let m=!1;for(const L of u){const C=h(L,E*x,i),S=e(C.x,C.z)+i,T=C.y-S;T<0&&(C.y=Math.min(S,s.y[L]+a-.05+i)),z.push(C);const I=h(L,E*(x+Math.max(0,T)*l.BANK_SLOPE),i);I.y=Math.min(e(I.x,I.z)+i,C.y),C.y-I.y>.02&&(m=!0),v.push(I)}const D=E>0?_:f;E>0?n.ballast.strip(z,D):n.ballast.strip(D,z),m&&(E>0?n.earth.strip(v,z):n.earth.strip(z,v))}}else{const x=-l.DECK_DEPTH_M,E=g+.15,z=u.map(L=>h(L,E,a+i+.1)),v=u.map(L=>h(L,-E,a+i+.1)),m=u.map(L=>h(L,E,x)),D=u.map(L=>h(L,-E,x));n.deck.strip(m,z),n.deck.strip(v,D),n.deck.strip(D,m),n.deck.strip(z,u.map(L=>h(L,g,a+i+.1))),n.deck.strip(u.map(L=>h(L,-g,a+i+.1)),v)}}const p=t.service?n.wood:n.concrete,A=t.gaugeMm/2e3+l.RAIL_HEAD_M/2,d=M?l.TRAM_RAIL_HEIGHT_M:l.RAIL_HEIGHT_M,O=a+i;for(const g of r){if(!M){const _=l.SLEEPER_LENGTH_M/2;p.strip(u.map(f=>h(f,g+_,O+.004)),u.map(f=>h(f,g-_,O+.004)),s.along)}for(const _ of[1,-1]){const f=g+_*A,x=l.RAIL_HEAD_M/2,E=u.map(v=>h(v,f+x,O+d)),z=u.map(v=>h(v,f-x,O+d));n.head.strip(E,z),n.rust.strip(u.map(v=>h(v,f+x,O)),E),n.rust.strip(z,u.map(v=>h(v,f-x,O)))}}t.electrification==="overhead"&&t.kind!=="subway"&&t.kind!=="funicular"&&this.catenary(t,s,r,O+d,h,e,n)}catenary(t,s,e,n,o,c,r){const M=l.catenaryOf(t),a=s.along[s.along.length-1],i=Math.max(1,Math.round(a/M.spanM)),h=a/i,u=Array.from({length:i},(E,z)=>(z+.5)*h),p=n+M.contactM,A=p+M.systemM,d=(M.systemM>0?A:p)+l.MAST_ABOVE_M,O=e[e.length-1],g=e.length>2,_=e.length===1?[1]:[1,-1],f=(E,z,v)=>{let m=0,D=s.along.length-2;for(;m<D;){const T=m+D+1>>1;s.along[T]<=E?m=T:D=T-1}const L=Math.min(1,Math.max(0,(E-s.along[m])/Math.max(1e-6,s.along[m+1]-s.along[m]))),C=o(m,z,v),S=o(m+1,z,v);return{x:C.x+(S.x-C.x)*L,y:C.y+(S.y-C.y)*L,z:C.z+(S.z-C.z)*L}};for(const E of u){const z=f(E,0,p);if(!(t.level===0&&c(z.x,z.z)>z.y)){for(const v of _){const m=v*(O+l.MAST_OFFSET_M),D=f(E,m,0);t.level===0&&(D.y=Math.min(D.y,c(D.x,D.z)));const L=f(E,m,d);if(r.steel.beam(D,L,l.MAST_SIDE_M),g)continue;const S=(_.length===1?e[0]:v*O)-v*.6,T=M.systemM>0?A:p+.3;r.steel.beam(f(E,m,T),f(E,S,T),l.ARM_SIDE_M),r.steel.beam(f(E,m,p-.4),f(E,S,T),l.ARM_SIDE_M)}if(g){const v=O+l.MAST_OFFSET_M;r.steel.beam(f(E,v,d-.2),f(E,-v,d-.2),l.MAST_SIDE_M*.8)}}}const x=[0,...u,a];for(const[E,z]of e.entries()){const v=m=>m===0||m===x.length-1?0:((m+E)%2===0?1:-1)*M.staggerM;for(let m=0;m<x.length-1;m++){const D=x[m],L=x[m+1];if(L-D<.1||(r.wire.beam(f(D,z+v(m),p),f(L,z+v(m+1),p),l.CONTACT_DIAMETER_M,!1),M.systemM<=0))continue;const C=4,S=T=>{const I=m===0?.5+T/2:m===x.length-2?T/2:T;return A-l.MESSENGER_SAG_M*4*I*(1-I)};for(let T=0;T<C;T++){const I=T/C,G=(T+1)/C;r.wire.beam(f(D+(L-D)*I,z,S(I)),f(D+(L-D)*G,z,S(G)),l.MESSENGER_DIAMETER_M,!1)}}}}static meshOf(t,s){if(t.indices.length===0)return;const e=new w;return e.setAttribute("position",new N(new Float32Array(t.positions),3)),t.along.length>0&&e.setAttribute("aAlong",new N(new Float32Array(t.along),1)),e.setIndex(new N(new Uint32Array(t.indices),1)),e.computeVertexNormals(),new U(e,s)}static lambert(t,s){return new k({color:new H(...t),...s?{polygonOffset:!0,polygonOffsetFactor:-2,polygonOffsetUnits:-2}:{}})}static pebbled(t){return t.onBeforeCompile=s=>{s.vertexShader=s.vertexShader.replace("#include <common>",`#include <common>
2
+ varying vec3 vBallast;`).replace("#include <begin_vertex>",`#include <begin_vertex>
3
+ vBallast = position;`),s.fragmentShader=s.fragmentShader.replace("#include <common>",`#include <common>
4
+ varying vec3 vBallast;
5
+ float ballastHash(vec2 p) {
6
+ p = fract(p * vec2(127.1, 311.7));
7
+ p += dot(p, p + 34.5);
8
+ return fract(p.x * p.y);
9
+ }
10
+ float ballastNoise(vec2 p) {
11
+ vec2 i = floor(p);
12
+ vec2 f = fract(p);
13
+ f = f * f * (3.0 - 2.0 * f);
14
+ return mix(mix(ballastHash(i), ballastHash(i + vec2(1.0, 0.0)), f.x), mix(ballastHash(i + vec2(0.0, 1.0)), ballastHash(i + vec2(1.0, 1.0)), f.x), f.y);
15
+ }`).replace("#include <map_fragment>",`#include <map_fragment>
16
+ {
17
+ float eye = length(vViewPosition);
18
+ vec2 at = vBallast.xz + vec2(0.0, vBallast.y);
19
+ float stones = (ballastHash(floor(at * 22.0)) - 0.5) * 0.9 * (1.0 - smoothstep(4.0, 18.0, eye))
20
+ + (ballastNoise(at * 6.0) - 0.5) * 0.5 * (1.0 - smoothstep(15.0, 60.0, eye))
21
+ + (ballastNoise(at * 0.7) - 0.5) * 0.35 * (1.0 - smoothstep(80.0, 300.0, eye));
22
+ diffuseColor.rgb *= 1.0 + stones;
23
+ }`)},t.customProgramCacheKey=()=>"rr0-railway-ballast",t}static sleeperMaterial(t){const s=new k({color:new H(1,1,1),polygonOffset:!0,polygonOffsetFactor:-3,polygonOffsetUnits:-3}),e=new H(...t),n=new H(...l.BALLAST_ALBEDO);return s.onBeforeCompile=o=>{o.uniforms.uSleeper={value:e},o.uniforms.uCrib={value:n},o.uniforms.uSpacing={value:l.SLEEPER_SPACING_M},o.uniforms.uDuty={value:l.SLEEPER_WIDTH_M/l.SLEEPER_SPACING_M},o.vertexShader=o.vertexShader.replace("#include <common>",`#include <common>
24
+ attribute float aAlong;
25
+ varying float vAlong;`).replace("#include <begin_vertex>",`#include <begin_vertex>
26
+ vAlong = aAlong;`),o.fragmentShader=o.fragmentShader.replace("#include <common>",`#include <common>
27
+ varying float vAlong;
28
+ uniform vec3 uSleeper;
29
+ uniform vec3 uCrib;
30
+ uniform float uSpacing;
31
+ uniform float uDuty;
32
+ // How much of [0, x] periods is sleeper: the integral of a pulse of width uDuty at the start of each period.
33
+ float sleeperIntegral(float x) {
34
+ return floor(x) * uDuty + min(fract(x), uDuty);
35
+ }`).replace("#include <map_fragment>",`#include <map_fragment>
36
+ {
37
+ float u = vAlong / uSpacing;
38
+ float w = max(fwidth(u), 1e-4);
39
+ float cover = (sleeperIntegral(u + 0.5 * w) - sleeperIntegral(u - 0.5 * w)) / w;
40
+ diffuseColor.rgb *= mix(uCrib, uSleeper, clamp(cover, 0.0, 1.0));
41
+ }`)},s.customProgramCacheKey=()=>"rr0-railway-sleepers",s}}export{l as RailwaySystem};