import { presentationEntities } from './presentation'; import type { Bounds,DrawingView,Entity,Point,QualityIssue } from './types'; export const overlaps=(a:Bounds,b:Bounds,pad=0)=>Math.min(a.maxX,b.maxX)-Math.max(a.minX,b.minX)>pad&&Math.min(a.maxY,b.maxY)-Math.max(a.minY,b.minY)>pad; export const union=(boxes:Bounds[]):Bounds=>boxes.length?{minX:Math.min(...boxes.map(b=>b.minX)),minY:Math.min(...boxes.map(b=>b.minY)),maxX:Math.max(...boxes.map(b=>b.maxX)),maxY:Math.max(...boxes.map(b=>b.maxY))}:{minX:0,minY:0,maxX:1,maxY:1}; export function textWidth(text:string,height:number):number{return Math.max(height*.35,[...text].reduce((s,c)=>s+(c.charCodeAt(0)>255?1:/[il., :;'|]/.test(c)?.3:/[MW@]/.test(c)?.88:.6),0)*height);} export function entityBox(e:Entity):Bounds { let ps:Point[]=[]; if(e.kind==='text'){ const w=textWidth(e.text,e.height),left=e.align==='center'?-w/2:e.align==='right'?-w:0,theta=(e.rotation??0)*Math.PI/180; ps=[[left,-.22*e.height],[left+w,-.22*e.height],[left+w,e.height],[left,e.height]].map(([x,y])=>[e.point[0]+x*Math.cos(theta)-y*Math.sin(theta),e.point[1]+x*Math.sin(theta)+y*Math.cos(theta)]); }else if(e.kind==='circle')ps=[[e.center[0]-e.radius,e.center[1]-e.radius],[e.center[0]+e.radius,e.center[1]+e.radius]]; else if(e.kind==='line')ps=[e.a,e.b];else if(e.kind==='polyline'||e.kind==='hatch')ps=e.points; if(!ps.length)return{minX:0,minY:0,maxX:0,maxY:0}; return{minX:Math.min(...ps.map(p=>p[0])),minY:Math.min(...ps.map(p=>p[1])),maxX:Math.max(...ps.map(p=>p[0])),maxY:Math.max(...ps.map(p=>p[1]))}; } export const occupiedBounds=(view:DrawingView):Bounds=>union(presentationEntities(view).map(p=>entityBox(p.entity))); const lineHits=(a:Point,b:Point,r:Bounds):boolean=>{ let t0=0,t1=1;const dx=b[0]-a[0],dy=b[1]-a[1]; for(const [p,q] of [[-dx,a[0]-r.minX],[dx,r.maxX-a[0]],[-dy,a[1]-r.minY],[dy,r.maxY-a[1]]]){ if(Math.abs(p)<1e-12){if(q<0)return false;}else{const t=q/p;if(p<0)t0=Math.max(t0,t);else t1=Math.min(t1,t);if(t0>t1)return false;} }return true; }; export function viewCollisions(view:DrawingView):QualityIssue[]{ const rendered=presentationEntities(view),texts=rendered.filter(p=>p.entity.kind==='text'),issues:QualityIssue[]=[],s=view.scale; const structural=rendered.filter(p=>['STRUCTURE','BEARING'].includes(p.entity.layer)); for(let i=0;i[e.points[i],b]).concat(e.closed?[[e.points[e.points.length-1],e.points[0]]]:[]):[]; if(segments.some(([a,b])=>lineHits(a,b,box))){issues.push({view:view.id,kind:'text-structure',first:a.index,second:p.index,message:'문자와 구조 외형선 간섭'});break;} } } return issues; }