// Framework-independent data adapter. Does not parse or modify the project's INP. export const CAD_ORIGIN=Object.freeze([513800,2344450]); export function cadToGltf(point,displayZ=0){ if(!Array.isArray(point)||point.length<2||!point.slice(0,2).every(Number.isFinite)||!Number.isFinite(displayZ))throw Error('Expected finite CAD XY and an explicit display height.'); return [point[0]-CAD_ORIGIN[0],displayZ,-(point[1]-CAD_ORIGIN[1])]; } // Supply longitude/latitude OF CAD_ORIGIN, not the old OSM anchor. This function // deliberately cannot guess CRS, accept the disabled legacy candidate, or add 23.9deg. export function mapModelMatrixElements(mercatorOrigin,meterScale,{rotationDeg=0,horizontalScale=1}={}){ if(![mercatorOrigin?.x,mercatorOrigin?.y,mercatorOrigin?.z,meterScale,rotationDeg,horizontalScale].every(Number.isFinite)||meterScale<=0||horizontalScale<=0)throw Error('Confirmed Mercator origin and positive scales required.'); const a=rotationDeg*Math.PI/180,c=Math.cos(a)*meterScale*horizontalScale,s=Math.sin(a)*meterScale*horizontalScale; // Column-major THREE.Matrix4; glTF +Y up, -Z CAD north; Mercator +Y south. return [c,-s,0,0, 0,0,meterScale,0, s,c,0,0, mercatorOrigin.x,mercatorOrigin.y,mercatorOrigin.z,1]; } export function resolveMeterPlacements(bindingFile,{inpSha256,links,displayHeight=0.35}){ if(inpSha256!==bindingFile.compatibleInpSha256)throw Error('INP version mismatch: reconcile the equipment mapping before placement.'); const byId=new Map(links.map(link=>[String(link.id),link]));if(byId.size!==links.length)throw Error('Duplicate INP link IDs.'); const placements=[],issues=[]; for(const meter of bindingFile.meters){ const link=byId.get(meter.inpLinkId); if(!link){issues.push({assetId:meter.assetId,reason:'missing_host_link',id:meter.inpLinkId});continue;} const pts=link.coordinates; if(!Array.isArray(pts)||pts.length<2||pts.some(p=>!Array.isArray(p)||p.length<2||!p.slice(0,2).every(Number.isFinite))||!Number.isFinite(link.diameterMm)||link.diameterMm<=0){issues.push({assetId:meter.assetId,reason:'invalid_host_geometry_or_diameter'});continue;} let best; for(let i=1;i.25){issues.push({assetId:meter.assetId,reason:'CAD_host_registration_exceeds_0.25m',distanceM:best.distance});continue;} const half=Math.min(.33,best.length*.2),t=Math.max(half/best.length,Math.min(1-half/best.length,best.t)); const center=[best.a[0]+best.dx*t,best.a[1]+best.dy*t]; placements.push({assetId:meter.assetId,prototype:meter.prototype,hostLinkId:meter.inpLinkId,position:cadToGltf(center,displayHeight),direction:[best.dx/best.length,0,-best.dy/best.length],scale:[half/.33,link.diameterMm/150,link.diameterMm/150],portDistanceM:2*half,createsHydraulicLink:false,scadaId:null}); } return {placements,issues}; }