import {vec,hex} from './math.js'; export const palette={junction:'#99c4ab',circular:'#64bdb9',rectangular:'#d7b47a',trapezoidal:'#b8a2d8',outfall:'#8bb5d4',storage:'#b8a2d8',pump:'#d8bd75',weir:'#d29a88',grid:'#35474a'}; export function layerOf(f){return ['junction','conduit','outfall'].includes(f.type)?f.type:'other';} export function physicalPoint(n,origin){return n.xy?[n.xy[0]-origin[0],n.xy[1]-origin[1],n.invert-origin[2]]:null;} // Inlet/outlet rings are vertical in the original XY frame. The cross-section // centre is raised by half the full input height above the respective invert. export function conduitPath(link,origin){ if(!link.pathXY||!link.renderable)return []; const lengths=[0];for(let i=1;i[p[0]-origin[0],p[1]-origin[1],link.invertStart-origin[2]+(link.invertEnd-link.invertStart)*lengths[i]/total]); } class Batch { constructor(mode){this.mode=mode;this.data=[];this.count=0;} vertex(base,offset,normal,color,id,kind=0,slice=0){this.data.push(...base,...offset,...normal,...color,id,kind,slice);this.count++;} triangle(bases,offsets,color,id,kind=0,slices=[0,0,0]){ const p=bases.map((b,i)=>vec.add(b,offsets[i]));const n=vec.norm(vec.cross(vec.sub(p[1],p[0]),vec.sub(p[2],p[0]))); for(let i=0;i<3;i++)this.vertex(bases[i],offsets[i],n,color,id,kind,slices[i]); } quad(bases,offsets,color,id,kind=0,slices=[0,0,0,0]){ for(const tri of [[0,1,2],[0,2,3]])this.triangle(tri.map(i=>bases[i]),tri.map(i=>offsets[i]),color,id,kind,tri.map(i=>slices[i])); } } function vessel(batch,point,height,radius,color,id,steps=14,kind=1){ const top=[point[0],point[1],point[2]+height]; for(let i=0;i{const t=i/12*Math.PI*2;return[section.width/2*Math.cos(t),section.height/2*(1+Math.sin(t)),Math.sin(t)];}):[[-section.width/2,0,-1],[section.width/2,0,-1],[section.width/2,section.height,1],[-section.width/2,section.height,1]]; const open=section.shape==='TRAPEZOIDAL'||section.shape==='RECT_OPEN'; if(open){ const h=section.height,w=section.width,left=section.sideSlopes?.[0]??0,right=section.sideSlopes?.[1]??0; ring=[[-w/2-left*h,h,1],[-w/2,0,-1],[w/2,0,-1],[w/2+right*h,h,1]]; } for(let k=1;k[n.id,n])); for(const f of facilities){ const group=batches[layerOf(f)],id=f.pickId,color=hex(palette[f.type==='conduit'?(f.section?.shape==='CIRCULAR'?'circular':f.section?.shape==='TRAPEZOIDAL'?'trapezoidal':'rectangular'):f.type]); if(f.xy){ const point=physicalPoint(f,origin);const height=f.maxDepth??1; if(f.type==='junction')vessel(group.mesh,point,height,.6,color,id); else if(f.type==='storage')vessel(group.mesh,point,height,1.7,color,id,6,1); else vessel(group.mesh,point,1,.8,color,id,4,3); // Five-pixel minimum marker is a position glyph, not a physical manhole diameter. group.points.vertex([point[0],point[1],point[2]+height],[0,0,0],[0,0,1],color,id,3); }else if(f.type==='conduit'&&f.renderable){ const path=tube(group.mesh,f,origin,color,id); for(let i=1;ip.type===f.type&&p.fromNode===f.fromNode&&p.toNode===f.toNode),slot=peers.indexOf(f)-(peers.length-1)/2; const direction=vec.norm([b[0]-a[0],b[1]-a[1],0]);mid[0]+=-direction[1]*slot*3;mid[1]+=direction[0]*slot*3; vessel(group.mesh,mid,1.3,f.type==='pump'?.8:1.1,color,id,f.type==='pump'?8:4,3); group.points.vertex([mid[0],mid[1],z+1.3],[0,0,0],[0,0,1],color,id,3); for(const p of [a,mid,mid,b])group.lines.vertex(p,[0,0,0],[0,0,1],color,id,4); f.symbolPoint=[mid[0],mid[1],z+1.3]; } } const bound=network.meta.bounds,extent=Math.max(bound.maxX-bound.minX,bound.maxY-bound.minY)*.72,grid=batches.grid.lines; for(let n=-Math.ceil(extent/100)*100;n<=extent;n+=100)for(const pair of [[[-extent,n,-1],[extent,n,-1]],[[n,-extent,-1],[n,extent,-1]]])for(const p of pair)grid.vertex(p,[0,0,0],[0,0,1],hex(palette.grid),0,4); return batches; }