commit 33403598173215c6cff1d43de412e9adf53ca00a Author: 이화영 Date: Thu Oct 29 09:08:50 2026 +0900 파일을 "/" 로 업로드 diff --git a/app.js b/app.js new file mode 100644 index 0000000..30e124e --- /dev/null +++ b/app.js @@ -0,0 +1,3034 @@ +/** + * ============================================================================ + * 프로젝트: 터널 단면 3D 파라메트릭 모델러 (Tunnel Modeler 3D) + * 개발자: m12205.tdc.hanmacgroup@gmail.com + * 저작권: Copyright (c) 2026 m12205.tdc.hanmacgroup@gmail.com. All rights reserved. + * 설명: DWG/DXF 표준단면도 기반 지능형 형상 인식 및 3D 솔리드 모델 자동 생성 엔진 + * ============================================================================ + */ + +window.current3DTunnelLength = 10; +window.current3DOpacity = 1.0; +window.current3DOuterOpacity = 1.0; +window.current3DViewMode = 'bird'; +window.current3DTheme = 'dark'; +window.current3DVisibility = { + shotcrete: true, + lining: true, + pavement: true, + lids: true, + blindDitch: true, + drainPipe: true, + lanes: true, + rims: true +}; + +// 추출된 단면 설계 제원 상태 +window.sectionParams = { + offset: 3500, + h1: 500, + theta1: 120, + r1: 5500, + r2: 7500, + r3: 7500, + r4: 9200, + r5: 9200, + lining_t: 300, + shotcrete_t: 150, + seVal: -2.0, + lanes: [3500, 3500], + left_sh: 1000, + right_sh: 1000, + left_side: 1000, + right_side: 1000, + h_val: 4800, + pave_t1: 20, + pave_t2: 260, + pave_t3: 150 +}; + +window.ugData = { + left: { + type: 'T1', + vw1: 300, vw2: 300, vh: 300, + lw: 400, lh: 60, gap: 50, + ww1: 144, ww2: 36, ww3: 120, + wh1: 360, wh2: 175, wh3: 125, + bcw: 650, bch: 500, dw: 100, dh: 150, + bw1: 125, bw2: 300, bw3: 300, + bd1: 50, bd2: 300, bd3: 50, + pipeD: 300 + }, + right: { + type: 'T1', + vw1: 300, vw2: 300, vh: 300, + lw: 400, lh: 60, gap: 50, + ww1: 144, ww2: 36, ww3: 120, + wh1: 360, wh2: 175, wh3: 125, + bcw: 650, bch: 500, dw: 100, dh: 150, + bw1: 125, bw2: 300, bw3: 300, + bd1: 50, bd2: 300, bd3: 50, + pipeD: 300 + } +}; + +window.rawDxfEntities = []; +window.lastGeneratedMeshesForExport = []; +window.activeViewTab = '3d'; +window.view2D = { panX: 0, panY: 0, zoom: 1, dragging: false, lastX: 0, lastY: 0 }; + +const $ = (id) => document.getElementById(id); + +// ───────────────────────────────────────────────────────────────────────────── +// 1. 전구(💡) 표시 레이어 컨트롤러 함수 +// ───────────────────────────────────────────────────────────────────────────── +function update3DObjectButtons() { + const keys = ['shotcrete', 'lining', 'pavement', 'lids', 'blindDitch', 'drainPipe', 'lanes', 'rims']; + let allOn = true; + let anyOn = false; + const isLight = (window.current3DTheme === 'light'); + + keys.forEach(k => { + const isOn = window.current3DVisibility && window.current3DVisibility[k] !== false; + if (isOn) anyOn = true; + else allOn = false; + + const item = $('item_layer_' + k); + const bulb = $('bulb_icon_' + k); + const badge = $('badge_status_' + k); + const label = $('label_layer_' + k); + + if (bulb && badge) { + if (isOn) { + bulb.style.filter = 'drop-shadow(0 0 5px #fbbf24)'; + bulb.style.opacity = '1.0'; + badge.innerText = 'ON'; + badge.style.background = '#10b981'; + badge.style.color = '#ffffff'; + if (label) { + label.style.color = isLight ? '#1f2937' : '#ffffff'; + label.style.fontWeight = 'bold'; + } + if (item) { + item.style.background = isLight ? 'rgba(0, 0, 0, 0.05)' : 'rgba(255, 255, 255, 0.08)'; + } + } else { + bulb.style.filter = 'grayscale(100%) brightness(40%)'; + bulb.style.opacity = '0.32'; + badge.innerText = 'OFF'; + badge.style.background = '#4b5563'; + badge.style.color = '#9ca3af'; + if (label) { + label.style.color = '#9ca3af'; + label.style.fontWeight = 'normal'; + } + if (item) { + item.style.background = 'transparent'; + } + } + } + }); + + const bulbAll = $('bulb_icon_all'); + const badgeAll = $('badge_status_all'); + const labelAll = $('label_layer_all'); + if (labelAll) { + labelAll.style.color = isLight ? '#111827' : '#ffffff'; + } + if (bulbAll && badgeAll) { + if (allOn) { + bulbAll.style.filter = 'drop-shadow(0 0 7px #f59e0b)'; + bulbAll.style.opacity = '1.0'; + badgeAll.innerText = 'ON'; + badgeAll.style.background = '#10b981'; + badgeAll.style.color = '#ffffff'; + } else if (!anyOn) { + bulbAll.style.filter = 'grayscale(100%) brightness(35%)'; + bulbAll.style.opacity = '0.32'; + badgeAll.innerText = 'OFF'; + badgeAll.style.background = '#4b5563'; + badgeAll.style.color = '#9ca3af'; + } else { + bulbAll.style.filter = 'drop-shadow(0 0 3px #f59e0b) brightness(85%)'; + bulbAll.style.opacity = '0.75'; + badgeAll.innerText = 'PART'; + badgeAll.style.background = '#d97706'; + badgeAll.style.color = '#ffffff'; + } + } +} + +function toggle3DObject(key) { + if (!window.current3DVisibility || window.current3DVisibility[key] === undefined) return; + window.current3DVisibility[key] = !window.current3DVisibility[key]; + update3DObjectButtons(); + buildCurrentSection3DPlot(true); + if (window.activeViewTab === '2d') render2DSection(); +} + +function setAll3DObjects(val) { + if (!window.current3DVisibility) return; + Object.keys(window.current3DVisibility).forEach(k => { + window.current3DVisibility[k] = !!val; + }); + update3DObjectButtons(); + buildCurrentSection3DPlot(true); + if (window.activeViewTab === '2d') render2DSection(); +} + +function toggleAll3DObjects() { + if (!window.current3DVisibility) return; + const keys = ['shotcrete', 'lining', 'pavement', 'lids', 'blindDitch', 'drainPipe', 'lanes', 'rims']; + const allOn = keys.every(k => window.current3DVisibility[k] !== false); + setAll3DObjects(!allOn); +} + +function toggle3DLayerPanelMinimize() { + const body = $('placement_3d_layer_items_wrapper'); + const btn = $('btn_toggle_layer_panel'); + if (!body) return; + const isHidden = (body.style.display === 'none'); + body.style.display = isHidden ? 'flex' : 'none'; + if (btn) btn.innerText = isHidden ? '−' : '+'; +} + +// ───────────────────────────────────────────────────────────────────────────── +// 2. 테마(다크/라이트), 시점, 길이 제어 +// ───────────────────────────────────────────────────────────────────────────── +function set3DTheme(theme) { + window.current3DTheme = theme; + const isDark = (theme === 'dark'); + const darkBtn = $('btnThemeDark'); + const lightBtn = $('btnThemeLight'); + if (darkBtn && lightBtn) { + darkBtn.classList.toggle('active', isDark); + lightBtn.classList.toggle('active', !isDark); + } + + const bgColor = isDark ? '#111827' : '#ffffff'; + const legendFontColor = isDark ? '#e5e7eb' : '#1f2937'; + const legendBgColor = isDark ? 'rgba(17, 24, 39, 0.75)' : 'rgba(255, 255, 255, 0.85)'; + + const viewport = $('viewport'); + if (viewport) { + viewport.style.backgroundColor = bgColor; + } + + const layerPanel = $('placement_3d_layer_panel'); + if (layerPanel) { + if (isDark) { + layerPanel.style.background = 'rgba(15, 23, 42, 0.88)'; + layerPanel.style.borderColor = 'rgba(255, 255, 255, 0.18)'; + layerPanel.style.color = '#f3f4f6'; + layerPanel.style.boxShadow = '0 8px 24px rgba(0,0,0,0.5)'; + } else { + layerPanel.style.background = 'rgba(255, 255, 255, 0.94)'; + layerPanel.style.borderColor = 'rgba(0, 0, 0, 0.18)'; + layerPanel.style.color = '#1f2937'; + layerPanel.style.boxShadow = '0 8px 24px rgba(0,0,0,0.15)'; + } + } + const sliderBox = $('opacitySliderBox'); + if (sliderBox) { + if (isDark) { + sliderBox.style.background = '#171e28'; + sliderBox.style.borderColor = '#2b3542'; + sliderBox.style.color = '#cbd5e1'; + } else { + sliderBox.style.background = '#f8fafc'; + sliderBox.style.borderColor = '#cbd5e1'; + sliderBox.style.color = '#1f2937'; + } + } + update3DObjectButtons(); + + const plotDiv = $('placement_3d_plot'); + if (plotDiv && typeof Plotly !== 'undefined' && plotDiv.data) { + try { + Plotly.relayout(plotDiv, { + 'paper_bgcolor': bgColor, + 'plot_bgcolor': bgColor, + 'scene.bgcolor': bgColor, + 'legend.font.color': legendFontColor, + 'legend.bgcolor': legendBgColor + }); + } catch (e) { + buildCurrentSection3DPlot(true); + } + } + if (window.activeViewTab === '2d') render2DSection(); +} + +function toggle3DOuterShell() { + if (window.current3DOpacity >= 0.85) { + window.current3DOpacity = 0.4; + } else if (window.current3DOpacity > 0.15) { + window.current3DOpacity = 0.1; + } else { + window.current3DOpacity = 1.0; + } + window.current3DOuterOpacity = window.current3DOpacity; + const pct = Math.round(window.current3DOpacity * 100); + const slider = $('opacitySlider'); + const valEl = $('opacityVal'); + if (slider) slider.value = pct; + if (valEl) valEl.textContent = `${pct}%`; + update3DObjectButtons(); + buildCurrentSection3DPlot(true); +} + +function set3DCameraView(mode) { + window.current3DViewMode = mode; + const plotEl = $('placement_3d_plot'); + if (!plotEl || !plotEl.layout || !plotEl.layout.scene) return; + + const newCam = (mode === 'inside') + ? { + eye: { x: 0, y: -0.05, z: 0.05 }, + center: { x: 0, y: 0.5, z: 0.05 }, + up: { x: 0, y: 0, z: 1 } + } + : { + eye: { x: 1.15, y: -1.35, z: 0.85 }, + center: { x: 0, y: 0, z: 0.1 }, + up: { x: 0, y: 0, z: 1 } + }; + Plotly.relayout(plotEl, { 'scene.camera': newCam }); +} + +// ───────────────────────────────────────────────────────────────────────────── +// 3. DXF 도면 자동 제원 추출 엔진 (cad_import.js 기반 + 임의 좌표계 호환) +// ───────────────────────────────────────────────────────────────────────────── +function extractParamsFromDXF(dxf, sourceName = '도면') { + if (!dxf || !dxf.entities) { + throw new Error('유효한 DXF 엔티티가 없습니다.'); + } + + // ★ 1. 도곽(Border Frame) 자동 감지 및 도곽 내부 격리 (비교안/다른 단면 섞임 원천 차단) + function detectFrameBBox() { + const frameInserts = dxf.entities.filter( + e => e && e.type === 'INSERT' && /도곽|A[0-3]|KECH|도로공사|BORDER|TITLE|FRAME/i.test(e.name || '') + ); + if (frameInserts.length === 0) return null; + + frameInserts.sort((a, b) => (b.xScale || 1) - (a.xScale || 1)); + const ins = frameInserts[0]; + const p = ins.position || { x: 0, y: 0 }; + const scale = ins.xScale || 50; + + let w = 841 * scale, h = 594 * scale; + const blk = dxf.blocks ? dxf.blocks[ins.name] : null; + if (blk && blk.entities) { + let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity; + blk.entities.forEach(e => { + const pts = e.vertices || e.points || (e.center ? [e.center] : (e.position ? [e.position] : [])); + pts.forEach(pt => { + minX = Math.min(minX, pt.x); maxX = Math.max(maxX, pt.x); + minY = Math.min(minY, pt.y); maxY = Math.max(maxY, pt.y); + }); + }); + if (Number.isFinite(minX) && Number.isFinite(maxX) && Number.isFinite(minY) && Number.isFinite(maxY)) { + return { + minX: p.x + minX * scale - 200, + maxX: p.x + maxX * scale + 200, + minY: p.y + minY * scale - 200, + maxY: p.y + maxY * scale + 200 + }; + } + } + + return { + minX: p.x - 200, + maxX: p.x + w + 200, + minY: p.y - 200, + maxY: p.y + h + 200 + }; + } + + const frameBBox = detectFrameBBox(); + const targetEntities = frameBBox + ? dxf.entities.filter(e => { + const p = e.center || e.position || (e.vertices && e.vertices[0]) || (e.points && e.points[0]); + if (!p) return false; + return p.x >= frameBBox.minX && p.x <= frameBBox.maxX && p.y >= frameBBox.minY && p.y <= frameBBox.maxY; + }) + : dxf.entities; + + // ★ 2. bulge(원호) 세그먼트를 ARC로 변환하는 기하학 엔진 + function bulgeToArc(p1, p2, bulge, layer) { + const theta = 4 * Math.atan(bulge); + const dx = p2.x - p1.x; + const dy = p2.y - p1.y; + const chord = Math.hypot(dx, dy); + const radius = Math.abs(chord / (2 * Math.sin(theta / 2))); + + const midX = (p1.x + p2.x) / 2; + const midY = (p1.y + p2.y) / 2; + const dCenter = (chord / 2) / Math.tan(theta / 2); + const perpX = -dy / chord; + const perpY = dx / chord; + const sign = bulge > 0 ? 1 : -1; + + const cx = midX + sign * dCenter * perpX; + const cy = midY + sign * dCenter * perpY; + + let startAngle = Math.atan2(p1.y - cy, p1.x - cx); + let endAngle = Math.atan2(p2.y - cy, p2.x - cx); + if (bulge < 0) { + const tmp = startAngle; + startAngle = endAngle; + endAngle = tmp; + } + if (startAngle < 0) startAngle += 2 * Math.PI; + if (endAngle < 0) endAngle += 2 * Math.PI; + + return { + type: 'ARC', + layer: layer, + center: { x: cx, y: cy, z: 0 }, + radius: radius, + startAngle: startAngle, + endAngle: endAngle + }; + } + + const allLines = []; + targetEntities.forEach(e => { + if (e && e.type === 'LINE' && e.vertices && e.vertices.length >= 2) { + allLines.push(e); + } else if (e && (e.type === 'LWPOLYLINE' || e.type === 'POLYLINE') && e.vertices && e.vertices.length >= 2) { + for (let i = 0; i < e.vertices.length - 1; i++) { + allLines.push({ + type: 'LINE', + layer: e.layer, + vertices: [e.vertices[i], e.vertices[i + 1]] + }); + } + } + }); + + const allArcs = []; + targetEntities.forEach(e => { + if (e && e.type === 'ARC' && e.radius > 500 && e.center) { + allArcs.push(e); + } else if (e && (e.type === 'LWPOLYLINE' || e.type === 'POLYLINE') && e.vertices) { + for (let i = 0; i < e.vertices.length - 1; i++) { + const v1 = e.vertices[i], v2 = e.vertices[i + 1]; + if (v1.bulge && Math.abs(v1.bulge) > 1e-4) { + allArcs.push(bulgeToArc(v1, v2, v1.bulge, e.layer)); + } + } + } + }); + + if (allArcs.length === 0) { + throw new Error('도면에서 터널 아치(ARC) 객체를 찾지 못했습니다.'); + } + + // 1) 터널 천단 아치 그룹(동심원 아치 패밀리) 및 표준단면 중심 X 결정 + // 도면 내 '표준단면' 타이틀 텍스트 위치 탐색 + const titleTexts = []; + if (targetEntities) { + targetEntities.forEach(e => { + if (e && (e.type === 'TEXT' || e.type === 'MTEXT') && e.text) { + const str = e.text.trim(); + if (str.includes('표준단면') && str.length <= 15) { + titleTexts.push(e.startPoint || e.position || e.insertionPoint || e); + } + } + }); + } + + // 상부를 향하는 천단 대반경 아치 탐색 (각도 약 15°~65° 시작, 115°~165° 종료, R >= 4500) + const candidateCrowns = allArcs.filter(a => { + let saDeg = (a.startAngle * 180) / Math.PI; + let eaDeg = (a.endAngle * 180) / Math.PI; + if (saDeg < 0) saDeg += 360; + if (eaDeg < 0) eaDeg += 360; + const isUpper = (saDeg >= 15 && saDeg <= 65 && eaDeg >= 115 && eaDeg <= 165); + return isUpper && a.radius >= 4500; + }); + + let mainCrown = null; + if (candidateCrowns.length > 0) { + // 1) 여러 단면(상행/하행 등)이 동시에 그려진 경우 독립 단면 후보 그룹화 + const distinctSections = []; + candidateCrowns.forEach(cArc => { + if (!distinctSections.some(sec => Math.abs(sec.arc.center.x - cArc.center.x) < 3000)) { + distinctSections.push({ arc: cArc, center: cArc.center }); + } + }); + + if (distinctSections.length > 1) { + // 각 단면 영역별 '도로' 중심선과 '터널' 중심선 위치 관계 조사 + // ★ 사용자 핵심 규칙: 도로중심이 터널중심보다 왼쪽에 있는 단면(X_road < X_tunnel) 우선 선택! + distinctSections.forEach(sec => { + const cX = sec.center.x, cY = sec.center.y; + let roadX = null, tunnelX = null; + if (targetEntities) { + targetEntities.forEach(e => { + if (e && (e.type === 'TEXT' || e.type === 'MTEXT') && e.text) { + const str = (e.text || '').replace(/\\[A-Za-z0-9_]+;/g, '').replace(/[{}\\]/g, '').trim(); + const p = e.startPoint || e.position; + if (p && Math.abs(p.x - cX) < 10000 && p.y >= cY - 4000) { + if (str.includes('도로') && (str.includes('L') || str.includes('중심선'))) roadX = p.x; + if (str.includes('터널') && (str.includes('L') || str.includes('중심선'))) tunnelX = p.x; + } + } + }); + } + sec.roadX = roadX; + sec.tunnelX = tunnelX; + sec.isRoadLeftOfTunnel = (roadX !== null && tunnelX !== null) ? (roadX < tunnelX) : null; + }); + + const roadLeftSec = distinctSections.find(sec => sec.isRoadLeftOfTunnel === true); + if (roadLeftSec) { + mainCrown = roadLeftSec.arc; + } + } + + if (!mainCrown) { + if (titleTexts.length > 0) { + // 타이틀 텍스트의 X와 가장 가까운 천단 아치 선택 + candidateCrowns.sort((a, b) => { + const distA = Math.min(...titleTexts.map(t => Math.abs(a.center.x - (t.x || 0)))); + const distB = Math.min(...titleTexts.map(t => Math.abs(b.center.x - (t.x || 0)))); + return distA - distB; + }); + } else { + // 가장 빈도 높은 중심 X 또는 표준 도로폭 대반경 선택 + candidateCrowns.sort((a, b) => b.radius - a.radius); + } + mainCrown = candidateCrowns[0]; + } + } + + let targetCenterX = mainCrown ? mainCrown.center.x : null; + let targetCenterY = mainCrown ? mainCrown.center.y : null; + + if (targetCenterX === null) { + // 수직선 탐색 fallback + const rightSideVertLines = allLines.filter( + l => Math.abs(l.vertices[0].x - l.vertices[1].x) < 1 && l.vertices[0].x > 10 + ); + if (rightSideVertLines.length > 0) { + rightSideVertLines.sort((a, b) => Math.abs(b.vertices[0].y - b.vertices[1].y) - Math.abs(a.vertices[0].y - a.vertices[1].y)); + targetCenterX = rightSideVertLines[0].vertices[0].x; + } + } + + if (targetCenterX === null) { + throw new Error('터널 표준단면의 중심 X 좌표를 찾을 수 없습니다.'); + } + + // ★ 핵심 교정: 애초에 X 좌표가 다른 객체/도면 프레임이 섞이지 않도록 단면 Bounding Box 격리! + // 또한 하부 추가 작도 단면(종배수관 Ø400 등)이 섞이지 않도록 Y 범위도 본선 단면 구조물 영역으로 한정! + const bboxX1 = targetCenterX - 11000; + const bboxX2 = targetCenterX + 11000; + const bboxY1 = targetCenterY !== null ? (targetCenterY - Math.max(4500, mainCrown.radius * 0.7)) : -Infinity; + const bboxY2 = targetCenterY !== null ? (targetCenterY + mainCrown.radius + 3500) : Infinity; + const secLines = allLines.filter( + l => l.vertices[0].x >= bboxX1 && l.vertices[0].x <= bboxX2 && l.vertices[1].x >= bboxX1 && l.vertices[1].x <= bboxX2 && + (targetCenterY === null || (l.vertices[0].y >= bboxY1 && l.vertices[1].y >= bboxY1 && l.vertices[0].y <= bboxY2 && l.vertices[1].y <= bboxY2)) + ); + const secArcs = allArcs.filter(a => + a.center.x >= bboxX1 && a.center.x <= bboxX2 && + (targetCenterY === null || (a.center.y >= bboxY1 - 2000 && a.center.y <= bboxY2)) + ); + + // 천단 동심원 패밀리 추출 (동일 중심 X, Y 기준) + let crownFamily = secArcs.filter( + a => Math.abs(a.center.x - targetCenterX) < 150 && (targetCenterY === null || Math.abs(a.center.y - targetCenterY) < 150) && a.radius >= 4500 + ); + crownFamily.sort((a, b) => a.radius - b.radius); + + if (crownFamily.length === 0) { + crownFamily = [mainCrown]; + } + + const innerCrown = crownFamily[0]; + // 라이닝 외면: 내면보다 150mm 이상 큰 첫 번째 호 + const outerCrown = crownFamily.find(a => a.radius >= innerCrown.radius + 150) || null; + // 숏크리트 외면: 라이닝 외면보다 50mm 이상 큰 호 + const shotcreteCrown = outerCrown ? crownFamily.find(a => a.radius >= outerCrown.radius + 50) : null; + + const r1 = Math.round(innerCrown.radius); + const eValAbs = innerCrown.center.x; + const h1Abs = innerCrown.center.y; + + let lining_t = outerCrown ? Math.round(outerCrown.radius - innerCrown.radius) : 300; + let shotcrete_t = (outerCrown && shotcreteCrown) ? Math.round(shotcreteCrown.radius - outerCrown.radius) : 150; + + // 천단 중심각 theta1 추출 + let spanDeg = ((innerCrown.endAngle - innerCrown.startAngle) * 180) / Math.PI; + if (spanDeg <= 0) spanDeg += 360; + const theta1 = (spanDeg >= 60 && spanDeg <= 165) ? Math.round(spanDeg) : 120; + + function toDeg(rad) { + return ((rad * 180) / Math.PI + 360) % 360; + } + function getPts(a) { + return [ + { x: a.center.x + a.radius * Math.cos(a.startAngle), y: a.center.y + a.radius * Math.sin(a.startAngle) }, + { x: a.center.x + a.radius * Math.cos(a.endAngle), y: a.center.y + a.radius * Math.sin(a.endAngle) } + ]; + } + function dist(p1, p2) { + return Math.hypot(p1.x - p2.x, p1.y - p2.y); + } + + const saCrown = toDeg(innerCrown.startAngle); + const eaCrown = toDeg(innerCrown.endAngle); + + // 치수표(테이블) 자동 파싱 함수 (도면 치수와 CAD 엔티티 크로스체크용) + function parseDimensionTable(slopePercent) { + const texts = dxf.entities.filter(e => e && (e.type === 'TEXT' || e.type === 'MTEXT') && e.text); + const title = texts.find(t => t.text.replace(/\s+/g, '').includes('치수표')); + if (!title) return null; + + const tPos = title.startPoint || title.position || title.insertionPoint || {}; + const nearby = texts.filter(t => { + const p = t.startPoint || t.position || t.insertionPoint || {}; + return Math.abs(p.x - tPos.x) < 15000 && p.y >= tPos.y - 12000 && p.y <= tPos.y + 2000; + }); + + const targetSlopeStr = String(Math.round(slopePercent)); + const slopeRows = nearby.filter(t => { + const str = t.text.trim(); + return str === targetSlopeStr || str === `${targetSlopeStr}%` || str === `${targetSlopeStr}.0`; + }); + if (slopeRows.length === 0) return null; + + const r1Header = nearby.find(t => t.text.trim() === 'R1'); + const r2Header = nearby.find(t => t.text.trim() === 'R2'); + const r3Header = nearby.find(t => t.text.trim() === 'R3'); + const r2pHeader = nearby.find(t => t.text.trim() === "R2'"); + const r3pHeader = nearby.find(t => t.text.trim() === "R3'"); + if (!r1Header) return null; + + const targetSlopeRow = slopeRows.find(sr => { + const p = sr.startPoint || sr.position || sr.insertionPoint || {}; + const r1p = r1Header.startPoint || r1Header.position || r1Header.insertionPoint || {}; + return Math.abs(p.y - r1p.y) < 6000 && p.y < r1p.y; + }); + if (!targetSlopeRow) return null; + + const rowY = (targetSlopeRow.startPoint || targetSlopeRow.position || targetSlopeRow.insertionPoint).y; + const rowCells = nearby.filter(t => { + const p = t.startPoint || t.position || t.insertionPoint || {}; + return Math.abs(p.y - rowY) < 100; + }); + + function getVal(header) { + if (!header) return null; + const hp = header.startPoint || header.position || header.insertionPoint || {}; + const match = rowCells.find(c => { + const cp = c.startPoint || c.position || c.insertionPoint || {}; + return Math.abs(cp.x - hp.x) < 300; + }); + if (!match) return null; + const val = parseFloat(match.text.trim()); + return isNaN(val) ? null : Math.round(val * 1000); + } + + return { + r1: getVal(r1Header), + r2: getVal(r2Header), + r4: getVal(r3Header), // 도면 치수표의 R3 = 좌측 라이닝 외면 + r3: getVal(r2pHeader), // 도면 치수표의 R2' = 우측 라이닝 내면 + r5: getVal(r3pHeader) // 도면 치수표의 R3' = 우측 라이닝 외면 + }; + } + + // 임시 편경사(-2%) 기준으로 치수표 우선 검색 + let crossTable = parseDimensionTable(-2); + + // ★ 접선 연속 각도 기반 벽부 호 탐색 + 방수막(10mm 오프셋 호) 자동 배제 + 치수표 크로스체크 + // - 좌측 벽부 호(R2, R4, 좌측 숏크리트): 천단 끝각도(eaCrown, 약 140°)에서 접선으로 시작 + // - 우측 벽부 호(R3, R5, 우측 숏크리트): 천단 시작각도(saCrown, 약 40°)에서 접선으로 종료 + function findWallArcByAngle(targetPt, side, expectedRadius = null, excludeArcs = []) { + let cands = secArcs.filter(a => { + if (excludeArcs.includes(a)) return false; + const pts = getPts(a); + if (dist(pts[0], targetPt) >= 25 && dist(pts[1], targetPt) >= 25) return false; + + const sa = toDeg(a.startAngle), ea = toDeg(a.endAngle); + if (side === 'left') { + return Math.abs(sa - eaCrown) <= 2.0 || Math.abs(ea - eaCrown) <= 2.0; + } else { + return Math.abs(ea - saCrown) <= 2.0 || Math.abs(sa - saCrown) <= 2.0; + } + }); + + if (cands.length === 0) return null; + + // ★ 방수막(두께 5~15mm, 통상 10mm) 배제: 동일 시작/끝점을 공유하는 호 쌍이 있는 경우 + // 콘크리트 외면보다 10mm 큰 바깥쪽 호는 방수막이므로 배제하고 안쪽 호(콘크리트 외면)를 선택 + if (cands.length >= 2) { + cands.sort((a, b) => a.radius - b.radius); + // 만약 두 호의 반경 차이가 5mm ~ 18mm 사이라면, 더 큰 호는 방수막! + const filtered = []; + for (let i = 0; i < cands.length; i++) { + const isMembrane = (i > 0 && (cands[i].radius - cands[i - 1].radius) >= 5 && (cands[i].radius - cands[i - 1].radius) <= 18); + if (!isMembrane) { + filtered.push(cands[i]); + } + } + if (filtered.length > 0) cands = filtered; + } + + // 치수표(테이블) 크로스체크가 가능한 경우 테이블 치수와 가장 일치하는 호 우선 + if (expectedRadius !== null) { + cands.sort((a, b) => Math.abs(a.radius - expectedRadius) - Math.abs(b.radius - expectedRadius)); + return cands[0]; + } + + cands.sort((a, b) => { + const ptsA = getPts(a), ptsB = getPts(b); + const minDA = Math.min(dist(ptsA[0], targetPt), dist(ptsA[1], targetPt)); + const minDB = Math.min(dist(ptsB[0], targetPt), dist(ptsB[1], targetPt)); + return minDA - minDB; + }); + return cands[0]; + } + + // R1 양 끝점과 연결된 R2, R3 (접선 각도 조건으로 탐색) + const r1Pts = getPts(innerCrown); + const r1LeftPt = r1Pts[0].x < r1Pts[1].x ? r1Pts[0] : r1Pts[1]; + const r1RightPt = r1Pts[0].x < r1Pts[1].x ? r1Pts[1] : r1Pts[0]; + + let r2 = null, r3 = null, r4 = null, r5 = null; + const usedArcs = [innerCrown]; + + const r2Arc = findWallArcByAngle(r1LeftPt, 'left', crossTable ? crossTable.r2 : null, usedArcs); + if (r2Arc) { r2 = Math.round(r2Arc.radius); usedArcs.push(r2Arc); } + + const r3Arc = findWallArcByAngle(r1RightPt, 'right', crossTable ? crossTable.r3 : null, usedArcs); + if (r3Arc) { r3 = Math.round(r3Arc.radius); usedArcs.push(r3Arc); } + + if (outerCrown) { + usedArcs.push(outerCrown); + const r1pPts = getPts(outerCrown); + const r1pLeftPt = r1pPts[0].x < r1pPts[1].x ? r1pPts[0] : r1pPts[1]; + const r1pRightPt = r1pPts[0].x < r1pPts[1].x ? r1pPts[1] : r1pPts[0]; + + const r4Arc = findWallArcByAngle(r1pLeftPt, 'left', crossTable ? crossTable.r4 : null, usedArcs); + if (r4Arc) { r4 = Math.round(r4Arc.radius); usedArcs.push(r4Arc); } + + const r5Arc = findWallArcByAngle(r1pRightPt, 'right', crossTable ? crossTable.r5 : null, usedArcs); + if (r5Arc) { r5 = Math.round(r5Arc.radius); usedArcs.push(r5Arc); } + } + + // 숏크리트 외면 호 탐색을 통한 두께 검증 + if (shotcreteCrown && r4) { + usedArcs.push(shotcreteCrown); + const scPts = getPts(shotcreteCrown); + const scLeftPt = scPts[0].x < scPts[1].x ? scPts[0] : scPts[1]; + const scLeftArc = findWallArcByAngle(scLeftPt, 'left', usedArcs); + if (scLeftArc) { + shotcrete_t = Math.round(scLeftArc.radius - r4); + } + } + + // 2) 도로 포장면(Pavement) 및 다층 두께 탐색 (secLines 내에서 탐색!) + let m3 = -0.02; + let pavementLeftX = null; + let pavementRightX = null; + let mainPv = null; + let pave_t1_val = 20, pave_t2_val = 260, pave_t3_val = 150; + + const roadCandidates = secLines.filter(l => { + const layer = (l.layer || '').toUpperCase(); + if (layer.includes('EXCV') || layer.includes('굴착') || layer.includes('CNTL') || layer.includes('HIDL') || layer.includes('DIM') || layer.includes('LEAL') || layer.includes('TEXT')) { + return false; + } + const p1 = l.vertices[0], p2 = l.vertices[1]; + const dx = Math.abs(p2.x - p1.x); + const dy = Math.abs(p2.y - p1.y); + if (dx < 4000 || dy > dx * 0.1) return false; + const midX = (p1.x + p2.x) / 2; + const midY = (p1.y + p2.y) / 2; + const crownTopY = h1Abs + r1; + // 터널 도로 포장면의 높이는 천단 최상단(crownTopY)으로부터 5.0m ~ 11.5m 아래에 위치! + return Math.abs(midX - targetCenterX) < 4500 && midY >= crownTopY - 11500 && midY <= crownTopY - 5000; + }); + + const verticalWalls = secLines.filter(l => { + const layer = (l.layer || '').toUpperCase(); + if (layer.includes('DIM') || layer.includes('LEAL') || layer.includes('ANNO') || layer.includes('TEXT')) return false; + const dx = Math.abs(l.vertices[0].x - l.vertices[1].x); + const dy = Math.abs(l.vertices[0].y - l.vertices[1].y); + return dx <= dy * 0.1 && dy <= 1200 && dy >= 40; + }); + + const TOL = 15; + const isTouching = (p, l) => + Math.hypot(p.x - l.vertices[0].x, p.y - l.vertices[0].y) <= TOL || + Math.hypot(p.x - l.vertices[1].x, p.y - l.vertices[1].y) <= TOL; + + // (0,0) 근처를 지나는 포장선 우선, 없으면 배수구 수직벽과 맞닿아 있는 상단 표층선 선택 + let centerPvs = roadCandidates.filter(l => { + const p1 = l.vertices[0], p2 = l.vertices[1]; + const crossProduct = p1.x * p2.y - p2.x * p1.y; + if (Math.abs(crossProduct) > 120) return false; + const minX = Math.min(p1.x, p2.x) - 5, maxX = Math.max(p1.x, p2.x) + 5; + const minY = Math.min(p1.y, p2.y) - 5, maxY = Math.max(p1.y, p2.y) + 5; + return (0 >= minX && 0 <= maxX && 0 >= minY && 0 <= maxY); + }); + + if (centerPvs.length === 0 && roadCandidates.length > 0) { + // 1) 수직벽(배수구 턱)과 접촉하는 선분을 최우선 순위로, 그 중 Y가 가장 높은 선(표층) 선택 + const sortedRoads = [...roadCandidates].sort((a, b) => { + const touchA = verticalWalls.filter(w => isTouching(a.vertices[0], w) || isTouching(a.vertices[1], w)).length; + const touchB = verticalWalls.filter(w => isTouching(b.vertices[0], w) || isTouching(b.vertices[1], w)).length; + if (touchA !== touchB) return touchB - touchA; + return ((b.vertices[0].y + b.vertices[1].y) / 2) - ((a.vertices[0].y + a.vertices[1].y) / 2); + }); + centerPvs = [sortedRoads[0]]; + } + + let fullPavementLines = []; + if (centerPvs.length > 0) { + mainPv = centerPvs[0]; + const centerSlopes = centerPvs.map(l => (l.vertices[1].y - l.vertices[0].y) / (l.vertices[1].x - l.vertices[0].x)); + if (Number.isFinite(centerSlopes[0]) && Math.abs(centerSlopes[0]) <= 0.08) { + m3 = Math.round(centerSlopes[0] * 1000) / 1000; + } + + const collinear = secLines.filter(l => { + const layer = (l.layer || '').toUpperCase(); + if (layer.includes('DIM') || layer.includes('LEAL') || layer.includes('ANNO') || layer.includes('TEXT') || layer.includes('TABL')) return false; + const dx = l.vertices[1].x - l.vertices[0].x; + if (Math.abs(dx) < 10) return false; + const lSlope = (l.vertices[1].y - l.vertices[0].y) / dx; + for (let i = 0; i < centerPvs.length; i++) { + const cPv = centerPvs[i]; + const cSlope = centerSlopes[i]; + if (Math.abs(lSlope - cSlope) < 0.005) { + const cYInt = cPv.vertices[0].y - cSlope * cPv.vertices[0].x; + const lYInt = l.vertices[0].y - lSlope * l.vertices[0].x; + if (Math.abs(cYInt - lYInt) < 5) return true; + } + } + return false; + }); + + const leftWalls = []; + const rightWalls = []; + for (const l of collinear) { + for (const w of verticalWalls) { + if (isTouching(l.vertices[0], w)) { + const x = l.vertices[0].x; + if (x < eValAbs) leftWalls.push(x); + else rightWalls.push(x); + } + if (isTouching(l.vertices[1], w)) { + const x = l.vertices[1].x; + if (x < eValAbs) leftWalls.push(x); + else rightWalls.push(x); + } + } + } + + if (leftWalls.length > 0) pavementLeftX = Math.max(...leftWalls); + if (rightWalls.length > 0) pavementRightX = Math.min(...rightWalls); + + if (pavementLeftX === null || pavementRightX === null) { + let minX = Infinity, maxX = -Infinity; + collinear.forEach(l => { + minX = Math.min(minX, l.vertices[0].x, l.vertices[1].x); + maxX = Math.max(maxX, l.vertices[0].x, l.vertices[1].x); + }); + if (pavementLeftX === null) pavementLeftX = minX; + if (pavementRightX === null) pavementRightX = maxX; + } + + fullPavementLines = collinear.filter(l => { + const cx = (l.vertices[0].x + l.vertices[1].x) / 2; + return cx >= pavementLeftX - 100 && cx <= pavementRightX + 100; + }); + + // 포장층 하단 평행선들로 t1, t2, t3 추출 + const parallelLines = secLines.filter(l => { + const layer = (l.layer || '').toUpperCase(); + if (layer.includes('DIM') || layer.includes('LEAL') || layer.includes('ANNO') || layer.includes('TEXT') || layer.includes('TABL')) return false; + const cx = (l.vertices[0].x + l.vertices[1].x) / 2; + if (cx < pavementLeftX - 100 || cx > pavementRightX + 100) return false; + const dx = l.vertices[1].x - l.vertices[0].x; + if (Math.abs(dx) < 1500) return false; + const lSlope = (l.vertices[1].y - l.vertices[0].y) / dx; + return centerSlopes.some(cs => Math.abs(lSlope - cs) <= 0.008); + }); + + const diffs = parallelLines + .filter(l => !fullPavementLines.includes(l)) + .map(l => { + const cx = (l.vertices[0].x + l.vertices[1].x) / 2; + const cy = (l.vertices[0].y + l.vertices[1].y) / 2; + const cSlope = centerSlopes[0]; + const targetY = mainPv.vertices[0].y + cSlope * (cx - mainPv.vertices[0].x); + return targetY - cy; + }) + .filter(d => d > 15 && d < 1000) + .sort((a, b) => a - b); + + // 중복 거리 병합 + const uniqueDiffs = []; + diffs.forEach(d => { + if (uniqueDiffs.length === 0 || Math.abs(uniqueDiffs[uniqueDiffs.length - 1] - d) > 15) { + uniqueDiffs.push(d); + } + }); + + if (uniqueDiffs.length >= 3) { + pave_t1_val = Math.round(uniqueDiffs[0] / 10) * 10; + pave_t2_val = Math.round((uniqueDiffs[1] - uniqueDiffs[0]) / 10) * 10; + pave_t3_val = Math.round((uniqueDiffs[2] - uniqueDiffs[1]) / 10) * 10; + } else if (uniqueDiffs.length === 2) { + // 도면에 2개 층만 작도된 경우 (예: 콘크리트 슬래브 t1=280mm + 필터층 t2=150mm) + pave_t1_val = Math.round(uniqueDiffs[0] / 10) * 10; + pave_t2_val = Math.round((uniqueDiffs[1] - uniqueDiffs[0]) / 10) * 10; + pave_t3_val = 0; + } else if (uniqueDiffs.length === 1) { + pave_t1_val = Math.round(uniqueDiffs[0] / 10) * 10; + pave_t2_val = 0; + pave_t3_val = 0; + } + } + + // 3) 공동구/배수구(ugData) 추출 + function isSamePt(p1, p2) { + return Math.abs(p1.x - p2.x) < 1.0 && Math.abs(p1.y - p2.y) < 1.0; + } + function getOtherPt(l, targetPt) { + const p1 = l.vertices[0], p2 = l.vertices[1]; + return isSamePt(p1, targetPt) ? p2 : p1; + } + function findConnectedLines(targetPt, lines, direction) { + return lines + .filter(l => { + const p1 = l.vertices[0], p2 = l.vertices[1]; + const isP1 = isSamePt(p1, targetPt); + const isP2 = isSamePt(p2, targetPt); + if (!isP1 && !isP2) return false; + const otherPt = isP1 ? p2 : p1; + const dx = Math.abs(otherPt.x - targetPt.x); + const dy = Math.abs(otherPt.y - targetPt.y); + if (direction === 'left' || direction === 'right') { + if (dy > dx * 0.5) return false; + if (direction === 'left' && otherPt.x > targetPt.x - 2) return false; + if (direction === 'right' && otherPt.x < targetPt.x + 2) return false; + } else if (direction === 'up' || direction === 'down') { + if (dx > dy * 0.5) return false; + if (direction === 'down' && otherPt.y > targetPt.y - 2) return false; + if (direction === 'up' && otherPt.y < targetPt.y + 2) return false; + } + return true; + }) + .map(l => ({ l, len: Math.hypot(l.vertices[1].x - l.vertices[0].x, l.vertices[1].y - l.vertices[0].y) })) + .sort((a, b) => b.len - a.len); + } + + function extractUg(pavementX, yMain, direction) { + const res = { + type: 'T1', + bcw: 650, bch: 500, dw: 100, dh: 150, + lw: 400, lh: 60, gap: 50, + vw1: 300, vw2: 300, vh: 300, + ww1: 144, ww2: 36, ww3: 120, + wh1: 360, wh2: 175, wh3: 125, + bw1: 125, bw2: 300, bw3: 300, + bd1: 50, bd2: 300, bd3: 50, + pipeD: 300 + }; + const startPt = { x: pavementX, y: yMain }; + + // 단면 바운딩 박스 내부의 secLines 사용 + const bcwLines = findConnectedLines(startPt, secLines, direction); + let ditchTopInnerPt = null; + if (bcwLines.length > 0) { + res.bcw = Math.round(bcwLines[0].len); + ditchTopInnerPt = getOtherPt(bcwLines[0].l, startPt); + } else { + ditchTopInnerPt = { x: startPt.x + (direction === 'left' ? -res.bcw : res.bcw), y: startPt.y }; + } + + let bchLines = findConnectedLines(startPt, secLines, 'down'); + if (bchLines.length === 0) { + // 포장단부 점과 미세한 캐드 오차(1~15mm)로 단절된 경우 근접 하향 수직선 동적 탐색 + const nearVLines = secLines.filter(l => { + const layer = (l.layer || '').toUpperCase(); + if (layer.includes('HIDL') || layer.includes('DASH') || layer.includes('DIM') || layer.includes('LEAL') || layer.includes('ANNO') || layer.includes('TEXT')) return false; + const p1 = l.vertices[0], p2 = l.vertices[1]; + if (Math.abs(p1.x - p2.x) > 2) return false; + const topY = Math.max(p1.y, p2.y), botY = Math.min(p1.y, p2.y); + const avgX = (p1.x + p2.x) / 2; + return Math.abs(avgX - startPt.x) <= 20 && Math.abs(topY - startPt.y) <= 20 && (topY - botY) >= 100; + }).sort((a, b) => { + const lenA = Math.abs(a.vertices[0].y - a.vertices[1].y); + const lenB = Math.abs(b.vertices[0].y - b.vertices[1].y); + return lenB - lenA; + }); + if (nearVLines.length > 0) { + bchLines = [{ l: nearVLines[0], len: Math.abs(nearVLines[0].vertices[0].y - nearVLines[0].vertices[1].y) }]; + } + } + + if (bchLines.length > 0 && bchLines[0].len >= 100) { + res.bch = Math.round(bchLines[0].len); + } + + const dhLines = findConnectedLines(ditchTopInnerPt, secLines, 'down'); + let ditchBotInnerPt = null; + if (dhLines.length > 0) { + res.dh = Math.round(dhLines[0].len); + ditchBotInnerPt = getOtherPt(dhLines[0].l, ditchTopInnerPt); + } else { + ditchBotInnerPt = { x: ditchTopInnerPt.x, y: ditchTopInnerPt.y - res.dh }; + } + + const dwLines = findConnectedLines(ditchBotInnerPt, secLines, direction); + let ditchBotOuterPt = null; + if (dwLines.length > 0) { + res.dw = Math.round(dwLines[0].len); + ditchBotOuterPt = getOtherPt(dwLines[0].l, ditchBotInnerPt); + } else { + ditchBotOuterPt = { x: ditchBotInnerPt.x + (direction === 'left' ? -res.dw : res.dw), y: ditchBotInnerPt.y }; + } + + const dhOuterLines = findConnectedLines(ditchBotOuterPt, secLines, 'up'); + let ditchTopOuterPt = null; + if (dhOuterLines.length > 0) { + ditchTopOuterPt = getOtherPt(dhOuterLines[0].l, ditchBotOuterPt); + } else { + ditchTopOuterPt = { x: ditchBotOuterPt.x, y: ditchBotOuterPt.y + res.dh }; + } + + let currentPt = ditchTopOuterPt; + function getConnectedLinesAt(pt) { + return secLines.filter(l => isSamePt(l.vertices[0], pt) || isSamePt(l.vertices[1], pt)); + } + + const lineH3 = getConnectedLinesAt(currentPt).find(l => { + const p = getOtherPt(l, currentPt); + return p.y > currentPt.y && Math.abs(p.x - currentPt.x) <= 1.0; + }); + + if (lineH3) { + const p1 = getOtherPt(lineH3, currentPt); + res.wh3 = Math.round(p1.y - currentPt.y); + currentPt = p1; + + const lineH2 = getConnectedLinesAt(currentPt).find(l => { + const p = getOtherPt(l, currentPt); + if (p.y <= currentPt.y) return false; + return direction === 'left' ? p.x < currentPt.x : p.x > currentPt.x; + }); + + if (lineH2) { + const p2 = getOtherPt(lineH2, currentPt); + res.wh2 = Math.round(p2.y - currentPt.y); + res.ww3 = Math.round(Math.abs(p2.x - currentPt.x)); + currentPt = p2; + + const lineH1 = getConnectedLinesAt(currentPt).find(l => { + const p = getOtherPt(l, currentPt); + if (p.y <= currentPt.y) return false; + return direction === 'left' ? p.x < currentPt.x : p.x > currentPt.x; + }); + + if (lineH1) { + const p3 = getOtherPt(lineH1, currentPt); + res.wh1 = Math.round(p3.y - currentPt.y); + res.ww2 = Math.round(Math.abs(p3.x - currentPt.x)); + currentPt = p3; + } + } + } + + const topY = currentPt.y; + const dropVLines = secLines.filter(l => { + const p1 = l.vertices[0], p2 = l.vertices[1]; + if (Math.abs(p1.x - p2.x) > 1.0) return false; + const maxY = Math.max(p1.y, p2.y); + if (Math.abs(maxY - topY) > 1.0) return false; + if (direction === 'left' && p1.x > currentPt.x - 0.5) return false; + if (direction === 'right' && p1.x < currentPt.x + 0.5) return false; + // 뚜껑은 포장 단부 currentPt에서 1200mm 이내의 X 좌표에 위치해야 함 + if (Math.abs(p1.x - currentPt.x) > 1200) return false; + return true; + }); + + dropVLines.sort((a, b) => + direction === 'left' ? b.vertices[0].x - a.vertices[0].x : a.vertices[0].x - b.vertices[0].x + ); + + if (dropVLines.length >= 2) { + const lidInnerX = dropVLines[0].vertices[0].x; + const lidOuterX = dropVLines[1].vertices[0].x; + res.ww1 = Math.round(Math.abs(currentPt.x - lidInnerX)); + res.lw = Math.round(Math.abs(lidInnerX - lidOuterX)); + res.lh = Math.round(Math.abs(dropVLines[0].vertices[0].y - dropVLines[0].vertices[1].y)); + + const outerHLines = secLines.filter(l => { + const p1 = l.vertices[0], p2 = l.vertices[1]; + if (Math.abs(p1.y - p2.y) > 1.0 || Math.abs(p1.y - topY) > 1.0) return false; + if (direction === 'left') { + return Math.max(p1.x, p2.x) <= lidOuterX + 1.0 && Math.min(p1.x, p2.x) < lidOuterX - 5; + } else { + return Math.min(p1.x, p2.x) >= lidOuterX - 1.0 && Math.max(p1.x, p2.x) > lidOuterX + 5; + } + }); + + if (outerHLines.length > 0) { + res.gap = Math.round(Math.abs(outerHLines[0].vertices[0].x - outerHLines[0].vertices[1].x)); + } + + const lidBotY = Math.min(dropVLines[0].vertices[0].y, dropVLines[0].vertices[1].y); + const minX = Math.min(lidInnerX, lidOuterX) - 5; + const maxX = Math.max(lidInnerX, lidOuterX) + 5; + + // 실제 트렌치 벽체/바닥 다각형 선분 추적 (X 좌표 및 깊이 구속: 2000mm까지 대형 공동구 수용) + function traceTrenchPoly() { + const candLines = secLines.filter(l => { + const layer = (l.layer || '').toUpperCase(); + if (layer.includes('HIDL') || layer.includes('DASH') || layer.includes('DIM') || layer.includes('LEAL') || layer.includes('ANNO') || layer.includes('TEXT')) return false; + const p1 = l.vertices[0], p2 = l.vertices[1]; + const minY = Math.min(p1.y, p2.y), maxY = Math.max(p1.y, p2.y); + const avgX = (p1.x + p2.x) / 2; + // 공동구는 200*300, 300*300 고정이 아니며, 대형 복합 공동구(깊이 600~1500mm 이상)도 도면 그대로 수용 + if (lidBotY - minY > 2000) return false; + return avgX >= minX && avgX <= maxX && maxY <= lidBotY + 2 && minY < lidBotY - 10; + }); + + const downLines = candLines.filter(l => { + const p1 = l.vertices[0], p2 = l.vertices[1]; + const topPt = p1.y > p2.y ? p1 : p2; + return Math.abs(topPt.y - lidBotY) <= 2; + }); + + downLines.sort((a, b) => { + const aTop = a.vertices[0].y > a.vertices[1].y ? a.vertices[0] : a.vertices[1]; + const bTop = b.vertices[0].y > b.vertices[1].y ? b.vertices[0] : b.vertices[1]; + return Math.abs(aTop.x - lidInnerX) - Math.abs(bTop.x - lidInnerX); + }); + + if (downLines.length === 0) return null; + const innerDownLine = downLines[0]; + const p1 = innerDownLine.vertices[0], p2 = innerDownLine.vertices[1]; + const innerTop = p1.y > p2.y ? p1 : p2; + const innerBot = p1.y > p2.y ? p2 : p1; + + let curr = innerBot; + const pts = [innerTop, innerBot]; + const used = new Set([innerDownLine]); + + for (let i = 0; i < 8; i++) { + const nexts = candLines.filter(l => { + if (used.has(l)) return false; + const d1 = Math.hypot(l.vertices[0].x - curr.x, l.vertices[0].y - curr.y); + const d2 = Math.hypot(l.vertices[1].x - curr.x, l.vertices[1].y - curr.y); + if (d1 >= 10 && d2 >= 10) return false; + const other = d1 < 10 ? l.vertices[1] : l.vertices[0]; + if (other.x < minX - 10 || other.x > maxX + 10 || other.y > lidBotY + 5) return false; + if (pts.length === 2) { + if (direction === 'left' && other.x > curr.x - 10) return false; + if (direction === 'right' && other.x < curr.x + 10) return false; + } + return true; + }).map(l => { + const len = Math.hypot(l.vertices[1].x - l.vertices[0].x, l.vertices[1].y - l.vertices[0].y); + return { l, len }; + }).sort((a, b) => b.len - a.len); + + if (nexts.length === 0) break; + const best = nexts[0].l; + used.add(best); + const d1 = Math.hypot(best.vertices[0].x - curr.x, best.vertices[0].y - curr.y); + const other = d1 < 10 ? best.vertices[1] : best.vertices[0]; + curr = other; + pts.push(curr); + if (Math.abs(curr.y - lidBotY) <= 2) break; + } + return pts; + } + + const tPts = traceTrenchPoly(); + if (tPts && tPts.length >= 3) { + res.trenchRelPts = tPts.map(p => ({ + dx: Math.round(p.x - lidInnerX), + dy: Math.round(p.y - lidBotY) + })); + + const innerWallDx = Math.abs(tPts[1].x - tPts[0].x); + const outerWallDx = Math.abs(tPts[tPts.length - 1].x - tPts[tPts.length - 2].x); + + res.vw1 = Math.round(Math.abs(tPts[0].x - tPts[tPts.length - 1].x)); + const minTrenchY = Math.min(...tPts.map(p => p.y)); + const floorPts = tPts.filter(p => Math.abs(p.y - minTrenchY) <= 5); + if (floorPts.length >= 2) { + res.vw2 = Math.round(Math.abs(floorPts[0].x - floorPts[floorPts.length - 1].x)); + } else { + res.vw2 = res.vw1; + } + res.vh = Math.round(Math.abs(lidBotY - minTrenchY)); + + if (tPts.length > 4) { + res.type = 'T3'; + } else if (outerWallDx > 25 || innerWallDx > 25) { + res.type = 'T2'; + } else { + res.type = 'T1'; + } + } else { + const bodyLines = secLines.filter(l => { + const p1 = l.vertices[0], p2 = l.vertices[1]; + const minY = Math.min(p1.y, p2.y); + const maxY = Math.max(p1.y, p2.y); + if (Math.abs(maxY - lidBotY) > 2.0) return false; + const topX = p1.y > p2.y ? p1.x : p2.x; + if (topX < minX || topX > maxX) return false; + return minY < lidBotY - 10 && (lidBotY - minY) <= 2000; + }); + + if (bodyLines.length >= 2) { + const getTopBot = l => + l.vertices[0].y > l.vertices[1].y + ? { top: l.vertices[0], bot: l.vertices[1] } + : { top: l.vertices[1], bot: l.vertices[0] }; + const l1 = getTopBot(bodyLines[0]); + const l2 = getTopBot(bodyLines[1]); + res.vw1 = Math.round(Math.abs(l1.top.x - l2.top.x)); + res.vw2 = Math.round(Math.abs(l1.bot.x - l2.bot.x)); + res.vh = Math.round(Math.abs(l1.top.y - l1.bot.y)); + if (Math.abs(res.vw1 - res.vw2) > 30) res.type = 'T2'; + } + } + } + return res; +} + + // 종배수관(유공관) 원형(CIRCLE) 직경 추출 (포장단부 맹암거 하부 대구경 유공관 우선 탐색) + let pipeDL = 300, pipeDR = 300; + if (pavementLeftX !== null && pavementRightX !== null && mainPv) { + const cSlope = (mainPv.vertices[1].y - mainPv.vertices[0].y) / (mainPv.vertices[1].x - mainPv.vertices[0].x); + const yMainL = mainPv.vertices[0].y + cSlope * (pavementLeftX - mainPv.vertices[0].x); + const yMainR = mainPv.vertices[0].y + cSlope * (pavementRightX - mainPv.vertices[0].x); + + const leftDrains = dxf.entities.filter(e => { + if (!e || e.type !== 'CIRCLE' || !e.center) return false; + return Math.abs(e.center.x - pavementLeftX) <= 2000 && e.center.y < yMainL + 500 && e.radius >= 100 && e.radius <= 300; + }).sort((a, b) => b.radius - a.radius); + + const rightDrains = dxf.entities.filter(e => { + if (!e || e.type !== 'CIRCLE' || !e.center) return false; + return Math.abs(e.center.x - pavementRightX) <= 2000 && e.center.y < yMainR + 500 && e.radius >= 100 && e.radius <= 300; + }).sort((a, b) => b.radius - a.radius); + + const fallbackCircles = dxf.entities.filter( + e => e && e.type === 'CIRCLE' && e.center && e.radius >= 80 && e.radius <= 300 && Math.abs(e.center.x - eValAbs) < r1 + ).sort((a, b) => b.radius - a.radius); + const defaultPipeD = fallbackCircles.length > 0 ? Math.round(fallbackCircles[0].radius * 2) : 300; + + pipeDL = leftDrains.length > 0 ? Math.round(leftDrains[0].radius * 2) : defaultPipeD; + pipeDR = rightDrains.length > 0 ? Math.round(rightDrains[0].radius * 2) : defaultPipeD; + + const parsedL = extractUg(pavementLeftX, yMainL, 'left'); + const parsedR = extractUg(pavementRightX, yMainR, 'right'); + parsedL.pipeD = pipeDL; + parsedR.pipeD = pipeDR; + window.ugData.left = parsedL; + window.ugData.right = parsedR; + } + + // 4) 차선 마커 및 도로 폭원 추출 + let laneWidths = [3500, 3500]; + let left_side = 1000; + let right_side = 1000; + let roadOriginX = 0; + let roadOriginY = 0; + + const solidCandidates = dxf.entities.filter( + e => e && (e.type === 'SOLID' || e.type === 'LWPOLYLINE' || e.type === 'POLYLINE' || e.type === 'HATCH') + ); + const markers = []; + solidCandidates.forEach(s => { + const layer = (s.layer || '').toUpperCase(); + if (layer.includes('DIM') || layer.includes('LEAL') || layer.includes('ANNO') || layer.includes('TEXT')) return; + + const pts = s.points || s.vertices || []; + if (!pts.length) return; + let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity; + pts.forEach(p => { + minX = Math.min(minX, p.x); maxX = Math.max(maxX, p.x); + minY = Math.min(minY, p.y); maxY = Math.max(maxY, p.y); + }); + const width = maxX - minX, height = maxY - minY; + if (width > 500 || height > 200) return; + const bcX = (minX + maxX) / 2, bcY = minY; + + // 차선 마커는 반드시 포장면 X구간 내에 존재해야 함 + if (pavementLeftX !== null && pavementRightX !== null) { + if (bcX < pavementLeftX - 30 || bcX > pavementRightX + 30) return; + } else if (Math.abs(bcX - eValAbs) > 10000) { + return; + } + + let targetPavementY = m3 * bcX; + if (mainPv) { + const v0 = mainPv.vertices[0], v1 = mainPv.vertices[1]; + const slope = (v1.y - v0.y) / (v1.x - v0.x); + targetPavementY = v0.y + slope * (bcX - v0.x); + } + if (Math.abs(bcY - targetPavementY) <= 12) { + markers.push({ x: bcX, y: bcY }); + } + }); + + if (markers.length >= 2) { + markers.sort((a, b) => a.x - b.x); + const uniqueMarkers = []; + markers.forEach(mk => { + if (uniqueMarkers.length === 0 || Math.abs(uniqueMarkers[uniqueMarkers.length - 1].x - mk.x) > 50) { + uniqueMarkers.push(mk); + } + }); + + let bestChain = []; + const findChain = startIndex => { + const queue = [[uniqueMarkers[startIndex]]]; + let longest = []; + while (queue.length > 0) { + const current = queue.shift(); + const last = current[current.length - 1]; + let expanded = false; + for (let j = 0; j < uniqueMarkers.length; j++) { + const next = uniqueMarkers[j]; + if (next.x > last.x) { + const d = next.x - last.x; + // 차로폭 규격: 최소 2800mm ~ 최대 4200mm 또는 차선 분리 마킹 간격(300mm ~ 1000mm) + if ((d >= 2800 && d <= 4200) || (d >= 300 && d <= 1000)) { + queue.push([...current, next]); + expanded = true; + } + } + } + if (!expanded && current.length > longest.length) longest = current; + } + return longest; + }; + + for (let i = 0; i < uniqueMarkers.length; i++) { + const ch = findChain(i); + if (ch.length > bestChain.length) bestChain = ch; + } + + if (bestChain.length >= 2) { + laneWidths = []; + for (let i = 0; i < bestChain.length - 1; i++) { + laneWidths.push(Math.round(bestChain[i + 1].x - bestChain[i].x)); + } + roadOriginX = bestChain[0].x; + roadOriginY = bestChain[0].y; + + if (pavementLeftX !== null && pavementRightX !== null) { + const theta = Math.atan(m3); + const cos = Math.cos(theta); + // 경사면(기울기 m3)을 따르는 실제 투영 길이 계산 (도면 절대좌표계 Y 오프셋 왜곡 방지) + const distL = Math.abs(bestChain[0].x - pavementLeftX) / cos; + const distR = Math.abs(pavementRightX - bestChain[bestChain.length - 1].x) / cos; + left_side = Math.round(distL); + right_side = Math.round(distR); + } + } + } else if (pavementLeftX !== null && pavementRightX !== null) { + // 차선 마커가 없는 도면인 경우 포장 전체 폭에서 차로 및 측대 산출 + const totalPaveW = Math.round(pavementRightX - pavementLeftX); + const totalLaneW = laneWidths.reduce((s, v) => s + v, 0); + const remainSide = Math.max(500, totalPaveW - totalLaneW); + left_side = Math.round(remainSide / 2); + right_side = remainSide - left_side; + roadOriginX = pavementLeftX + left_side; + if (mainPv) { + const v0 = mainPv.vertices[0], v1 = mainPv.vertices[1]; + const slope = (v1.y - v0.y) / (v1.x - v0.x); + roadOriginY = v0.y + slope * (roadOriginX - v0.x); + } + } + + // 도로 좌측 차선 기준 상대 좌표 (offset, h1) 계산 + const offset = Math.round(eValAbs - roadOriginX); + const pavementYAtCenter = roadOriginY + m3 * offset; + const h1 = Math.round(h1Abs - pavementYAtCenter); + + // 만약 R2~R5 중 일부가 도면 선분 분리로 미검출된 경우 기하학 공식으로 보완 + const tw = laneWidths.reduce((s, v) => s + v, 0); + const cx1 = offset; + const cy1 = m3 * offset + h1; + const rad1 = (theta1 * Math.PI) / 180; + const startAng = Math.PI / 2 + rad1 / 2; + const endAng = Math.PI / 2 - rad1 / 2; + const rx_l = cx1 + r1 * Math.cos(startAng), ry_l = cy1 + r1 * Math.sin(startAng); + const rx_r = cx1 + r1 * Math.cos(endAng), ry_r = cy1 + r1 * Math.sin(endAng); + + if (!r2) { + const dL = window.ugData.left; + const bL = m3 * -left_side; + const baseYL = bL - (dL.bcw + dL.dw) * 0.02; + const totalTopYL = baseYL + (dL.wh1 + dL.wh2 + dL.wh3); + const LP1_x = -left_side - (dL.bcw + dL.dw) - (dL.ww1 + dL.ww2 + dL.ww3); + const P_UG_x = LP1_x - dL.lw - dL.gap, P_UG_y = totalTopYL; + const dx = rx_l - P_UG_x, dy = ry_l - P_UG_y; + const ux = Math.cos(startAng), uy = Math.sin(startAng); + r2 = Math.round(Math.max(1000, (dx * dx + dy * dy) / (2 * (dx * ux + dy * uy)))); + } + if (!r3) { + const dR = window.ugData.right; + const bR = m3 * (tw + right_side); + const baseXR = tw + right_side + dR.bcw + dR.dw; + const baseYR = bR - (dR.bcw + dR.dw) * 0.02; + const wallEndXR = baseXR + (dR.ww1 + dR.ww2 + dR.ww3); + const totalTopYR = baseYR + (dR.wh1 + dR.wh2 + dR.wh3); + const P_UG_x_R = wallEndXR + dR.lw + dR.gap, P_UG_y_R = totalTopYR; + const dx_R = rx_r - P_UG_x_R, dy_R = ry_r - P_UG_y_R; + const ux_R = Math.cos(endAng), uy_R = Math.sin(endAng); + r3 = Math.round(Math.max(1000, Math.abs((dx_R * dx_R + dy_R * dy_R) / (2 * (dx_R * ux_R + dy_R * uy_R))))); + } + if (!r4) r4 = Math.round(r2 + lining_t * 2.5); + if (!r5) r5 = Math.round(r3 + lining_t * 2.5); + + // 전역 상태 업데이트 + window.sectionParams = { + offset, + h1, + theta1, + r1, + r2, + r3, + r4, + r5, + lining_t, + shotcrete_t, + seVal: Math.round(m3 * 100 * 10) / 10, + lanes: laneWidths, + left_sh: left_side, + right_sh: right_side, + left_side, + right_side, + h_val: 4800, + pave_t1: pave_t1_val, + pave_t2: pave_t2_val, + pave_t3: pave_t3_val, + pavementLeftX, + pavementRightX + }; + + syncParamsToInputs(); + renderDetectionList(sourceName, dxf.entities.length); + buildCurrentSection3DPlot(false); + render2DSection(); +} + +function syncParamsToInputs() { + const p = window.sectionParams; + const setIf = (id, v) => { const el = $(id); if (el) el.value = v; }; + setIf('p_r1', p.r1); + setIf('p_theta1', p.theta1); + setIf('p_r2', p.r2); + setIf('p_r3', p.r3); + setIf('p_r4', p.r4); + setIf('p_r5', p.r5); + setIf('p_h1', p.h1); + setIf('p_offset', p.offset); + setIf('p_lining_t', p.lining_t); + setIf('p_shotcrete_t', p.shotcrete_t); + setIf('p_se', p.seVal); + setIf('p_pave_t1', p.pave_t1); + setIf('p_pave_t2', p.pave_t2); + setIf('p_pave_t3', p.pave_t3); +} + +function readParamsFromInputs() { + const p = window.sectionParams; + const getNum = (id, fb) => { + const v = parseFloat($(id)?.value); + return Number.isFinite(v) ? v : fb; + }; + p.r1 = getNum('p_r1', p.r1); + p.theta1 = getNum('p_theta1', p.theta1); + p.r2 = getNum('p_r2', p.r2); + p.r3 = getNum('p_r3', p.r3); + p.r4 = getNum('p_r4', p.r4); + p.r5 = getNum('p_r5', p.r5); + p.h1 = getNum('p_h1', p.h1); + p.offset = getNum('p_offset', p.offset); + p.lining_t = getNum('p_lining_t', p.lining_t); + p.shotcrete_t = getNum('p_shotcrete_t', p.shotcrete_t); + p.seVal = getNum('p_se', p.seVal); + p.pave_t1 = getNum('p_pave_t1', p.pave_t1); + p.pave_t2 = getNum('p_pave_t2', p.pave_t2); + p.pave_t3 = getNum('p_pave_t3', p.pave_t3); +} + +function renderDetectionList(sourceName, entityCount) { + const p = window.sectionParams; + const dL = window.ugData.left; + const dR = window.ugData.right; + const host = $('detectionList'); + if (!host) return; + + const items = [ + { + color: '#475569', + title: '숏크리트 1차 지보재', + detail: `두께 ${p.shotcrete_t}mm · 천단 반경 R=${p.r1 + p.lining_t + p.shotcrete_t}mm 폐합 아치`, + badge: '100%' + }, + { + color: '#8da0b0', + title: '라이닝·공동구 일체형 본체', + detail: `R1=${p.r1} · R2=${p.r2} · R3=${p.r3} · 두께 ${p.lining_t}mm (경계면 없는 단일 솔리드)`, + badge: '100%' + }, + { + color: '#cbd5e1', + title: '공동구 및 프리캐스트 뚜껑', + detail: `좌 ${dL.type || 'T2'} (${dL.lw}×${dL.lh}mm, 내공 ${dL.vw1}×${dL.vh}mm) · 우 ${dR.type || 'T2'} (${dR.lw}×${dR.lh}mm, 내공 ${dR.vw1}×${dR.vh}mm)`, + badge: '100%' + }, + { + color: '#38bdf8', + title: p.pave_t3 > 0 ? '도로포장 다층 구조 (3개층)' : '도로포장 구조 (2개층)', + detail: p.pave_t3 > 0 + ? `표층 t1=${p.pave_t1}mm · 콘크리트층 t2=${p.pave_t2}mm · 필터층 t3=${p.pave_t3}mm` + : `콘크리트 슬래브 t1=${p.pave_t1}mm · 린콘크리트/필터층 t2=${p.pave_t2}mm`, + badge: '98%' + }, + { + color: '#d97706', + title: '맹암거 및 유공 종배수관', + detail: `좌·우 투수필터층 맹암거 · 종배수관 Ø${dL.pipeD}mm 원형 파이프`, + badge: '95%' + }, + { + color: '#10b981', + title: '차선 및 도로 제원', + detail: `${p.lanes.length}차로 (${p.lanes.join('+')}mm) · 측대 좌 ${p.left_side}/우 ${p.right_side}mm · 편경사 ${p.seVal}%`, + badge: '95%' + } + ]; + + host.innerHTML = items + .map( + it => ` +
+ +
+ ${it.title} + ${it.detail} +
+ ${it.badge} +
` + ) + .join(''); + + $('toggleParamsBtn')?.classList.remove('hidden'); + $('welcome')?.classList.add('hidden'); + $('hint')?.classList.remove('hidden'); + $('buildButton').disabled = false; + $('exportObj').disabled = false; + if ($('export3dm')) $('export3dm').disabled = false; + $('modelSummary').textContent = `${sourceName} · R1=${p.r1}mm, 라이닝=${p.lining_t}mm, 숏크리트=${p.shotcrete_t}mm, 연장=${window.current3DTunnelLength}m`; +} + +// ───────────────────────────────────────────────────────────────────────────── +// 4. Plotly 3D 모델 생성기 (터널단면설계02.html 동일 솔리드 엔진) +// ───────────────────────────────────────────────────────────────────────────── +function buildCurrentSection3DPlot(preserveCam = false) { + try { + const plotDiv = $('placement_3d_plot'); + if (!plotDiv || typeof Plotly === 'undefined') return; + + const p = window.sectionParams; + const offset = p.offset; + const h1 = p.h1; + const theta1 = p.theta1; + const r1 = p.r1; + const r2 = p.r2; + const r3 = p.r3; + const r4 = p.r4; + const r5 = p.r5; + const lining_t = p.lining_t; + const shotcrete_t = p.shotcrete_t; + const l_side = p.left_side; + const r_side = p.right_side; + const lanes = p.lanes && p.lanes.length > 0 ? p.lanes : [3500, 3500]; + const tw = lanes.reduce((s, v) => s + v, 0); + const m = (p.seVal || -2.0) / 100.0; + + // 천단 중심 및 각도 계산 + const cx1 = offset; + const cy1 = m * offset + h1; + const rad1 = (theta1 * Math.PI) / 180; + const startAng = Math.PI / 2 + rad1 / 2; + const endAng = Math.PI / 2 - rad1 / 2; + + const uxL = Math.cos(startAng), uyL = Math.sin(startAng); + const uxR = Math.cos(endAng), uyR = Math.sin(endAng); + + // R2, R3 중심점 (내공 측벽 아치) + const rx_l = cx1 + r1 * uxL, ry_l = cy1 + r1 * uyL; + const rx_r = cx1 + r1 * uxR, ry_r = cy1 + r1 * uyR; + const c2_x = rx_l - r2 * uxL, c2_y = ry_l - r2 * uyL; + const c3_x = rx_r - r3 * uxR, c3_y = ry_r - r3 * uyR; + + // R4, R5 중심점 (라이닝 외면 아치) + const P_top_L_lining = { x: cx1 + (r1 + lining_t) * uxL, y: cy1 + (r1 + lining_t) * uyL }; + const P_top_R_lining = { x: cx1 + (r1 + lining_t) * uxR, y: cy1 + (r1 + lining_t) * uyR }; + const c4_x = P_top_L_lining.x - r4 * uxL, c4_y = P_top_L_lining.y - r4 * uyL; + const c5_x = P_top_R_lining.x - r5 * uxR, c5_y = P_top_R_lining.y - r5 * uyR; + + // 공동구 및 바닥 기초 레벨 (bch) + const bL = m * -l_side; + const bR = m * (tw + r_side); + const dL = window.ugData.left || {}; + const dR = window.ugData.right || {}; + const bcwL = parseFloat(dL.bcw) || 250, bchL = parseFloat(dL.bch) || 500, dwL = parseFloat(dL.dw) || 300, dhL = parseFloat(dL.dh) || 300; + const bcwR = parseFloat(dR.bcw) || 250, bchR = parseFloat(dR.bch) || 500, dwR = parseFloat(dR.dw) || 300, dhR = parseFloat(dR.dh) || 300; + const wwL = [parseFloat(dL.ww1) || 150, parseFloat(dL.ww2) || 150, parseFloat(dL.ww3) || 150]; + const whL = [parseFloat(dL.wh1) || 200, parseFloat(dL.wh2) || 200, parseFloat(dL.wh3) || 200]; + const wwR = [parseFloat(dR.ww1) || 150, parseFloat(dR.ww2) || 150, parseFloat(dR.ww3) || 150]; + const whR = [parseFloat(dR.wh1) || 200, parseFloat(dR.wh2) || 200, parseFloat(dR.wh3) || 200]; + const lwL = parseFloat(dL.lw) || 400, lhL = parseFloat(dL.lh) || 100, gapL = parseFloat(dL.gap) || 50; + const lwR = parseFloat(dR.lw) || 400, lhR = parseFloat(dR.lh) || 100, gapR = parseFloat(dR.gap) || 50; + const vvwL = [parseFloat(dL.vw1) || 300, parseFloat(dL.vw2) || 300, parseFloat(dL.vh) || 300]; + const vvwR = [parseFloat(dR.vw1) || 300, parseFloat(dR.vw2) || 300, parseFloat(dR.vh) || 300]; + + const bottom_y_L = bL - bchL; + const bottom_y_R = bR - bchR; + + // 좌측 공동구 기준점 + const baseXL = -l_side - (bcwL + dwL), baseYL = bL - (bcwL + dwL) * 0.02; + const totalTopYL = baseYL + (whL[0] + whL[1] + whL[2]); + const LP1 = { x: baseXL - (wwL[0] + wwL[1] + wwL[2]), y: totalTopYL }; + const LP2 = { x: baseXL - (wwL[1] + wwL[2]), y: totalTopYL }; + const LP3 = { x: baseXL - wwL[2], y: baseYL + whL[1] + whL[2] }; + const LP4 = { x: baseXL, y: baseYL + whL[2] }; + const trY_L = bL - bcwL * 0.02; + const ditch_bot_y_L = baseYL - dhL; + const bx1L = (LP1.x - lwL / 2) + vvwL[0] / 2; + const bx2L = bx1L - vvwL[0]; + + // 우측 공동구 기준점 + const aR = tw + r_side; + const baseXR = aR + bcwR + dwR, baseYR = bR - (bcwR + dwR) * 0.02; + const totalTopYR = baseYR + (whR[0] + whR[1] + whR[2]); + const wallEndXR = baseXR + (wwR[0] + wwR[1] + wwR[2]); + const RP5 = { x: wallEndXR, y: totalTopYR }; + const RP4 = { x: baseXR + (wwR[1] + wwR[2]), y: totalTopYR }; + const RP3 = { x: baseXR + wwR[2], y: baseYR + whR[1] + whR[2] }; + const RP2 = { x: baseXR, y: baseYR + whR[2] }; + const RP1 = { x: baseXR, y: baseYR }; + const tlY_R = bR - bcwR * 0.02; + const ditch_bot_y_R = baseYR - dhR; + const bx1R = (wallEndXR + lwR / 2) - vvwR[0] / 2; + const bx2R = bx1R + vvwR[0]; + + // R2, R3, R4, R5 및 숏크리트 하단 교점 + const y_diff_2 = totalTopYL - c2_y; + let new_P_UG_x = LP1.x - lwL - gapL; + if (Math.abs(y_diff_2) <= r2) { + new_P_UG_x = c2_x - Math.sqrt(Math.max(1, r2 * r2 - y_diff_2 * y_diff_2)); + } + let r2_draw_end = Math.atan2(y_diff_2, new_P_UG_x - c2_x); + if (r2_draw_end < startAng) r2_draw_end += 2 * Math.PI; + + const y_diff_3 = totalTopYR - c3_y; + let new_P_UG_x_R = wallEndXR + lwR + gapR; + if (Math.abs(y_diff_3) <= r3) { + new_P_UG_x_R = c3_x + Math.sqrt(Math.max(1, r3 * r3 - y_diff_3 * y_diff_3)); + } + let r3_draw_end = Math.atan2(y_diff_3, new_P_UG_x_R - c3_x); + if (r3_draw_end > endAng) r3_draw_end -= 2 * Math.PI; + + const y_diff_4 = bottom_y_L - c4_y; + const x4_bot = c4_x - Math.sqrt(Math.max(1, r4 * r4 - y_diff_4 * y_diff_4)); + let r4_draw_end = Math.atan2(y_diff_4, x4_bot - c4_x); + if (r4_draw_end < startAng) r4_draw_end += 2 * Math.PI; + + const y_diff_5 = bottom_y_R - c5_y; + const x5_bot = c5_x + Math.sqrt(Math.max(1, r5 * r5 - y_diff_5 * y_diff_5)); + let r5_draw_end = Math.atan2(y_diff_5, x5_bot - c5_x); + if (r5_draw_end > endAng) r5_draw_end -= 2 * Math.PI; + + const sc_r4 = r4 + shotcrete_t; + const x_sc_4 = c4_x - Math.sqrt(Math.max(1, sc_r4 * sc_r4 - y_diff_4 * y_diff_4)); + let sc_r4_end = Math.atan2(y_diff_4, x_sc_4 - c4_x); + if (sc_r4_end < startAng) sc_r4_end += 2 * Math.PI; + + const sc_r5 = r5 + shotcrete_t; + const x_sc_5 = c5_x + Math.sqrt(Math.max(1, sc_r5 * sc_r5 - y_diff_5 * y_diff_5)); + let sc_r5_end = Math.atan2(y_diff_5, x_sc_5 - c5_x); + if (sc_r5_end > endAng) sc_r5_end -= 2 * Math.PI; + + const toX = valX => (valX - cx1) / 1000.0; + const toZ = valY => valY / 1000.0; + + const nSteps = 14; + const nCrown = 24; + + // [폐합 단면 1] 터널 콘크리트 본체 (라이닝 + 좌/우 공동구 일체형 단일 솔리드) + const unifiedConcreteLoop2D = []; + unifiedConcreteLoop2D.push({ x: toX(-l_side), z: toZ(bL) }); + unifiedConcreteLoop2D.push({ x: toX(-l_side - bcwL), z: toZ(trY_L) }); + unifiedConcreteLoop2D.push({ x: toX(baseXL + dwL), z: toZ(trY_L) }); + unifiedConcreteLoop2D.push({ x: toX(baseXL + dwL), z: toZ(ditch_bot_y_L) }); + unifiedConcreteLoop2D.push({ x: toX(baseXL), z: toZ(ditch_bot_y_L) }); + unifiedConcreteLoop2D.push({ x: toX(baseXL), z: toZ(baseYL) }); + unifiedConcreteLoop2D.push({ x: toX(LP4.x), z: toZ(LP4.y) }); + unifiedConcreteLoop2D.push({ x: toX(LP3.x), z: toZ(LP3.y) }); + unifiedConcreteLoop2D.push({ x: toX(LP2.x), z: toZ(LP2.y) }); + unifiedConcreteLoop2D.push({ x: toX(LP1.x), z: toZ(LP1.y) }); + + // 좌측 공동구 중공 트렌치 파내기 + unifiedConcreteLoop2D.push({ x: toX(LP1.x), z: toZ(totalTopYL - lhL) }); + if (dL.trenchRelPts && dL.trenchRelPts.length >= 3) { + dL.trenchRelPts.forEach(p => { + unifiedConcreteLoop2D.push({ x: toX(LP1.x + p.dx), z: toZ(totalTopYL - lhL + p.dy) }); + }); + } else { + unifiedConcreteLoop2D.push({ x: toX(bx1L), z: toZ(totalTopYL - lhL) }); + unifiedConcreteLoop2D.push({ x: toX(bx1L), z: toZ(totalTopYL - lhL - vvwL[2]) }); + unifiedConcreteLoop2D.push({ x: toX(bx2L - (vvwL[1] - vvwL[0])), z: toZ(totalTopYL - lhL - vvwL[2]) }); + unifiedConcreteLoop2D.push({ x: toX(bx2L), z: toZ(totalTopYL - lhL) }); + } + unifiedConcreteLoop2D.push({ x: toX(LP1.x - lwL), z: toZ(totalTopYL - lhL) }); + unifiedConcreteLoop2D.push({ x: toX(LP1.x - lwL), z: toZ(totalTopYL) }); + unifiedConcreteLoop2D.push({ x: toX(new_P_UG_x), z: toZ(totalTopYL) }); + + for (let i = 1; i <= nSteps; i++) { + const a = r2_draw_end + (startAng - r2_draw_end) * (i / nSteps); + unifiedConcreteLoop2D.push({ x: toX(c2_x + r2 * Math.cos(a)), z: toZ(c2_y + r2 * Math.sin(a)) }); + } + for (let i = 1; i <= nCrown; i++) { + const a = startAng + (endAng - startAng) * (i / nCrown); + unifiedConcreteLoop2D.push({ x: toX(cx1 + r1 * Math.cos(a)), z: toZ(cy1 + r1 * Math.sin(a)) }); + } + for (let i = 1; i <= nSteps; i++) { + const a = endAng + (r3_draw_end - endAng) * (i / nSteps); + unifiedConcreteLoop2D.push({ x: toX(c3_x + r3 * Math.cos(a)), z: toZ(c3_y + r3 * Math.sin(a)) }); + } + + // 우측 공동구 중공 트렌치 파내기 + unifiedConcreteLoop2D.push({ x: toX(new_P_UG_x_R), z: toZ(totalTopYR) }); + unifiedConcreteLoop2D.push({ x: toX(wallEndXR + lwR), z: toZ(totalTopYR) }); + unifiedConcreteLoop2D.push({ x: toX(wallEndXR + lwR), z: toZ(totalTopYR - lhR) }); + if (dR.trenchRelPts && dR.trenchRelPts.length >= 3) { + for (let i = dR.trenchRelPts.length - 1; i >= 0; i--) { + const p = dR.trenchRelPts[i]; + unifiedConcreteLoop2D.push({ x: toX(wallEndXR + p.dx), z: toZ(totalTopYR - lhR + p.dy) }); + } + } else { + unifiedConcreteLoop2D.push({ x: toX(bx2R), z: toZ(totalTopYR - lhR) }); + if (dR.type === 'T3') { + unifiedConcreteLoop2D.push({ x: toX(bx2R), z: toZ(totalTopYR - lhR - (vvwR[2] - 100)) }); + unifiedConcreteLoop2D.push({ x: toX(bx2R - 100), z: toZ(totalTopYR - lhR - vvwR[2]) }); + } else { + unifiedConcreteLoop2D.push({ x: toX(bx2R + (vvwR[1] - vvwR[0])), z: toZ(totalTopYR - lhR - vvwR[2]) }); + } + unifiedConcreteLoop2D.push({ x: toX(bx1R), z: toZ(totalTopYR - lhR - vvwR[2]) }); + unifiedConcreteLoop2D.push({ x: toX(bx1R), z: toZ(totalTopYR - lhR) }); + } + unifiedConcreteLoop2D.push({ x: toX(wallEndXR), z: toZ(totalTopYR - lhR) }); + unifiedConcreteLoop2D.push({ x: toX(RP5.x), z: toZ(totalTopYR) }); + + unifiedConcreteLoop2D.push({ x: toX(RP4.x), z: toZ(totalTopYR) }); + unifiedConcreteLoop2D.push({ x: toX(RP3.x), z: toZ(RP3.y) }); + unifiedConcreteLoop2D.push({ x: toX(RP2.x), z: toZ(RP2.y) }); + unifiedConcreteLoop2D.push({ x: toX(RP1.x), z: toZ(RP1.y) }); + unifiedConcreteLoop2D.push({ x: toX(baseXR), z: toZ(ditch_bot_y_R) }); + unifiedConcreteLoop2D.push({ x: toX(baseXR - dwR), z: toZ(ditch_bot_y_R) }); + unifiedConcreteLoop2D.push({ x: toX(baseXR - dwR), z: toZ(tlY_R) }); + unifiedConcreteLoop2D.push({ x: toX(aR + bcwR), z: toZ(tlY_R) }); + unifiedConcreteLoop2D.push({ x: toX(aR), z: toZ(bR) }); + + unifiedConcreteLoop2D.push({ x: toX(aR), z: toZ(bottom_y_R) }); + unifiedConcreteLoop2D.push({ x: toX(x5_bot), z: toZ(bottom_y_R) }); + + for (let i = 1; i <= nSteps; i++) { + const a = r5_draw_end + (endAng - r5_draw_end) * (i / nSteps); + unifiedConcreteLoop2D.push({ x: toX(c5_x + r5 * Math.cos(a)), z: toZ(c5_y + r5 * Math.sin(a)) }); + } + for (let i = 1; i <= nCrown; i++) { + const a = endAng + (startAng - endAng) * (i / nCrown); + unifiedConcreteLoop2D.push({ x: toX(cx1 + (r1 + lining_t) * Math.cos(a)), z: toZ(cy1 + (r1 + lining_t) * Math.sin(a)) }); + } + for (let i = 1; i <= nSteps; i++) { + const a = startAng + (r4_draw_end - startAng) * (i / nSteps); + unifiedConcreteLoop2D.push({ x: toX(c4_x + r4 * Math.cos(a)), z: toZ(c4_y + r4 * Math.sin(a)) }); + } + unifiedConcreteLoop2D.push({ x: toX(-l_side), z: toZ(bottom_y_L) }); + unifiedConcreteLoop2D.push(unifiedConcreteLoop2D[0]); + + // [폐합 단면 2] 숏크리트 층 + const shotcreteLoop2D = []; + for (let i = 0; i <= nSteps; i++) { + const a = r4_draw_end + (startAng - r4_draw_end) * (i / nSteps); + shotcreteLoop2D.push({ x: toX(c4_x + r4 * Math.cos(a)), z: toZ(c4_y + r4 * Math.sin(a)) }); + } + for (let i = 1; i <= nCrown; i++) { + const a = startAng + (endAng - startAng) * (i / nCrown); + shotcreteLoop2D.push({ x: toX(cx1 + (r1 + lining_t) * Math.cos(a)), z: toZ(cy1 + (r1 + lining_t) * Math.sin(a)) }); + } + for (let i = 1; i <= nSteps; i++) { + const a = endAng + (r5_draw_end - endAng) * (i / nSteps); + shotcreteLoop2D.push({ x: toX(c5_x + r5 * Math.cos(a)), z: toZ(c5_y + r5 * Math.sin(a)) }); + } + shotcreteLoop2D.push({ x: toX(x_sc_5), z: toZ(bottom_y_R) }); + for (let i = 1; i <= nSteps; i++) { + const a = sc_r5_end + (endAng - sc_r5_end) * (i / nSteps); + shotcreteLoop2D.push({ x: toX(c5_x + sc_r5 * Math.cos(a)), z: toZ(c5_y + sc_r5 * Math.sin(a)) }); + } + const sc_crown_r = r1 + lining_t + shotcrete_t; + for (let i = 1; i <= nCrown; i++) { + const a = endAng + (startAng - endAng) * (i / nCrown); + shotcreteLoop2D.push({ x: toX(cx1 + sc_crown_r * Math.cos(a)), z: toZ(cy1 + sc_crown_r * Math.sin(a)) }); + } + for (let i = 1; i <= nSteps; i++) { + const a = startAng + (sc_r4_end - startAng) * (i / nSteps); + shotcreteLoop2D.push({ x: toX(c4_x + sc_r4 * Math.cos(a)), z: toZ(c4_y + sc_r4 * Math.sin(a)) }); + } + shotcreteLoop2D.push(shotcreteLoop2D[0]); + + // [폐합 단면 3] 도로 포장 다층 구조 (t1, t2, t3) + const pts_pave = [ + p.pave_t1 !== undefined ? p.pave_t1 : 20, + p.pave_t2 !== undefined ? p.pave_t2 : 260, + p.pave_t3 !== undefined ? p.pave_t3 : 0 + ]; + const paveLayers = [ + { id: 't1', name: `포장층 - 표층/생애주기층 (t1: ${pts_pave[0]}mm)`, thick: pts_pave[0], colorscale: [[0, '#0f172a'], [1, '#1e293b']], rimColor: '#38bdf8', opacity: 1.0 }, + { id: 't2', name: `포장층 - 콘크리트층 (t2: ${pts_pave[1]}mm)`, thick: pts_pave[1], colorscale: [[0, '#94a3b8'], [1, '#cbd5e1']], rimColor: '#f59e0b', opacity: 0.96 }, + { id: 't3', name: `포장층 - 필터층 (t3: ${pts_pave[2]}mm)`, thick: pts_pave[2], colorscale: [[0, '#475569'], [1, '#64748b']], rimColor: '#c084fc', opacity: 0.92 } + ]; + + const xFloorL = -l_side; + const xFloorR = tw + r_side; + const roadSteps = 20; + const L = window.current3DTunnelLength || 10; + const ySteps = 16; + const yCoords = []; + for (let j = 0; j <= ySteps; j++) { + yCoords.push(j * (L / ySteps)); + } + + function createSurfaceGrid(pts2D) { + const X = [], Y = [], Z = []; + for (let j = 0; j < yCoords.length; j++) { + const rowX = [], rowY = [], rowZ = []; + const yVal = yCoords[j]; + for (let i = 0; i < pts2D.length; i++) { + rowX.push(pts2D[i].x); + rowY.push(yVal); + rowZ.push(pts2D[i].z); + } + X.push(rowX); + Y.push(rowY); + Z.push(rowZ); + } + return { X, Y, Z }; + } + + let cur_pave_off = 0; + const paveGridsData = []; + const paveLoops2D = []; + + paveLayers.forEach(layer => { + if (layer.thick > 0) { + const loop = []; + for (let i = 0; i <= roadSteps; i++) { + const rx = xFloorL + (xFloorR - xFloorL) * (i / roadSteps); + loop.push({ x: toX(rx), z: toZ(m * rx - cur_pave_off) }); + } + for (let i = roadSteps; i >= 0; i--) { + const rx = xFloorL + (xFloorR - xFloorL) * (i / roadSteps); + loop.push({ x: toX(rx), z: toZ(m * rx - (cur_pave_off + layer.thick)) }); + } + loop.push(loop[0]); + paveLoops2D.push({ loop, layer }); + paveGridsData.push({ grid: createSurfaceGrid(loop), layer }); + cur_pave_off += layer.thick; + } + }); + + // [4] 맹암거 및 종배수관 + const aL = -l_side; + const paveThick = pts_pave[0] + pts_pave[1] + pts_pave[2]; + const lb_m = [parseFloat(dL.bw1) || 125, parseFloat(dL.bw2) || 300, parseFloat(dL.bw3) || 300]; + const lb_d = [parseFloat(dL.bd1) || 50, parseFloat(dL.bd2) || 300, parseFloat(dL.bd3) || 50]; + const pDL = parseFloat(dL.pipeD) || 300; + + const rb_m = [parseFloat(dR.bw1) || 125, parseFloat(dR.bw2) || 300, parseFloat(dR.bw3) || 300]; + const rb_d = [parseFloat(dR.bd1) || 50, parseFloat(dR.bd2) || 300, parseFloat(dR.bd3) || 50]; + const pDR = parseFloat(dR.pipeD) || 300; + + const bdLoopL = [ + { x: toX(aL - (125 + lb_m[0] + lb_m[1])), z: toZ(bL - bchL) }, + { x: toX(aL - (125 + lb_m[1])), z: toZ(bL - (bchL + (lb_d[0] + lb_d[1] + lb_d[2]))) }, + { x: toX(aL - 125), z: toZ(bL - (bchL + (lb_d[0] + lb_d[1] + lb_d[2]))) }, + { x: toX(aL + lb_m[2] - 125), z: toZ(m * (aL + lb_m[2] - 125) - paveThick) }, + { x: toX(aL), z: toZ(bL - paveThick) }, + { x: toX(aL), z: toZ(bL - bchL) }, + { x: toX(aL - (125 + lb_m[0] + lb_m[1])), z: toZ(bL - bchL) } + ]; + const bdGridL = createSurfaceGrid(bdLoopL); + + const bdLoopR = [ + { x: toX(aR + (125 + rb_m[0] + rb_m[1])), z: toZ(bR - bchR) }, + { x: toX(aR + (125 + rb_m[1])), z: toZ(bR - (bchR + (rb_d[0] + rb_d[1] + rb_d[2]))) }, + { x: toX(aR + 125), z: toZ(bR - (bchR + (rb_d[0] + rb_d[1] + rb_d[2]))) }, + { x: toX(aR - (rb_m[2] - 125)), z: toZ(m * (aR - (rb_m[2] - 125)) - paveThick) }, + { x: toX(aR), z: toZ(bR - paveThick) }, + { x: toX(aR), z: toZ(bR - bchR) }, + { x: toX(aR + (125 + rb_m[0] + rb_m[1])), z: toZ(bR - bchR) } + ]; + const bdGridR = createSurfaceGrid(bdLoopR); + + function createCylinderLoop(cx_mm, cy_mm, r_mm, segs = 24) { + const loop = []; + for (let i = 0; i <= segs; i++) { + const ang = (i / segs) * Math.PI * 2; + loop.push({ x: toX(cx_mm + r_mm * Math.cos(ang)), z: toZ(cy_mm + r_mm * Math.sin(ang)) }); + } + return loop; + } + + const cX_L = aL - (125 + pDL / 2); + const cY_L = bL - (bchL + lb_d[0] + pDL / 2); + const pipeLoopL = createCylinderLoop(cX_L, cY_L, pDL / 2); + const pipeGridL = createSurfaceGrid(pipeLoopL); + + const cX_R = aR + (125 + pDR / 2); + const cY_R = bR - (bchR + rb_d[0] + pDR / 2); + const pipeLoopR = createCylinderLoop(cX_R, cY_R, pDR / 2); + const pipeGridR = createSurfaceGrid(pipeLoopR); + + // [5] 공동구 뚜껑 + const lidLoopL = [ + { x: toX(LP1.x - lwL), z: toZ(totalTopYL) }, + { x: toX(LP1.x), z: toZ(totalTopYL) }, + { x: toX(LP1.x), z: toZ(totalTopYL - lhL) }, + { x: toX(LP1.x - lwL), z: toZ(totalTopYL - lhL) }, + { x: toX(LP1.x - lwL), z: toZ(totalTopYL) } + ]; + const lidGridL = createSurfaceGrid(lidLoopL); + + const lidLoopR = [ + { x: toX(wallEndXR), z: toZ(totalTopYR) }, + { x: toX(wallEndXR + lwR), z: toZ(totalTopYR) }, + { x: toX(wallEndXR + lwR), z: toZ(totalTopYR - lhR) }, + { x: toX(wallEndXR), z: toZ(totalTopYR - lhR) }, + { x: toX(wallEndXR), z: toZ(totalTopYR) } + ]; + const lidGridR = createSurfaceGrid(lidLoopR); + + const unifiedConcreteGrid = createSurfaceGrid(unifiedConcreteLoop2D); + const shotcreteGrid = createSurfaceGrid(shotcreteLoop2D); + + // OBJ 내보내기용 루프 저장 + window.lastGeneratedLoops2D = [ + { name: 'lining_concrete', loop: unifiedConcreteLoop2D, visible: window.current3DVisibility?.lining !== false }, + { name: 'shotcrete', loop: shotcreteLoop2D, visible: window.current3DVisibility?.shotcrete !== false }, + ...paveLoops2D.map((pl, idx) => ({ name: `pavement_t${idx + 1}`, loop: pl.loop, visible: window.current3DVisibility?.pavement !== false })), + { name: 'blind_ditch_L', loop: bdLoopL, visible: window.current3DVisibility?.blindDitch !== false }, + { name: 'blind_ditch_R', loop: bdLoopR, visible: window.current3DVisibility?.blindDitch !== false }, + { name: 'drain_pipe_L', loop: pipeLoopL, visible: window.current3DVisibility?.drainPipe !== false }, + { name: 'drain_pipe_R', loop: pipeLoopR, visible: window.current3DVisibility?.drainPipe !== false }, + { name: 'lid_L', loop: lidLoopL, visible: window.current3DVisibility?.lids !== false }, + { name: 'lid_R', loop: lidLoopR, visible: window.current3DVisibility?.lids !== false } + ]; + + const doEarcut = (function() { + if (typeof earcut === 'function') return earcut; + if (typeof earcut !== 'undefined' && typeof earcut.default === 'function') return earcut.default; + if (typeof window !== 'undefined' && window.earcut) { + if (typeof window.earcut === 'function') return window.earcut; + if (typeof window.earcut.default === 'function') return window.earcut.default; + } + if (typeof globalThis !== 'undefined' && globalThis.earcut) { + if (typeof globalThis.earcut === 'function') return globalThis.earcut; + if (typeof globalThis.earcut.default === 'function') return globalThis.earcut.default; + } + return null; + })(); + + function createEndCapMesh(pts2D, yPos, color, name, opacity = 1.0, holePts2D = null) { + if (!doEarcut) return null; + const validPts = []; + for (let i = 0; i < pts2D.length; i++) { + const p = pts2D[i]; + if (validPts.length > 0) { + const prev = validPts[validPts.length - 1]; + if (Math.hypot(p.x - prev.x, p.z - prev.z) < 1e-5) continue; + } + validPts.push(p); + } + if (validPts.length > 2) { + const first = validPts[0], last = validPts[validPts.length - 1]; + if (Math.hypot(first.x - last.x, first.z - last.z) < 1e-5) { + validPts.pop(); + } + } + if (validPts.length < 3) return null; + + const flatPts = []; + for (let i = 0; i < validPts.length; i++) { + flatPts.push(validPts[i].x, validPts[i].z); + } + + let holeIndices = null; + if (holePts2D && holePts2D.length >= 3) { + const validHole = []; + for (let i = 0; i < holePts2D.length; i++) { + const p = holePts2D[i]; + if (validHole.length > 0) { + const prev = validHole[validHole.length - 1]; + if (Math.hypot(p.x - prev.x, p.z - prev.z) < 1e-5) continue; + } + validHole.push(p); + } + if (validHole.length > 2) { + const first = validHole[0], last = validHole[validHole.length - 1]; + if (Math.hypot(first.x - last.x, first.z - last.z) < 1e-5) { + validHole.pop(); + } + } + if (validHole.length >= 3) { + holeIndices = [validPts.length]; + for (let i = 0; i < validHole.length; i++) { + validPts.push(validHole[i]); + flatPts.push(validHole[i].x, validHole[i].z); + } + } + } + + const indices = doEarcut(flatPts, holeIndices, 2); + if (!indices || indices.length === 0) return null; + + const I = [], J = [], K = []; + for (let i = 0; i < indices.length; i += 3) { + if (yPos === 0) { + I.push(indices[i]); + J.push(indices[i + 2]); + K.push(indices[i + 1]); + } else { + I.push(indices[i]); + J.push(indices[i + 1]); + K.push(indices[i + 2]); + } + } + + return { + type: 'mesh3d', + x: validPts.map(p => p.x), + y: validPts.map(() => yPos), + z: validPts.map(p => p.z), + i: I, + j: J, + k: K, + color: color, + opacity: opacity, + name: `${name} (${yPos === 0 ? '시점' : '종점'} 마감면)`, + showlegend: false, + hoverinfo: 'skip', + flatshading: true, + lighting: { ambient: 0.85, diffuse: 0.8, roughness: 0.3 } + }; + } + + const plotData = []; + const modelOpacity = (typeof window.current3DOpacity === 'number') ? window.current3DOpacity : 1.0; + + if (window.current3DVisibility?.lining !== false) { + plotData.push({ + type: 'surface', + x: unifiedConcreteGrid.X, + y: unifiedConcreteGrid.Y, + z: unifiedConcreteGrid.Z, + colorscale: [[0, '#8da0b0'], [1, '#b8c5ce']], + showscale: false, + name: '터널 콘크리트 본체 (라이닝+공동구 일체형)', + opacity: modelOpacity, + lighting: { ambient: 0.75, diffuse: 0.8, roughness: 0.35, specular: 0.2 } + }); + [0, L].forEach(yPos => { + const cap = createEndCapMesh(unifiedConcreteLoop2D, yPos, '#a1b2c2', '라이닝 본체', modelOpacity); + if (cap) plotData.push(cap); + }); + } + + if (window.current3DVisibility?.shotcrete !== false) { + plotData.push({ + type: 'surface', + x: shotcreteGrid.X, + y: shotcreteGrid.Y, + z: shotcreteGrid.Z, + colorscale: [[0, '#334155'], [1, '#475569']], + showscale: false, + name: '숏크리트 층 (1차 지보재)', + opacity: modelOpacity, + lighting: { ambient: 0.7, diffuse: 0.75 } + }); + [0, L].forEach(yPos => { + const cap = createEndCapMesh(shotcreteLoop2D, yPos, '#3b4759', '숏크리트', modelOpacity); + if (cap) plotData.push(cap); + }); + } + + if (window.current3DVisibility?.pavement !== false) { + paveGridsData.forEach((item, idx) => { + plotData.push({ + type: 'surface', + x: item.grid.X, + y: item.grid.Y, + z: item.grid.Z, + colorscale: item.layer.colorscale, + showscale: false, + name: item.layer.name, + opacity: modelOpacity, + lighting: { ambient: 0.85, diffuse: 0.75, roughness: 0.3 } + }); + const paveColor = idx === 0 ? '#1e293b' : (idx === 1 ? '#cbd5e1' : '#64748b'); + [0, L].forEach(yPos => { + const cap = createEndCapMesh(paveLoops2D[idx].loop, yPos, paveColor, item.layer.name, modelOpacity); + if (cap) plotData.push(cap); + }); + }); + } + + if (window.current3DVisibility?.blindDitch !== false) { + plotData.push({ + type: 'surface', + x: bdGridL.X, + y: bdGridL.Y, + z: bdGridL.Z, + colorscale: [[0, '#78716c'], [1, '#a8a29e']], + showscale: false, + name: '좌측 맹암거 (투수필터층)', + opacity: modelOpacity, + lighting: { ambient: 0.8, diffuse: 0.7 } + }); + plotData.push({ + type: 'surface', + x: bdGridR.X, + y: bdGridR.Y, + z: bdGridR.Z, + colorscale: [[0, '#78716c'], [1, '#a8a29e']], + showscale: false, + name: '우측 맹암거 (투수필터층)', + opacity: modelOpacity, + lighting: { ambient: 0.8, diffuse: 0.7 } + }); + [0, L].forEach(yPos => { + // 종배수관과 겹치는 원형 단면 부위를 제외한 마감면 생성 + const capL = createEndCapMesh(bdLoopL, yPos, '#8c857f', '좌측 맹암거', modelOpacity, pipeLoopL); + if (capL) plotData.push(capL); + const capR = createEndCapMesh(bdLoopR, yPos, '#8c857f', '우측 맹암거', modelOpacity, pipeLoopR); + if (capR) plotData.push(capR); + }); + } + + if (window.current3DVisibility?.drainPipe !== false) { + plotData.push({ + type: 'surface', + x: pipeGridL.X, + y: pipeGridL.Y, + z: pipeGridL.Z, + colorscale: [[0, '#c2410c'], [1, '#f97316']], + showscale: false, + name: `좌측 종배수관 (D${pDL}mm)`, + opacity: modelOpacity, + lighting: { ambient: 0.9, diffuse: 0.8, roughness: 0.2 } + }); + plotData.push({ + type: 'surface', + x: pipeGridR.X, + y: pipeGridR.Y, + z: pipeGridR.Z, + colorscale: [[0, '#c2410c'], [1, '#f97316']], + showscale: false, + name: `우측 종배수관 (D${pDR}mm)`, + opacity: modelOpacity, + lighting: { ambient: 0.9, diffuse: 0.8, roughness: 0.2 } + }); + // 종배수관은 배수관이므로 앞뒤가 뚫려있는 상태(hollow) 유지 (마감면 추가 안함) + } + + if (window.current3DVisibility?.lids !== false) { + plotData.push({ + type: 'surface', + x: lidGridL.X, + y: lidGridL.Y, + z: lidGridL.Z, + colorscale: [[0, '#94a3b8'], [1, '#e2e8f0']], + showscale: false, + name: `좌측 공동구 뚜껑 (${lwL}x${lhL}mm)`, + opacity: modelOpacity, + lighting: { ambient: 0.85, diffuse: 0.8, roughness: 0.3 } + }); + plotData.push({ + type: 'surface', + x: lidGridR.X, + y: lidGridR.Y, + z: lidGridR.Z, + colorscale: [[0, '#94a3b8'], [1, '#e2e8f0']], + showscale: false, + name: `우측 공동구 뚜껑 (${lwR}x${lhR}mm)`, + opacity: modelOpacity, + lighting: { ambient: 0.85, diffuse: 0.8, roughness: 0.3 } + }); + [0, L].forEach(yPos => { + const capL = createEndCapMesh(lidLoopL, yPos, '#cbd5e1', '좌측 공동구 뚜껑', modelOpacity); + if (capL) plotData.push(capL); + const capR = createEndCapMesh(lidLoopR, yPos, '#cbd5e1', '우측 공동구 뚜껑', modelOpacity); + if (capR) plotData.push(capR); + }); + } + + if (window.current3DVisibility?.lanes !== false) { + const leftLineX = toX(0); + const leftLineZ = toZ(0) + 0.02; + plotData.push({ + type: 'scatter3d', + mode: 'lines', + x: [leftLineX, leftLineX], + y: [0, L], + z: [leftLineZ, leftLineZ], + line: { color: '#facc15', width: 6 }, // 마커 1: 황색 실선 (Yellow) + name: '가장자리선 (좌측, 황색)', + showlegend: false + }); + + const rightLineX = toX(tw); + const rightLineZ = toZ(m * tw) + 0.02; + plotData.push({ + type: 'scatter3d', + mode: 'lines', + x: [rightLineX, rightLineX], + y: [0, L], + z: [rightLineZ, rightLineZ], + line: { color: '#ffffff', width: 5 }, // 마커 5: 흰색 실선 (White) + name: '가장자리선 (우측, 흰색)', + showlegend: false + }); + + let curLaneDist = 0; + for (let li = 0; li < lanes.length - 1; li++) { + curLaneDist += lanes[li]; + const laneDividerX = toX(curLaneDist); + const laneDividerZ = toZ(m * curLaneDist) + 0.02; + plotData.push({ + type: 'scatter3d', + mode: 'lines', + x: [laneDividerX, laneDividerX], + y: [0, L], + z: [laneDividerZ, laneDividerZ], + line: { color: '#ffffff', width: 5 }, // 흰색선 (White) + name: `차선 마킹 ${li + 2} (흰색)`, + showlegend: false + }); + } + } + + if (window.current3DVisibility?.rims !== false) { + [0, L].forEach(yPos => { + plotData.push({ + type: 'scatter3d', + mode: 'lines', + x: unifiedConcreteLoop2D.map(pt => pt.x), + y: unifiedConcreteLoop2D.map(() => yPos), + z: unifiedConcreteLoop2D.map(pt => pt.z), + line: { color: '#10b981', width: 6 }, + showlegend: false + }); + plotData.push({ + type: 'scatter3d', + mode: 'lines', + x: shotcreteLoop2D.map(pt => pt.x), + y: shotcreteLoop2D.map(() => yPos), + z: shotcreteLoop2D.map(pt => pt.z), + line: { color: '#06b6d4', width: 5 }, + showlegend: false + }); + paveLoops2D.forEach(pItem => { + plotData.push({ + type: 'scatter3d', + mode: 'lines', + x: pItem.loop.map(pt => pt.x), + y: pItem.loop.map(() => yPos), + z: pItem.loop.map(pt => pt.z), + line: { color: pItem.layer.rimColor, width: 4 }, + showlegend: false + }); + }); + [bdLoopL, bdLoopR].forEach(bLoop => { + plotData.push({ + type: 'scatter3d', + mode: 'lines', + x: bLoop.map(pt => pt.x), + y: bLoop.map(() => yPos), + z: bLoop.map(pt => pt.z), + line: { color: '#d97706', width: 4 }, + showlegend: false + }); + }); + [pipeLoopL, pipeLoopR].forEach(pLoop => { + plotData.push({ + type: 'scatter3d', + mode: 'lines', + x: pLoop.map(pt => pt.x), + y: pLoop.map(() => yPos), + z: pLoop.map(pt => pt.z), + line: { color: '#ea580c', width: 4 }, + showlegend: false + }); + }); + [lidLoopL, lidLoopR].forEach(lLoop => { + plotData.push({ + type: 'scatter3d', + mode: 'lines', + x: lLoop.map(pt => pt.x), + y: lLoop.map(() => yPos), + z: lLoop.map(pt => pt.z), + line: { color: '#0284c7', width: 4 }, + showlegend: false + }); + }); + }); + } + + const initialCam = (window.current3DViewMode === 'inside') + ? { + eye: { x: 0, y: -0.05, z: 0.1 }, + center: { x: 0, y: 0.5, z: 0.1 }, + up: { x: 0, y: 0, z: 1 } + } + : { + eye: { x: 1.15, y: -1.35, z: 0.85 }, + center: { x: 0, y: 0, z: 0.1 }, + up: { x: 0, y: 0, z: 1 } + }; + + let targetCam = initialCam; + if (preserveCam && plotDiv) { + if (plotDiv._fullLayout?.scene?._scene?.getCamera) { + try { + const c = plotDiv._fullLayout.scene._scene.getCamera(); + if (c && c.eye) targetCam = c; + } catch (e) {} + } else if (plotDiv.layout?.scene?.camera) { + targetCam = plotDiv.layout.scene.camera; + } + } + + const isDark = (window.current3DTheme !== 'light'); + const bgColor = isDark ? '#111827' : '#ffffff'; + const legendFontColor = isDark ? '#e5e7eb' : '#1f2937'; + const legendBgColor = isDark ? 'rgba(17, 24, 39, 0.75)' : 'rgba(255, 255, 255, 0.85)'; + + const layout = { + margin: { l: 0, r: 0, b: 0, t: 0 }, + paper_bgcolor: bgColor, + plot_bgcolor: bgColor, + scene: { + bgcolor: bgColor, + aspectmode: 'data', + camera: targetCam, + xaxis: { visible: false, showgrid: false, showline: false, showticklabels: false, showbackground: false, zeroline: false }, + yaxis: { visible: false, showgrid: false, showline: false, showticklabels: false, showbackground: false, zeroline: false }, + zaxis: { visible: false, showgrid: false, showline: false, showticklabels: false, showbackground: false, zeroline: false } + }, + showlegend: false, + }; + + Plotly.react(plotDiv, plotData, layout, { responsive: true, displaylogo: false }); + } catch (err) { + console.error('3D 뷰어 렌더링 오류:', err); + } +} + +// ───────────────────────────────────────────────────────────────────────────── +// 5. 2D 단면 뷰어 렌더링 +// ───────────────────────────────────────────────────────────────────────────── +function render2DSection() { + const canvas = $('modelCanvas'); + if (!canvas) return; + const ctx = canvas.getContext('2d'); + const rect = canvas.getBoundingClientRect(); + const dpr = window.devicePixelRatio || 1; + const w = Math.max(1, rect.width); + const h = Math.max(1, rect.height); + canvas.width = Math.floor(w * dpr); + canvas.height = Math.floor(h * dpr); + ctx.setTransform(dpr, 0, 0, dpr, 0, 0); + + const isDark = (window.current3DTheme !== 'light'); + ctx.fillStyle = isDark ? '#0c1118' : '#f8fafc'; + ctx.fillRect(0, 0, w, h); + + // 배경 그리드 + ctx.strokeStyle = isDark ? '#171f2a' : '#e2e8f0'; + ctx.lineWidth = 1; + const step = 36; + for (let x = (w / 2) % step; x < w; x += step) { + ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, h); ctx.stroke(); + } + for (let y = (h / 2) % step; y < h; y += step) { + ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(w, y); ctx.stroke(); + } + + const loops = window.lastGeneratedLoops2D || []; + if (!loops.length) return; + + // 전체 경계 계산 + let minX = Infinity, maxX = -Infinity, minZ = Infinity, maxZ = -Infinity; + loops.forEach(item => { + item.loop.forEach(pt => { + minX = Math.min(minX, pt.x); maxX = Math.max(maxX, pt.x); + minZ = Math.min(minZ, pt.z); maxZ = Math.max(maxZ, pt.z); + }); + }); + + const pad = 60; + const spanX = Math.max(1, maxX - minX); + const spanZ = Math.max(1, maxZ - minZ); + const baseScale = Math.min((w - pad * 2) / spanX, (h - pad * 2) / spanZ); + const scale = baseScale * window.view2D.zoom; + const cx = (minX + maxX) / 2; + const cz = (minZ + maxZ) / 2; + + const toScreenX = x => w / 2 + window.view2D.panX + (x - cx) * scale; + const toScreenY = z => h / 2 + window.view2D.panY - (z - cz) * scale; + + const styleMap = { + shotcrete: { fill: 'rgba(71, 85, 105, 0.45)', stroke: '#06b6d4', width: 2 }, + lining_concrete: { fill: 'rgba(141, 160, 176, 0.55)', stroke: '#10b981', width: 2.2 }, + pavement_t1: { fill: 'rgba(30, 41, 59, 0.85)', stroke: '#38bdf8', width: 1.5 }, + pavement_t2: { fill: 'rgba(148, 163, 184, 0.65)', stroke: '#f59e0b', width: 1.5 }, + pavement_t3: { fill: 'rgba(100, 116, 139, 0.55)', stroke: '#c084fc', width: 1.5 }, + blind_ditch_L: { fill: 'rgba(217, 119, 6, 0.4)', stroke: '#d97706', width: 1.5 }, + blind_ditch_R: { fill: 'rgba(217, 119, 6, 0.4)', stroke: '#d97706', width: 1.5 }, + drain_pipe_L: { fill: 'rgba(234, 88, 12, 0.7)', stroke: '#ea580c', width: 1.8 }, + drain_pipe_R: { fill: 'rgba(234, 88, 12, 0.7)', stroke: '#ea580c', width: 1.8 }, + lid_L: { fill: 'rgba(203, 213, 225, 0.8)', stroke: '#0284c7', width: 1.5 }, + lid_R: { fill: 'rgba(203, 213, 225, 0.8)', stroke: '#0284c7', width: 1.5 } + }; + + loops.forEach(item => { + if (!item.visible || !item.loop.length) return; + const st = styleMap[item.name] || { fill: 'rgba(148,163,184,0.3)', stroke: '#94a3b8', width: 1.5 }; + ctx.beginPath(); + item.loop.forEach((pt, idx) => { + const sx = toScreenX(pt.x); + const sy = toScreenY(pt.z); + if (idx === 0) ctx.moveTo(sx, sy); + else ctx.lineTo(sx, sy); + }); + ctx.closePath(); + + // 맹암거의 경우 종배수관과 겹치는 원형 단면 부위를 제외하고 채우기 + const correspondingPipe = (item.name === 'blind_ditch_L') + ? loops.find(l => l.name === 'drain_pipe_L') + : (item.name === 'blind_ditch_R' ? loops.find(l => l.name === 'drain_pipe_R') : null); + + if (correspondingPipe && correspondingPipe.loop.length) { + correspondingPipe.loop.forEach((pt, idx) => { + const sx = toScreenX(pt.x); + const sy = toScreenY(pt.z); + if (idx === 0) ctx.moveTo(sx, sy); + else ctx.lineTo(sx, sy); + }); + ctx.closePath(); + ctx.fillStyle = st.fill; + ctx.fill('evenodd'); + } else { + ctx.fillStyle = st.fill; + ctx.fill(); + } + ctx.strokeStyle = st.stroke; + ctx.lineWidth = st.width; + ctx.stroke(); + }); + + // 캔버스 하단 저작권 및 개발자 워터마크 렌더링 + ctx.save(); + ctx.font = '11px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'; + ctx.fillStyle = isDark ? 'rgba(255, 255, 255, 0.45)' : 'rgba(15, 23, 42, 0.45)'; + ctx.textAlign = 'right'; + ctx.textBaseline = 'bottom'; + ctx.fillText('Developed by m12205.tdc.hanmacgroup@gmail.com · © 2026', w - 16, h - 12); + ctx.restore(); +} + +// ───────────────────────────────────────────────────────────────────────────── +// 6. OBJ 3D 모델 파일 내보내기 +// ───────────────────────────────────────────────────────────────────────────── +function exportObjModel() { + const loops = window.lastGeneratedLoops2D || []; + if (!loops.length) return; + + const L = window.current3DTunnelLength || 10; + const verts = []; + const faces = []; + + const doEarcut = (function() { + if (typeof earcut === 'function') return earcut; + if (typeof earcut !== 'undefined' && typeof earcut.default === 'function') return earcut.default; + if (typeof window !== 'undefined' && window.earcut) { + if (typeof window.earcut === 'function') return window.earcut; + if (typeof window.earcut.default === 'function') return window.earcut.default; + } + return null; + })(); + + loops.forEach(item => { + if (!item.visible || item.loop.length < 3) return; + const pts = item.loop; + const n = pts.length; + const baseIdx = verts.length + 1; + + // y = 0 단면 정점 및 y = L 단면 정점 추가 + for (let i = 0; i < n; i++) { + verts.push(`v ${pts[i].x.toFixed(4)} 0.0000 ${pts[i].z.toFixed(4)}`); + } + for (let i = 0; i < n; i++) { + verts.push(`v ${pts[i].x.toFixed(4)} ${L.toFixed(4)} ${pts[i].z.toFixed(4)}`); + } + + faces.push(`g ${item.name}`); + // 측면 면(Quad) 생성 + for (let i = 0; i < n - 1; i++) { + const a0 = baseIdx + i; + const b0 = baseIdx + i + 1; + const a1 = baseIdx + n + i; + const b1 = baseIdx + n + i + 1; + faces.push(`f ${a0} ${b0} ${b1}`); + faces.push(`f ${a0} ${b1} ${a1}`); + } + + // 종배수관(drain_pipe)은 뚫려있는 관이므로 마감면 제외, 그 외 솔리드 객체는 앞뒤 마감면(End-caps) 추가 + if (doEarcut && !item.name.includes('drain_pipe')) { + const validIndices = []; + for (let i = 0; i < n - 1; i++) validIndices.push(i); + + const flatPts = []; + for (let i = 0; i < validIndices.length; i++) { + flatPts.push(pts[validIndices[i]].x, pts[validIndices[i]].z); + } + + let holeIndices = null; + let extraHoleVerts = 0; + if (item.name.startsWith('blind_ditch_')) { + const pipeName = item.name === 'blind_ditch_L' ? 'drain_pipe_L' : 'drain_pipe_R'; + const pipeItem = loops.find(l => l.name === pipeName); + if (pipeItem && pipeItem.loop.length >= 3) { + const pipePts = pipeItem.loop; + const pipeN = pipePts.length - 1; + holeIndices = [validIndices.length]; + extraHoleVerts = pipeN; + for (let pi = 0; pi < pipeN; pi++) { + verts.push(`v ${pipePts[pi].x.toFixed(4)} 0.0000 ${pipePts[pi].z.toFixed(4)}`); + flatPts.push(pipePts[pi].x, pipePts[pi].z); + } + for (let pi = 0; pi < pipeN; pi++) { + verts.push(`v ${pipePts[pi].x.toFixed(4)} ${L.toFixed(4)} ${pipePts[pi].z.toFixed(4)}`); + } + } + } + + const tris = doEarcut(flatPts, holeIndices, 2); + if (tris && tris.length > 0) { + const getV0 = vi => { + if (vi < n - 1) return baseIdx + vi; + return baseIdx + 2 * n + (vi - (n - 1)); + }; + const getVL = vi => { + if (vi < n - 1) return baseIdx + n + vi; + return baseIdx + 2 * n + extraHoleVerts + (vi - (n - 1)); + }; + + // Front cap (y = 0) + for (let i = 0; i < tris.length; i += 3) { + const i0 = getV0(tris[i]); + const i1 = getV0(tris[i + 2]); + const i2 = getV0(tris[i + 1]); + faces.push(`f ${i0} ${i1} ${i2}`); + } + // Back cap (y = L) + for (let i = 0; i < tris.length; i += 3) { + const i0 = getVL(tris[i]); + const i1 = getVL(tris[i + 1]); + const i2 = getVL(tris[i + 2]); + faces.push(`f ${i0} ${i1} ${i2}`); + } + } + } + }); + + const objContent = `# Tunnel 3D Model (${L}m)\n` + verts.join('\n') + '\n' + faces.join('\n') + '\n'; + const blob = new Blob([objContent], { type: 'text/plain;charset=utf-8' }); + const a = document.createElement('a'); + a.href = URL.createObjectURL(blob); + a.download = `tunnel_model_${L}m.obj`; + a.click(); + URL.revokeObjectURL(a.href); +} + +// ───────────────────────────────────────────────────────────────────────────── +// 6-2. Rhino 3DM (.3dm) 3D 모델 파일 내보내기 +// ───────────────────────────────────────────────────────────────────────────── +let _rhinoInstance = null; +async function getRhinoInstance() { + if (_rhinoInstance) return _rhinoInstance; + if (typeof rhino3dm !== 'function') { + // 로컬 스크립트 미탑재 또는 지연 시 CDN 동적 로드 시도 + await new Promise((resolve, reject) => { + const s = document.createElement('script'); + s.src = 'https://cdn.jsdelivr.net/npm/rhino3dm@8.35.0/rhino3dm.min.js'; + s.onload = resolve; + s.onerror = () => reject(new Error('rhino3dm 스크립트를 불러오지 못했습니다. 네트워크 연결 상태를 확인해주세요.')); + document.head.appendChild(s); + }); + } + if (typeof rhino3dm !== 'function') { + throw new Error('rhino3dm 라이브러리를 초기화할 수 없습니다.'); + } + _rhinoInstance = await rhino3dm({ + locateFile: (path) => { + if (location.protocol === 'file:') { + return 'https://cdn.jsdelivr.net/npm/rhino3dm@8.35.0/' + path; + } + return path; + } + }); + return _rhinoInstance; +} + +async function export3dmModel() { + const loops = window.lastGeneratedLoops2D || []; + if (!loops.length) { + alert('내보낼 3D 모델 형상이 없습니다.'); + return; + } + + const exportBtn = $('export3dm'); + const origBtnText = exportBtn ? exportBtn.textContent : ''; + if (exportBtn) { + exportBtn.disabled = true; + exportBtn.textContent = '3DM 생성 중…'; + } + + try { + const rhino = await getRhinoInstance(); + const doc = new rhino.File3dm(); + if (rhino.UnitSystem && rhino.UnitSystem.Meters) { + doc.settings().modelUnitSystem = rhino.UnitSystem.Meters; + } + const L = window.current3DTunnelLength || 10; + + // 레이어 사전 정의 및 생성 + const layerConfigs = [ + { id: 'lining_concrete', name: 'lining_concrete', color: { r: 141, g: 160, b: 176, a: 255 } }, + { id: 'shotcrete', name: 'shotcrete', color: { r: 71, g: 85, b: 105, a: 255 } }, + { id: 'pavement', name: 'pavement', color: { r: 30, g: 41, b: 59, a: 255 } }, + { id: 'lids', name: 'lids', color: { r: 203, g: 213, b: 225, a: 255 } }, + { id: 'blind_ditch', name: 'blind_ditch', color: { r: 217, g: 119, b: 6, a: 255 } }, + { id: 'drain_pipe', name: 'drain_pipe', color: { r: 56, g: 189, b: 248, a: 255 } } + ]; + + const layerMap = {}; + layerConfigs.forEach(cfg => { + const lay = new rhino.Layer(); + lay.name = cfg.name; + lay.color = cfg.color; + const idx = doc.layers().add(lay); + layerMap[cfg.id] = idx; + }); + + const getLayerId = (name) => { + if (name === 'shotcrete') return 'shotcrete'; + if (name === 'lining') return 'lining_concrete'; + if (name.startsWith('pavement')) return 'pavement'; + if (name.startsWith('lid_')) return 'lids'; + if (name.startsWith('blind_ditch_')) return 'blind_ditch'; + if (name.startsWith('drain_pipe_')) return 'drain_pipe'; + return 'lining_concrete'; + }; + + const doEarcut = (function() { + if (typeof earcut === 'function') return earcut; + if (typeof earcut !== 'undefined' && typeof earcut.default === 'function') return earcut.default; + if (typeof window !== 'undefined' && window.earcut) { + if (typeof window.earcut === 'function') return window.earcut; + if (typeof window.earcut.default === 'function') return window.earcut.default; + } + return null; + })(); + + loops.forEach(item => { + if (!item.visible || item.loop.length < 3) return; + const pts = item.loop; + const n = pts.length; + const mesh = new rhino.Mesh(); + + // y = 0 단면 정점 및 y = L 단면 정점 추가 + for (let i = 0; i < n; i++) { + mesh.vertices().add(pts[i].x, 0.0, pts[i].z); + } + for (let i = 0; i < n; i++) { + mesh.vertices().add(pts[i].x, L, pts[i].z); + } + + // 측면 쿼드 면 추가 (Quad) + for (let i = 0; i < n - 1; i++) { + const a0 = i; + const b0 = i + 1; + const a1 = n + i; + const b1 = n + i + 1; + mesh.faces().addQuadFace(a0, b0, b1, a1); + } + + // 종배수관(drain_pipe)은 뚫려있는 관이므로 마감면 제외, 그 외 솔리드 객체는 앞뒤 마감면(End-caps) 추가 + if (doEarcut && !item.name.includes('drain_pipe')) { + const validIndices = []; + for (let i = 0; i < n - 1; i++) validIndices.push(i); + + const flatPts = []; + for (let i = 0; i < validIndices.length; i++) { + flatPts.push(pts[validIndices[i]].x, pts[validIndices[i]].z); + } + + let holeIndices = null; + let extraHoleVerts = 0; + if (item.name.startsWith('blind_ditch_')) { + const pipeName = item.name === 'blind_ditch_L' ? 'drain_pipe_L' : 'drain_pipe_R'; + const pipeItem = loops.find(l => l.name === pipeName); + if (pipeItem && pipeItem.loop.length >= 3) { + const pipePts = pipeItem.loop; + const pipeN = pipePts.length - 1; + holeIndices = [validIndices.length]; + extraHoleVerts = pipeN; + for (let pi = 0; pi < pipeN; pi++) { + mesh.vertices().add(pipePts[pi].x, 0.0, pipePts[pi].z); + flatPts.push(pipePts[pi].x, pipePts[pi].z); + } + for (let pi = 0; pi < pipeN; pi++) { + mesh.vertices().add(pipePts[pi].x, L, pipePts[pi].z); + } + } + } + + const tris = doEarcut(flatPts, holeIndices, 2); + if (tris && tris.length > 0) { + const getV0 = vi => { + if (vi < n - 1) return vi; + return 2 * n + (vi - (n - 1)); + }; + const getVL = vi => { + if (vi < n - 1) return n + vi; + return 2 * n + extraHoleVerts + (vi - (n - 1)); + }; + + // Front cap (y = 0) + for (let i = 0; i < tris.length; i += 3) { + const i0 = getV0(tris[i]); + const i1 = getV0(tris[i + 2]); + const i2 = getV0(tris[i + 1]); + mesh.faces().addTriFace(i0, i1, i2); + } + // Back cap (y = L) + for (let i = 0; i < tris.length; i += 3) { + const i0 = getVL(tris[i]); + const i1 = getVL(tris[i + 1]); + const i2 = getVL(tris[i + 2]); + mesh.faces().addTriFace(i0, i1, i2); + } + } + } + + // 노멀 계산 + mesh.normals().computeNormals(); + mesh.compact(); + + // 객체 속성 및 레이어 설정 + const attr = new rhino.ObjectAttributes(); + const layerId = getLayerId(item.name); + if (layerMap[layerId] !== undefined) { + attr.layerIndex = layerMap[layerId]; + } + attr.name = item.name; + + doc.objects().addMesh(mesh, attr); + }); + + const bytes = doc.toByteArray(); + const blob = new Blob([bytes], { type: 'application/octet-stream' }); + const a = document.createElement('a'); + a.href = URL.createObjectURL(blob); + a.download = `tunnel_model_${L}m.3dm`; + a.click(); + URL.revokeObjectURL(a.href); + doc.delete(); + } catch (err) { + console.error('3DM 내보내기 실패:', err); + alert('3DM 내보내기 중 오류가 발생했습니다: ' + (err.message || err)); + } finally { + if (exportBtn) { + exportBtn.disabled = false; + exportBtn.textContent = origBtnText; + } + } +} + +// ───────────────────────────────────────────────────────────────────────────── +// 7. 파일 불러오기 및 이벤트 바인딩 +// ───────────────────────────────────────────────────────────────────────────── +async function checkServerEngineStatus() { + try { + const r = await fetch('/api/status'); + const s = await r.json(); + $('engineStatus').textContent = s.converterAvailable ? 'DWG 변환 엔진 준비됨' : 'DWG 변환기 미설치 · DXF 직독 가능'; + $('statusDot').style.background = s.converterAvailable ? '#57c9a9' : '#e7b75c'; + } catch { + $('engineStatus').textContent = 'DXF 로컬 처리 준비됨'; + } +} + +async function handleCadFileLoad(file) { + if (!file) return; + const metaEl = $('fileMeta'); + if (metaEl) { + metaEl.textContent = `${file.name} 분석 및 3D 모델 생성 중…`; + metaEl.classList.remove('hidden'); + } + + try { + let dxfText = ''; + const sigBuf = await file.slice(0, 8).arrayBuffer(); + const sig = new TextDecoder().decode(sigBuf); + const isDwg = file.name.toLowerCase().endsWith('.dwg') || /^AC10\d{2}/.test(sig); + + if (isDwg) { + if (location.protocol !== 'http:' && location.protocol !== 'https:') { + throw new Error('DWG 변환은 start.bat으로 로컬 서버를 실행한 뒤 이용할 수 있습니다. (DXF 파일은 바로 불러올 수 있습니다)'); + } + $('engineStatus').textContent = 'DWG → DXF 변환 중…'; + const convName = file.name.toLowerCase().endsWith('.dwg') ? file.name : file.name + '.dwg'; + const res = await fetch('/api/convert', { + method: 'POST', + headers: { 'Content-Type': 'application/octet-stream', 'X-Filename': encodeURIComponent(convName) }, + body: file + }); + if (!res.ok) { + const err = await res.json().catch(() => ({ error: 'DWG 변환 실패' })); + throw new Error(err.error || 'DWG 변환 실패'); + } + dxfText = await res.text(); + $('engineStatus').textContent = 'DWG 변환 완료'; + } else { + dxfText = await file.text(); + } + + const parser = new window.DxfParser(); + const dxf = parser.parseSync(dxfText); + extractParamsFromDXF(dxf, file.name); + if (metaEl) { + metaEl.textContent = `✅ ${file.name} · 총 ${(dxf.entities || []).length.toLocaleString()}개 CAD 객체 분석 완료`; + } + } catch (err) { + console.error('도면 로드 실패:', err); + if (metaEl) { + metaEl.textContent = `⚠️ ${err.message || '도면 분석 중 오류가 발생했습니다.'}`; + } + } +} + +window.addEventListener('DOMContentLoaded', () => { + checkServerEngineStatus(); + update3DObjectButtons(); + + // 파일 선택 및 드래그 앤 드롭 + const fileInput = $('fileInput'); + if (fileInput) { + fileInput.addEventListener('change', e => { + if (e.target.files && e.target.files[0]) { + handleCadFileLoad(e.target.files[0]); + e.target.value = ''; + } + }); + } + + const dropzone = $('dropzone'); + if (dropzone) { + dropzone.addEventListener('dragover', e => { + e.preventDefault(); + dropzone.classList.add('dragging'); + }); + dropzone.addEventListener('dragleave', () => dropzone.classList.remove('dragging')); + dropzone.addEventListener('drop', e => { + e.preventDefault(); + dropzone.classList.remove('dragging'); + if (e.dataTransfer.files && e.dataTransfer.files[0]) { + handleCadFileLoad(e.dataTransfer.files[0]); + } + }); + } + + // 전체 창 드래그 앤 드롭 지원 + window.addEventListener('dragover', e => e.preventDefault()); + window.addEventListener('drop', e => { + e.preventDefault(); + if (e.dataTransfer?.files?.[0]) { + handleCadFileLoad(e.dataTransfer.files[0]); + } + }); + + // 예제 DXF 불러오기 + $('loadSample')?.addEventListener('click', async () => { + const metaEl = $('fileMeta'); + try { + if (metaEl) { + metaEl.textContent = '예제 DXF 도면을 불러오는 중…'; + metaEl.classList.remove('hidden'); + } + let res = await fetch('sample.dxf').catch(() => null); + if (!res || !res.ok) { + res = await fetch('/api/sample'); + } + if (!res || !res.ok) { + throw new Error('예제 DXF 파일을 불러오지 못했습니다.'); + } + const text = await res.text(); + const parser = new window.DxfParser(); + const dxf = parser.parseSync(text); + extractParamsFromDXF(dxf, '표준단면 예제 (sample.dxf)'); + if (metaEl) { + metaEl.textContent = `✅ 표준단면 예제 (sample.dxf) · ${(dxf.entities || []).length.toLocaleString()}개 객체 분석 완료`; + } + } catch (err) { + if (metaEl) { + metaEl.textContent = `⚠️ ${err.message}`; + } + } + }); + + // 길이 입력 및 프리셋 버튼 + const lengthInput = $('lengthInput'); + const updateLength = len => { + const validLen = Math.max(1, Math.min(500, Number(len) || 10)); + window.current3DTunnelLength = validLen; + if (lengthInput) lengthInput.value = validLen; + document.querySelectorAll('.preset-len-btn').forEach(btn => { + btn.classList.toggle('active', Number(btn.dataset.len) === validLen); + }); + buildCurrentSection3DPlot(true); + }; + + lengthInput?.addEventListener('change', e => updateLength(e.target.value)); + document.querySelectorAll('.preset-len-btn').forEach(btn => { + btn.addEventListener('click', () => updateLength(btn.dataset.len)); + }); + + // 단면 분석 및 3D 생성 버튼 + $('buildButton')?.addEventListener('click', () => { + readParamsFromInputs(); + buildCurrentSection3DPlot(false); + render2DSection(); + }); + + // 상세 제원 편집 토글 및 반영 + $('toggleParamsBtn')?.addEventListener('click', () => { + const panel = $('paramEditorPanel'); + if (panel) panel.classList.toggle('hidden'); + }); + + $('applyParamsBtn')?.addEventListener('click', () => { + readParamsFromInputs(); + renderDetectionList('사용자 수정 제원', 0); + buildCurrentSection3DPlot(true); + render2DSection(); + }); + + // 상단 뷰어 툴바 버튼들 + const opacitySlider = $('opacitySlider'); + if (opacitySlider) { + opacitySlider.addEventListener('input', e => { + const val = parseInt(e.target.value, 10); + window.current3DOpacity = Math.max(10, Math.min(100, val)) / 100.0; + window.current3DOuterOpacity = window.current3DOpacity; + const label = $('opacityVal'); + if (label) label.textContent = `${val}%`; + buildCurrentSection3DPlot(true); + }); + } + $('btnToggleOuter')?.addEventListener('click', toggle3DOuterShell); + $('btnViewInside')?.addEventListener('click', () => set3DCameraView('inside')); + $('btnViewBird')?.addEventListener('click', () => set3DCameraView('bird')); + $('btnThemeDark')?.addEventListener('click', () => set3DTheme('dark')); + $('btnThemeLight')?.addEventListener('click', () => set3DTheme('light')); + $('resetView')?.addEventListener('click', () => { + window.view2D = { panX: 0, panY: 0, zoom: 1, dragging: false, lastX: 0, lastY: 0 }; + set3DCameraView('bird'); + render2DSection(); + }); + $('exportObj')?.addEventListener('click', exportObjModel); + $('export3dm')?.addEventListener('click', export3dmModel); + + // 3D 모델 / 2D 단면 탭 전환 + document.querySelectorAll('.view-tab').forEach(tab => { + tab.addEventListener('click', () => { + const mode = tab.dataset.view; + window.activeViewTab = mode; + document.querySelectorAll('.view-tab').forEach(t => t.classList.toggle('active', t === tab)); + if (mode === '2d') { + $('placement_3d_plot')?.classList.add('hidden'); + $('modelCanvas')?.classList.remove('hidden'); + render2DSection(); + } else { + $('modelCanvas')?.classList.add('hidden'); + $('placement_3d_plot')?.classList.remove('hidden'); + buildCurrentSection3DPlot(true); + } + }); + }); + + // 2D 캔버스 마우스 이동 및 줌 + const canvas2d = $('modelCanvas'); + if (canvas2d) { + canvas2d.addEventListener('pointerdown', e => { + window.view2D.dragging = true; + window.view2D.lastX = e.clientX; + window.view2D.lastY = e.clientY; + canvas2d.setPointerCapture(e.pointerId); + }); + canvas2d.addEventListener('pointermove', e => { + if (!window.view2D.dragging) return; + window.view2D.panX += e.clientX - window.view2D.lastX; + window.view2D.panY += e.clientY - window.view2D.lastY; + window.view2D.lastX = e.clientX; + window.view2D.lastY = e.clientY; + render2DSection(); + }); + canvas2d.addEventListener('pointerup', () => { window.view2D.dragging = false; }); + canvas2d.addEventListener('wheel', e => { + e.preventDefault(); + window.view2D.zoom = Math.max(0.25, Math.min(6, window.view2D.zoom * Math.exp(-e.deltaY * 0.001))); + render2DSection(); + }, { passive: false }); + } + + window.addEventListener('resize', () => { + if (window.activeViewTab === '2d') render2DSection(); + }); +}); diff --git a/dxf-parser.js b/dxf-parser.js new file mode 100644 index 0000000..661e218 --- /dev/null +++ b/dxf-parser.js @@ -0,0 +1 @@ +!function(e,a){"object"==typeof exports&&"object"==typeof module?module.exports=a():"function"==typeof define&&define.amd?define([],a):"object"==typeof exports?exports.DxfParser=a():e.DxfParser=a()}("undefined"!=typeof self?self:this,(function(){return(()=>{var e={43:function(e,a,t){var r,n;!function(s,o){"use strict";void 0===(n="function"==typeof(r=function(){var e=function(){},a="undefined",t=typeof window!==a&&typeof window.navigator!==a&&/Trident\/|MSIE /.test(window.navigator.userAgent),r=["trace","debug","info","warn","error"];function n(e,a){var t=e[a];if("function"==typeof t.bind)return t.bind(e);try{return Function.prototype.bind.call(t,e)}catch(a){return function(){return Function.prototype.apply.apply(t,[e,arguments])}}}function s(){console.log&&(console.log.apply?console.log.apply(console,arguments):Function.prototype.apply.apply(console.log,[console,arguments])),console.trace&&console.trace()}function o(r){return"debug"===r&&(r="log"),typeof console!==a&&("trace"===r&&t?s:void 0!==console[r]?n(console,r):void 0!==console.log?n(console,"log"):e)}function c(a,t){for(var n=0;n=0&&t<=o.levels.SILENT))throw"log.setLevel() called with invalid level: "+t;if(s=t,!1!==n&&function(e){var t=(r[e]||"silent").toUpperCase();if(typeof window!==a&&l){try{return void(window.localStorage[l]=t)}catch(e){}try{window.document.cookie=encodeURIComponent(l)+"="+t+";"}catch(e){}}}(t),c.call(o,t,e),typeof console===a&&t{var a=e&&e.__esModule?()=>e.default:()=>e;return t.d(a,{a}),a},t.d=(e,a)=>{for(var r in a)t.o(a,r)&&!t.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:a[r]})},t.o=(e,a)=>Object.prototype.hasOwnProperty.call(e,a);var r={};return(()=>{"use strict";t.d(r,{DxfParser:()=>T});class e{constructor(e){this._pointer=0,this._eof=!1,this._data=e}next(){if(!this.hasNext())throw this._eof?new Error("Cannot call 'next' after EOF group has been read"):new Error("Unexpected end of input: EOF group not read before end of file. Ended on code "+this._data[this._pointer]);const e={code:parseInt(this._data[this._pointer])};return this._pointer++,e.value=a(e.code,this._data[this._pointer].trim()),this._pointer++,0===e.code&&"EOF"===e.value&&(this._eof=!0),this.lastReadGroup=e,e}peek(){if(!this.hasNext())throw this._eof?new Error("Cannot call 'next' after EOF group has been read"):new Error("Unexpected end of input: EOF group not read before end of file. Ended on code "+this._data[this._pointer]);const e={code:parseInt(this._data[this._pointer])};return e.value=a(e.code,this._data[this._pointer+1].trim()),e}rewind(e=1){this._pointer=this._pointer-2*e}hasNext(){return!(this._eof||this._pointer>this._data.length-2)}isEOF(){return this._eof}}function a(e,a){return e<=9?a:e>=10&&e<=59?parseFloat(a):e>=60&&e<=99?parseInt(a):e>=100&&e<=109?a:e>=110&&e<=149?parseFloat(a):e>=160&&e<=179?parseInt(a):e>=210&&e<=239?parseFloat(a):e>=270&&e<=289?parseInt(a):e>=290&&e<=299?function(e){if("0"===e)return!1;if("1"===e)return!0;throw TypeError("String '"+e+"' cannot be cast to Boolean type")}(a):e>=300&&e<=369?a:e>=370&&e<=389?parseInt(a):e>=390&&e<=399?a:e>=400&&e<=409?parseInt(a):e>=410&&e<=419?a:e>=420&&e<=429?parseInt(a):e>=430&&e<=439?a:e>=440&&e<=459?parseInt(a):e>=460&&e<=469?parseFloat(a):e>=470&&e<=481||999===e||e>=1e3&&e<=1009?a:e>=1010&&e<=1059?parseFloat(a):e>=1060&&e<=1071?parseInt(a):(console.log("WARNING: Group code does not have a defined type: %j",{code:e,value:a}),a)}const n=[0,16711680,16776960,65280,65535,255,16711935,16777215,8421504,12632256,16711680,16744319,13369344,13395558,10027008,10046540,8323072,8339263,4980736,4990502,16727808,16752511,13382400,13401958,10036736,10051404,8331008,8343359,4985600,4992806,16744192,16760703,13395456,13408614,10046464,10056268,8339200,8347455,4990464,4995366,16760576,16768895,13408512,13415014,10056192,10061132,8347392,8351551,4995328,4997670,16776960,16777087,13421568,13421670,10000384,10000460,8355584,8355647,5000192,5000230,12582656,14679935,10079232,11717734,7510016,8755276,6258432,7307071,3755008,4344870,8388352,12582783,6736896,10079334,5019648,7510092,4161280,6258495,2509824,3755046,4194048,10485631,3394560,8375398,2529280,6264908,2064128,5209919,1264640,3099686,65280,8388479,52224,6736998,38912,5019724,32512,4161343,19456,2509862,65343,8388511,52275,6737023,38950,5019743,32543,4161359,19475,2509871,65407,8388543,52326,6737049,38988,5019762,32575,4161375,19494,2509881,65471,8388575,52377,6737074,39026,5019781,32607,4161391,19513,2509890,65535,8388607,52428,6737100,39064,5019800,32639,4161407,19532,2509900,49151,8380415,39372,6730444,29336,5014936,24447,4157311,14668,2507340,32767,8372223,26316,6724044,19608,5010072,16255,4153215,9804,2505036,16383,8364031,13260,6717388,9880,5005208,8063,4149119,4940,2502476,255,8355839,204,6710988,152,5000344,127,4145023,76,2500172,4129023,10452991,3342540,8349388,2490520,6245528,2031743,5193599,1245260,3089996,8323327,12550143,6684876,10053324,4980888,7490712,4128895,6242175,2490444,3745356,12517631,14647295,10027212,11691724,7471256,8735896,6226047,7290751,3735628,4335180,16711935,16744447,13369548,13395660,9961624,9981080,8323199,8339327,4980812,4990540,16711871,16744415,13369497,13395634,9961586,9981061,8323167,8339311,4980793,4990530,16711807,16744383,13369446,13395609,9961548,9981042,8323135,8339295,4980774,4990521,16711743,16744351,13369395,13395583,9961510,9981023,8323103,8339279,4980755,4990511,3355443,5987163,8684676,11382189,14079702,16777215];function s(e){const a={};e.rewind();let t=e.next(),r=t.code;if(a.x=t.value,r+=10,t=e.next(),t.code!=r)throw new Error("Expected code for point value to be "+r+" but got "+t.code+".");return a.y=t.value,r+=10,t=e.next(),t.code!=r?(e.rewind(),a):(a.z=t.value,a)}function o(e,a,t){switch(a.code){case 0:e.type=a.value;break;case 5:e.handle=a.value;break;case 6:e.lineType=a.value;break;case 8:e.layer=a.value;break;case 48:e.lineTypeScale=a.value;break;case 60:e.visible=0===a.value;break;case 62:e.colorIndex=a.value,e.color=(r=Math.abs(a.value),n[r]);break;case 67:e.inPaperSpace=0!==a.value;break;case 100:break;case 101:for(;0!=a.code;)a=t.next();t.rewind();break;case 330:e.ownerHandle=a.value;break;case 347:e.materialObjectHandle=a.value;break;case 370:e.lineweight=a.value;break;case 420:e.color=a.value;break;case 1e3:e.extendedData=e.extendedData||{},e.extendedData.customStrings=e.extendedData.customStrings||[],e.extendedData.customStrings.push(a.value);break;case 1001:e.extendedData=e.extendedData||{},e.extendedData.applicationName=a.value;break;default:return!1}var r;return!0}class c{constructor(){this.ForEntityName="3DFACE"}parseEntity(e,a){const t={type:a.value,vertices:[]};for(a=e.next();!e.isEOF()&&0!==a.code;){switch(a.code){case 70:t.shape=1==(1&a.value),t.hasContinuousLinetypePattern=128==(128&a.value);break;case 10:t.vertices=l(e,a),a=e.lastReadGroup;break;default:o(t,a,e)}a=e.next()}return t}}function l(e,a){var t=[],r=!1,n=!1;for(let o=0;o<=4;o++){for(var s={};!e.isEOF()&&0!==a.code&&!n;){switch(a.code){case 10:case 11:case 12:case 13:if(r){n=!0;continue}s.x=a.value,r=!0;break;case 20:case 21:case 22:case 23:s.y=a.value;break;case 30:case 31:case 32:case 33:s.z=a.value;break;default:return t}a=e.next()}t.push(s),r=!1,n=!1}return e.rewind(),t}class i{constructor(){this.ForEntityName="ARC"}parseEntity(e,a){const t={type:a.value};for(a=e.next();!e.isEOF()&&0!==a.code;){switch(a.code){case 10:t.center=s(e);break;case 40:t.radius=a.value;break;case 50:t.startAngle=Math.PI/180*a.value;break;case 51:t.endAngle=Math.PI/180*a.value,t.angleLength=t.endAngle-t.startAngle;break;case 210:t.extrusionDirectionX=a.value;break;case 220:t.extrusionDirectionY=a.value;break;case 230:t.extrusionDirectionZ=a.value;break;default:o(t,a,e)}a=e.next()}return t}}class u{constructor(){this.ForEntityName="ATTDEF"}parseEntity(e,a){var t={type:a.value,scale:1,textStyle:"STANDARD"};for(a=e.next();!e.isEOF()&&0!==a.code;){switch(a.code){case 1:t.text=a.value;break;case 2:t.tag=a.value;break;case 3:t.prompt=a.value;break;case 7:t.textStyle=a.value;break;case 10:t.startPoint=s(e);break;case 11:t.endPoint=s(e);break;case 39:t.thickness=a.value;break;case 40:t.textHeight=a.value;break;case 41:t.scale=a.value;break;case 50:t.rotation=a.value;break;case 51:t.obliqueAngle=a.value;break;case 70:t.invisible=!!(1&a.value),t.constant=!!(2&a.value),t.verificationRequired=!!(4&a.value),t.preset=!!(8&a.value);break;case 71:t.backwards=!!(2&a.value),t.mirrored=!!(4&a.value);break;case 72:t.horizontalJustification=a.value;break;case 73:t.fieldLength=a.value;break;case 74:t.verticalJustification=a.value;break;case 100:break;case 210:t.extrusionDirectionX=a.value;break;case 220:t.extrusionDirectionY=a.value;break;case 230:t.extrusionDirectionZ=a.value;break;default:o(t,a,e)}a=e.next()}return t}}class d{constructor(){this.ForEntityName="CIRCLE"}parseEntity(e,a){const t={type:a.value};for(a=e.next();!e.isEOF()&&0!==a.code;){switch(a.code){case 10:t.center=s(e);break;case 40:t.radius=a.value;break;case 50:t.startAngle=Math.PI/180*a.value;break;case 51:const r=Math.PI/180*a.value;r{e.on("data",(e=>{a+=e})),e.on("end",(()=>{try{r(t._parse(a))}catch(e){n(e)}})),e.on("error",(e=>{n(e)}))}))}_parse(a){const t={};let r=0;const s=a.split(/\r\n|\r|\n/g),o=new e(s);if(!o.hasNext())throw Error("Empty file");const c=this;let l;function i(){let e=null,a=null;const t={};for(l=o.next();;){if(L(l,0,"ENDSEC")){e&&(t[e]=a);break}9===l.code?(e&&(t[e]=a),e=l.value):10===l.code?a={x:l.value}:20===l.code?a.y=l.value:30===l.code?a.z=l.value:a=l.value,l=o.next()}return l=o.next(),t}function u(){const e={};for(l=o.next();"EOF"!==l.value&&!L(l,0,"ENDSEC");)if(L(l,0,"BLOCK")){O().debug("block {");const a=d();O().debug("}"),h(a),a.name?e[a.name]=a:O().error('block with handle "'+a.handle+'" is missing a name.')}else S(l),l=o.next();return e}function d(){const e={};for(l=o.next();"EOF"!==l.value;){switch(l.code){case 1:e.xrefPath=l.value,l=o.next();break;case 2:e.name=l.value,l=o.next();break;case 3:e.name2=l.value,l=o.next();break;case 5:e.handle=l.value,l=o.next();break;case 8:e.layer=l.value,l=o.next();break;case 10:e.position=x(l),l=o.next();break;case 67:e.paperSpace=!(!l.value||1!=l.value),l=o.next();break;case 70:0!=l.value&&(e.type=l.value),l=o.next();break;case 100:l=o.next();break;case 330:e.ownerHandle=l.value,l=o.next();break;case 0:if("ENDBLK"==l.value)break;e.entities=k(!0);break;default:S(l),l=o.next()}if(L(l,0,"ENDBLK")){l=o.next();break}}return e}function v(){const e={};for(l=o.next();"EOF"!==l.value&&!L(l,0,"ENDSEC");)L(l,0,"TABLE")?(l=o.next(),f[l.value]?(O().debug(l.value+" Table {"),e[f[l.value].tableName]=p(l),O().debug("}")):O().debug("Unhandled Table "+l.value)):l=o.next();return l=o.next(),e}const b="ENDTAB";function p(e){const a=f[e.value],t={};let r=0;for(l=o.next();!L(l,0,b);)switch(l.code){case 5:t.handle=l.value,l=o.next();break;case 330:t.ownerHandle=l.value,l=o.next();break;case 100:"AcDbSymbolTable"===l.value||S(l),l=o.next();break;case 70:r=l.value,l=o.next();break;case 0:l.value===a.dxfSymbolName?t[a.tableRecordsProperty]=a.parseTableRecords():(S(l),l=o.next());break;default:S(l),l=o.next()}const n=t[a.tableRecordsProperty];if(n){let e=n.constructor===Array?n.length:"object"==typeof n?Object.keys(n).length:void 0;r!==e&&O().warn("Parsed "+e+" "+a.dxfSymbolName+"'s but expected "+r)}return l=o.next(),t}const f={VPORT:{tableRecordsProperty:"viewPorts",tableName:"viewPort",dxfSymbolName:"VPORT",parseTableRecords:function(){const e=[];let a={};for(O().debug("ViewPort {"),l=o.next();!L(l,0,b);)switch(l.code){case 2:a.name=l.value,l=o.next();break;case 10:a.lowerLeftCorner=x(l),l=o.next();break;case 11:a.upperRightCorner=x(l),l=o.next();break;case 12:a.center=x(l),l=o.next();break;case 13:a.snapBasePoint=x(l),l=o.next();break;case 14:a.snapSpacing=x(l),l=o.next();break;case 15:a.gridSpacing=x(l),l=o.next();break;case 16:a.viewDirectionFromTarget=x(l),l=o.next();break;case 17:a.viewTarget=x(l),l=o.next();break;case 42:a.lensLength=l.value,l=o.next();break;case 43:a.frontClippingPlane=l.value,l=o.next();break;case 44:a.backClippingPlane=l.value,l=o.next();break;case 45:a.viewHeight=l.value,l=o.next();break;case 50:a.snapRotationAngle=l.value,l=o.next();break;case 51:a.viewTwistAngle=l.value,l=o.next();break;case 79:a.orthographicType=l.value,l=o.next();break;case 110:a.ucsOrigin=x(l),l=o.next();break;case 111:a.ucsXAxis=x(l),l=o.next();break;case 112:a.ucsYAxis=x(l),l=o.next();break;case 110:a.ucsOrigin=x(l),l=o.next();break;case 281:a.renderMode=l.value,l=o.next();break;case 281:a.defaultLightingType=l.value,l=o.next();break;case 292:a.defaultLightingOn=l.value,l=o.next();break;case 330:a.ownerHandle=l.value,l=o.next();break;case 63:case 421:case 431:a.ambientColor=l.value,l=o.next();break;case 0:"VPORT"===l.value&&(O().debug("}"),e.push(a),O().debug("ViewPort {"),a={},l=o.next());break;default:S(l),l=o.next()}return O().debug("}"),e.push(a),e}},LTYPE:{tableRecordsProperty:"lineTypes",tableName:"lineType",dxfSymbolName:"LTYPE",parseTableRecords:function(){const e={};let a,t={},r=0;for(O().debug("LType {"),l=o.next();!L(l,0,"ENDTAB");)switch(l.code){case 2:t.name=l.value,a=l.value,l=o.next();break;case 3:t.description=l.value,l=o.next();break;case 73:r=l.value,r>0&&(t.pattern=[]),l=o.next();break;case 40:t.patternLength=l.value,l=o.next();break;case 49:t.pattern.push(l.value),l=o.next();break;case 0:O().debug("}"),r>0&&r!==t.pattern.length&&O().warn("lengths do not match on LTYPE pattern"),e[a]=t,t={},O().debug("LType {"),l=o.next();break;default:l=o.next()}return O().debug("}"),e[a]=t,e}},LAYER:{tableRecordsProperty:"layers",tableName:"layer",dxfSymbolName:"LAYER",parseTableRecords:function(){const e={};let a,t={};for(O().debug("Layer {"),l=o.next();!L(l,0,"ENDTAB");)switch(l.code){case 2:t.name=l.value,a=l.value,l=o.next();break;case 62:t.visible=l.value>=0,t.colorIndex=Math.abs(l.value),t.color=(r=t.colorIndex,n[r]),l=o.next();break;case 70:t.frozen=0!=(1&l.value)||0!=(2&l.value),l=o.next();break;case 0:"LAYER"===l.value&&(O().debug("}"),e[a]=t,O().debug("Layer {"),t={},a=void 0,l=o.next());break;default:S(l),l=o.next()}var r;return O().debug("}"),e[a]=t,e}}};function k(e){const a=[],t=e?"ENDBLK":"ENDSEC";for(e||(l=o.next());;)if(0===l.code){if(l.value===t)break;const e=c._entityHandlers[l.value];if(null==e){O().warn("Unhandled entity "+l.value),l=o.next();continue}{O().debug(l.value+" {");const t=e.parseEntity(o,l);l=o.lastReadGroup,O().debug("}"),h(t),a.push(t)}}else l=o.next();return"ENDSEC"==t&&(l=o.next()),a}function x(e){const a={};let t=e.code;if(a.x=e.value,t+=10,(e=o.next()).code!=t)throw new Error("Expected code for point value to be "+t+" but got "+e.code+".");return a.y=e.value,t+=10,(e=o.next()).code!=t?(o.rewind(),a):(a.z=e.value,a)}function h(e){if(!e)throw new TypeError("entity cannot be undefined or null");e.handle||(e.handle=r++)}return function(){for(l=o.next();!o.isEOF();)if(0===l.code&&"SECTION"===l.value){if(l=o.next(),2!==l.code){console.error("Unexpected code %s after 0:SECTION",D(l)),l=o.next();continue}"HEADER"===l.value?(O().debug("> HEADER"),t.header=i(),O().debug("<")):"BLOCKS"===l.value?(O().debug("> BLOCKS"),t.blocks=u(),O().debug("<")):"ENTITIES"===l.value?(O().debug("> ENTITIES"),t.entities=k(!1),O().debug("<")):"TABLES"===l.value?(O().debug("> TABLES"),t.tables=v(),O().debug("<")):"EOF"===l.value?O().debug("EOF"):O().warn("Skipping section '%s'",l.value)}else l=o.next()}(),t}}function L(e,a,t){return e.code===a&&e.value===t}function S(e){O().debug("unhandled group "+D(e))}function D(e){return e.code+":"+e.value}})(),r.DxfParser})()})); \ No newline at end of file diff --git a/earcut.min.js b/earcut.min.js new file mode 100644 index 0000000..de26f45 --- /dev/null +++ b/earcut.min.js @@ -0,0 +1 @@ +!function(t,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n((t="undefined"!=typeof globalThis?globalThis:t||self).earcut={})}(this,function(t){"use strict";const n=new Set;let e=!1;function r(t,n,e,r,x){let o=null;if(x===C(t,n,e,r)>0)for(let x=n;x=n;x-=r)o=L(x/r|0,t[x],t[x+1],o);return o&&P(o,o.next)&&(q(o),o=o.next),o}function x(t,r=t){const x=r===t;let o,i=t;do{o=!1,i===i.next||0!==n.size&&n.has(i)||!P(i,i.next)&&0!==O(i.prev,i,i.next)?(x||i!==r)&&(i=i.next,o=!x):((x||i===r)&&(r=i.prev),e=!0,q(i),i=i.prev,o=!0)}while(o||i!==r);return r}function o(t,n,r,o,c){c&&function(t,n,e,r){let x=t,o=0;do{x.z=j(x.x,x.y,n,e,r),Z[o++]=x,x=x.next}while(x!==t);!function(t){if(t<=32){for(let n=1;n=0&&Z[r].z>e;)Z[r+1]=Z[r],r--;Z[r+1]=t}return}A.length=c&&h.x<=s&&h.y>=y&&h.y<=a&&(x!==h.x||l!==h.y)&&F(x,l,o,f,i,u,h.x,h.y)&&O(h.prev,h,h.next)>=0)return!1;h=h.next}return!0}function l(t,n,e,r){const x=t.prev,o=t,i=t.next,l=x.x,f=o.x,u=i.x,c=x.y,y=o.y,s=i.y,a=Math.min(l,f,u),h=Math.min(c,y,s),p=Math.max(l,f,u),v=Math.max(c,y,s),M=j(a,h,n,e,r),d=j(p,v,n,e,r);let g=t.prevZ;for(;g&&g.z>=M;){if(g.x>=a&&g.x<=p&&g.y>=h&&g.y<=v&&g!==i&&(l!==g.x||c!==g.y)&&F(l,c,f,y,u,s,g.x,g.y)&&O(g.prev,g,g.next)>=0)return!1;g=g.prevZ}let m=t.nextZ;for(;m&&m.z<=d;){if(m.x>=a&&m.x<=p&&m.y>=h&&m.y<=v&&m!==i&&(l!==m.x||c!==m.y)&&F(l,c,f,y,u,s,m.x,m.y)&&O(m.prev,m,m.next)>=0)return!1;m=m.nextZ}return!0}function f(t,n){let e=t,r=!1;do{const x=e.prev,o=e.next.next;S(x,e,e.next,o,!1)&&_(x,o)&&_(o,x)&&(n.push(x.i,e.i,o.i),q(e),q(e.next),e=t=o,r=!0),e=e.next}while(e!==t);return r?x(e):e}function u(t,n,e,r,i){let l=t;do{let t=l.next.next;for(;t!==l.prev;){if(l.i!==t.i&&N(l,t)){let f=E(l,t);return l=x(l,l.next),f=x(f,f.next),o(l,n,e,r,i),void o(f,n,e,r,i)}t=t.next}l=l.next}while(l!==t)}let c=!1;function y(t,n){return t.x-n.x||t.y-n.y||(t.next.y-t.y)/(t.next.x-t.x)-(n.next.y-n.y)/(n.next.x-n.x)}function s(t,n){const e=function(t,n){let e=n;const r=t.x,x=t.y;let o,i=-1/0;if(P(t,e))return e;for(let n=0,l=0;nh[l+3]||h[l]>r||h[l+2]<=i)continue;const f=g(n);e=m(n);do{if(e.prev.next===e){if(P(t,e.next))return e.next;if(x<=e.y&&x>=e.next.y&&e.next.y!==e.y){const t=e.x+(x-e.y)*(e.next.x-e.x)/(e.next.y-e.y);if(t<=r&&t>i&&(i=t,o=e.xr||h[s+3]c)continue;const a=g(n);e=m(n);do{if(e.prev.next===e&&r>=e.x&&e.x>=l&&r!==e.x&&F(xr)&&(no.x||e.x===o.x&&w(o,e)))&&(o=e,y=n)}e=e.next}while(e!==a)}return o}(t,n);if(!e)return n;const r=E(e,t);return d(e,r.next.next),x(r,r.next),x(e,e.next)}const a=16;let h=new Float64Array(0),p=0;const v=[],M=[];function d(t,n){let e=t;do{const t=p++;v[t]=e;let r=e.x,x=e.y,o=e.x,i=e.y,l=0;do{const n=e.next;e.z=t,n.xo&&(o=n.x),n.yi&&(i=n.y),e=n}while(++l>>o&255]++;let i=0;for(let t=0;t<256;t++){const n=U[t];U[t]=i,i+=n}for(let i=0;i>>o&255]++;r[l]=n[i],x[l]=t}}function j(t,n,e,r,x){return(t=1431655765&((t=858993459&((t=252645135&((t=16711935&((t=(t-e)*x|0)|t<<8))|t<<4))|t<<2))|t<<1))|(n=1431655765&((n=858993459&((n=252645135&((n=16711935&((n=(n-r)*x|0)|n<<8))|n<<4))|n<<2))|n<<1))<<1}function k(t){let n=t,e=t;do{(n.x=(t-i)*(o-l)&&(t-i)*(r-l)>=(e-i)*(n-l)&&(e-i)*(o-l)>=(x-i)*(r-l)}function N(t,n){const e=P(t,n)&&O(t.prev,t,t.next)>0&&O(n.prev,n,n.next)>0;return t.next.i!==n.i&&(e||_(t,n)&&_(n,t)&&(0!==O(t.prev,t,n.prev)||0!==O(t,n.prev,n)))&&!function(t,n){const e=Math.min(t.x,n.x),r=Math.max(t.x,n.x),x=Math.min(t.y,n.y),o=Math.max(t.y,n.y);let i=t;do{const l=i.next;if(i.x>r&&l.x>r||i.xo&&l.y>o||i.yo!=t.y>o&&x<(t.x-e.x)*(o-e.y)/(t.y-e.y)+e.x&&(r=!r),e=t}while(e!==t);return r}(t,n))}function O(t,n,e){return(n.y-t.y)*(e.x-n.x)-(n.x-t.x)*(e.y-n.y)}function P(t,n){return t.x===n.x&&t.y===n.y}function S(t,n,e,r,x=!0){const o=O(t,n,e),i=O(t,n,r),l=O(e,r,t),f=O(e,r,n);return(o>0&&i<0||o<0&&i>0)&&(l>0&&f<0||l<0&&f>0)||!!x&&(!(0!==o||!T(t,e,n))||(!(0!==i||!T(t,r,n))||(!(0!==l||!T(e,t,r))||!(0!==f||!T(e,n,r)))))}function T(t,n,e){return n.x<=Math.max(t.x,e.x)&&n.x>=Math.min(t.x,e.x)&&n.y<=Math.max(t.y,e.y)&&n.y>=Math.min(t.y,e.y)}function _(t,n){return O(t.prev,t,t.next)<0?O(t,n,t.next)>=0&&O(t,t.prev,n)>=0:O(t,n,t.prev)<0||O(t,t.next,n)<0}function E(t,n){const e=B(t.i,t.x,t.y),r=B(n.i,n.x,n.y),x=t.next,o=n.prev;return t.next=n,n.prev=t,e.next=x,x.prev=e,r.next=e,e.prev=r,o.next=r,r.prev=o,r}function L(t,n,e,r){const x=B(t,n,e);return r?(x.next=r.next,x.prev=r,r.next.prev=x,r.next=x):(x.prev=x,x.next=x),x}function q(t){t.next.prev=t.prev,t.prev.next=t.next,t.prevZ&&(t.prevZ.nextZ=t.nextZ),t.nextZ&&(t.nextZ.prevZ=t.prevZ),c&&function(t,n){const e=4*t.z;n.xh[e+2]&&(h[e+2]=n.x),n.y>h[e+3]&&(h[e+3]=n.y)}(t.prev,t.next)}function B(t,n,e){return{i:t,x:n,y:e,prev:null,next:null,z:0,prevZ:null,nextZ:null}}function C(t,n,e,r){let x=0;for(let o=n,i=e-r;o80*i){M=t[0],g=t[1];let n=M,e=g;for(let r=i;rn&&(n=x),o>e&&(e=o)}m=Math.max(n-M,e-g),m=0!==m?32767/m:0}return o(u,v,M,g,m),v},t.deviation=function(t,n,e,r){const x=n&&n.length,o=x?n[0]*e:t.length;let i=Math.abs(C(t,0,o,e));if(x)for(let r=0,x=n.length;r0;){const t=D[--o];K[t]=0;const x=G[t];if(-1===x)continue;const i=t-t%3,l=x-x%3,f=i+(t+2)%3,u=i+(t+1)%3,c=l+(x+2)%3,y=l+(x+1)%3,s=r[f],a=r[t],h=r[u],p=r[c],v=n[s*e],M=n[s*e+1],d=n[a*e],g=n[a*e+1],m=n[h*e],w=n[h*e+1],Z=n[p*e],b=n[p*e+1];if(!W(v,M,d,g,m,w,Z,b)&&V(v,M,d,g,Z,b)>0&&V(v,M,Z,b,m,w)>0){r[t]=p,r[x]=s;const n=G[c],e=G[f];G[t]=n,-1!==n&&(G[n]=t),G[x]=e,-1!==e&&(G[e]=x),G[f]=c,G[c]=f,-1!==n&&0===K[t]&&(K[t]=1,D[o++]=t),-1!==e&&0===K[x]&&(K[x]=1,D[o++]=x),-1!==G[u]&&0===K[u]&&(K[u]=1,D[o++]=u),-1!==G[y]&&0===K[y]&&(K[y]=1,D[o++]=y)}}},Object.defineProperty(t,"__esModule",{value:!0})}); diff --git a/index.html b/index.html new file mode 100644 index 0000000..53e5c65 --- /dev/null +++ b/index.html @@ -0,0 +1,259 @@ + + + + + + + 터널 단면 모델러 — DWG/DXF → 3D 모델 + + + + + + + + +
+
T
+
+ 터널 단면 모델러 + DWG 표준단면에서 10m 3D 모델 생성 +
+
+ + 엔진 확인 중 +
+
+ +
+ + +
+
+ + +
+ +
+
+ 객체 투명도 + + 100% +
+ + +
+ + +
+ + + +
+
+ +
+ +
+ + + + + +
+
◈
+

단면 도면을 불러오세요

+

인식된 표준단면을 길이 10m로 생성해 3D로 확인합니다.

+
+ + +
+ +
+
+ 💡 + 전체 모델 +
+
+ ON + +
+
+ + +
+
+ + +
+
+ 💡 + + 숏크리트 +
+ ON +
+ + +
+
+ 💡 + + 라이닝·공동구 +
+ ON +
+ + +
+
+ 💡 + + 도로포장 +
+ ON +
+ + +
+
+ 💡 + + 공동구 뚜껑 +
+ ON +
+ + +
+
+ 💡 + + 맹암거 +
+ ON +
+ + +
+
+ 💡 + + 종배수관 +
+ ON +
+ + +
+
+ 💡 + + 차선 +
+ ON +
+ + +
+
+ 💡 + + 단면 테두리 +
+ ON +
+
+
+ + +
Developed by m12205.tdc.hanmacgroup@gmail.com · © 2026
+
+ +
+ 도면 대기 중 + Developed by m12205.tdc.hanmacgroup@gmail.com · © 2026 + 단위: 도면 mm / 3D 모델 m +
+
+
+ + diff --git a/rhino3dm.min.js b/rhino3dm.min.js new file mode 100644 index 0000000..eeae6c1 --- /dev/null +++ b/rhino3dm.min.js @@ -0,0 +1,8 @@ +var rhino3dm=(()=>{var _scriptName=globalThis.document?.currentScript?.src;return async function(moduleArg={}){var Module=moduleArg,moduleArg=!!globalThis.window,ENVIRONMENT_IS_WORKER=!!globalThis.WorkerGlobalScope,ENVIRONMENT_IS_NODE=globalThis.process?.versions?.node&&"renderer"!=globalThis.process?.type,thisProgram="./this.program",scriptDirectory=("undefined"!=typeof __filename?_scriptName=__filename:ENVIRONMENT_IS_WORKER&&(_scriptName=self.location.href),"");if(ENVIRONMENT_IS_NODE){var fs=require("node:fs"),scriptDirectory=__dirname+"/",readBinary=filename=>(filename=isFileURI(filename)?new URL(filename):filename,fs.readFileSync(filename)),readAsync=async(filename,binary=!0)=>(filename=isFileURI(filename)?new URL(filename):filename,fs.readFileSync(filename,binary?void 0:"utf8"));1{var xhr=new XMLHttpRequest;return xhr.open("GET",url,!1),xhr.responseType="arraybuffer",xhr.send(null),new Uint8Array(xhr.response)}),readAsync=async url=>{if(isFileURI(url))return new Promise((resolve,reject)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,!0),xhr.responseType="arraybuffer",xhr.onload=()=>{200==xhr.status||0==xhr.status&&xhr.response?resolve(xhr.response):reject(xhr.status)},xhr.onerror=reject,xhr.send(null)});var response=await fetch(url,{credentials:"same-origin"});if(response.ok)return response.arrayBuffer();throw new Error(response.status+" : "+response.url)}}var wasmBinary,readyPromiseResolve,readyPromiseReject,out=console.log.bind(console),err=console.error.bind(console),ABORT=!1,isFileURI=filename=>filename.startsWith("file://");var wasmBinaryFile,HEAP16,HEAP32,HEAP64,HEAP8,HEAPF32,HEAPF64,HEAPU16,HEAPU32,HEAPU64,HEAPU8,runtimeInitialized=!1;function updateMemoryViews(){var b=wasmMemory.buffer;HEAP8=new Int8Array(b),HEAP16=new Int16Array(b),HEAPU8=new Uint8Array(b),HEAPU16=new Uint16Array(b),HEAP32=new Int32Array(b),HEAPU32=new Uint32Array(b),HEAPF32=new Float32Array(b),HEAPF64=new Float64Array(b),HEAP64=new BigInt64Array(b),HEAPU64=new BigUint64Array(b)}function abort(what){Module.onAbort?.(what),err(what=`Aborted(${what})`),ABORT=!0,what+=". Build with -sASSERTIONS for more info.";what=new WebAssembly.RuntimeError(what);throw readyPromiseReject?.(what),what}function findWasmBinary(){return path="rhino3dm.wasm",Module.locateFile?Module.locateFile(path,scriptDirectory):scriptDirectory+path;var path}async function getWasmBinary(binaryFile){if(!wasmBinary)try{var response=await readAsync(binaryFile);return new Uint8Array(response)}catch{}response=binaryFile;if(response==wasmBinaryFile&&wasmBinary)return new Uint8Array(wasmBinary);if(readBinary)return readBinary(response);throw"both async and sync fetching of the wasm failed"}async function instantiateAsync(binary,binaryFile,imports){if(!binary&&!isFileURI(binaryFile)&&!ENVIRONMENT_IS_NODE)try{var response=fetch(binaryFile,{credentials:"same-origin"});return await WebAssembly.instantiateStreaming(response,imports)}catch(reason){err("wasm streaming compile failed: "+reason),err("falling back to ArrayBuffer instantiation")}return(async(binaryFile,imports)=>{try{var binary=await getWasmBinary(binaryFile);return await WebAssembly.instantiate(binary,imports)}catch(reason){err("failed to asynchronously prepare wasm: "+reason),abort(reason)}})(binaryFile,imports)}var callRuntimeCallbacks=callbacks=>{for(;0onPostRuns.push(cb),onPreRuns=[],addOnPreRun=cb=>onPreRuns.push(cb);class ExceptionInfo{constructor(excPtr){this.excPtr=excPtr,this.ptr=excPtr-24}set_type(type){HEAPU32[this.ptr+4>>2]=type}get_type(){return HEAPU32[this.ptr+4>>2]}set_destructor(destructor){HEAPU32[this.ptr+8>>2]=destructor}get_destructor(){return HEAPU32[this.ptr+8>>2]}set_caught(caught){HEAP8[this.ptr+12]=caught=caught?1:0}get_caught(){return 0!=HEAP8[this.ptr+12]}set_rethrown(rethrown){HEAP8[this.ptr+13]=rethrown=rethrown?1:0}get_rethrown(){return 0!=HEAP8[this.ptr+13]}init(type,destructor){this.set_adjusted_ptr(0),this.set_type(type),this.set_destructor(destructor)}set_adjusted_ptr(adjustedPtr){HEAPU32[this.ptr+16>>2]=adjustedPtr}get_adjusted_ptr(){return HEAPU32[this.ptr+16>>2]}}var initRandomFill=()=>{var nodeCrypto;return ENVIRONMENT_IS_NODE?(nodeCrypto=require("node:crypto"),view=>nodeCrypto.randomFillSync(view)):view=>(crypto.getRandomValues(view),0)},randomFill=view=>(randomFill=initRandomFill())(view),PATH={isAbs:path=>"/"===path.charAt(0),splitPath:filename=>/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/.exec(filename).slice(1),normalizeArray:(parts,allowAboveRoot)=>{for(var up=0,i=parts.length-1;0<=i;i--){var last=parts[i];"."===last?parts.splice(i,1):".."===last?(parts.splice(i,1),up++):up&&(parts.splice(i,1),up--)}if(allowAboveRoot)for(;up;up--)parts.unshift("..");return parts},normalize:path=>{var isAbsolute=PATH.isAbs(path),trailingSlash="/"===path.slice(-1);return(path=(path=PATH.normalizeArray(path.split("/").filter(p=>!!p),!isAbsolute).join("/"))||isAbsolute?path:".")&&trailingSlash&&(path+="/"),(isAbsolute?"/":"")+path},dirname:path=>{var path=PATH.splitPath(path),root=path[0],path=path[1];return root||path?root+(path=path&&path.slice(0,-1)):"."},basename:path=>path&&path.match(/([^\/]+|\/)\/*$/)[1],join:(...paths)=>PATH.normalize(paths.join("/")),join2:(l,r)=>PATH.normalize(l+"/"+r)},PATH_FS={resolve:(...args)=>{for(var resolvedPath="",resolvedAbsolute=!1,i=args.length-1;-1<=i&&!resolvedAbsolute;i--){var path=0<=i?args[i]:FS.cwd();if("string"!=typeof path)throw new TypeError("Arguments to path.resolve must be strings");if(!path)return"";resolvedPath=path+"/"+resolvedPath,resolvedAbsolute=PATH.isAbs(path)}return(resolvedAbsolute?"/":"")+(resolvedPath=PATH.normalizeArray(resolvedPath.split("/").filter(p=>!!p),!resolvedAbsolute).join("/"))||"."},relative:(from,to)=>{function trim(arr){for(var start=0;start{var maxIdx=idx+maxBytesToRead;if(ignoreNul)return maxIdx;for(;heapOrArray[idx]&&!(maxIdx<=idx);)++idx;return idx},UTF8ArrayToString=(heapOrArray,idx=0,maxBytesToRead,ignoreNul)=>{maxBytesToRead=findStringEnd(heapOrArray,idx,maxBytesToRead,ignoreNul);return UTF8Decoder.decode(heapOrArray.buffer?heapOrArray.subarray(idx,maxBytesToRead):new Uint8Array(heapOrArray.slice(idx,maxBytesToRead)))},FS_stdin_getChar_buffer=[],lengthBytesUTF8=str=>{for(var len=0,i=0;i{if(!(0>6,heap[outIdx++]=128|63&u}else if(u<=65535){if(endIdx<=outIdx+2)break;heap[outIdx++]=224|u>>12,heap[outIdx++]=128|u>>6&63,heap[outIdx++]=128|63&u}else{if(endIdx<=outIdx+3)break;heap[outIdx++]=240|u>>18,heap[outIdx++]=128|u>>12&63,heap[outIdx++]=128|u>>6&63,heap[outIdx++]=128|63&u,i++}}return heap[outIdx]=0,outIdx-startIdx},intArrayFromString=(stringy,dontAddNull,length)=>{length=0{if(!FS_stdin_getChar_buffer.length){var result=null;if(ENVIRONMENT_IS_NODE){var buf=Buffer.alloc(256),bytesRead=0,fd=process.stdin.fd;try{bytesRead=fs.readSync(fd,buf,0,256)}catch(e){if(!e.toString().includes("EOF"))throw e;bytesRead=0}0{abort()},MEMFS={ops_table:null,mount(mount){return MEMFS.createNode(null,"/",16895,0)},createNode(parent,name,mode,dev){if(FS.isBlkdev(mode)||FS.isFIFO(mode))throw new FS.ErrnoError(63);MEMFS.ops_table||={dir:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,lookup:MEMFS.node_ops.lookup,mknod:MEMFS.node_ops.mknod,rename:MEMFS.node_ops.rename,unlink:MEMFS.node_ops.unlink,rmdir:MEMFS.node_ops.rmdir,readdir:MEMFS.node_ops.readdir,symlink:MEMFS.node_ops.symlink},stream:{llseek:MEMFS.stream_ops.llseek}},file:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:{llseek:MEMFS.stream_ops.llseek,read:MEMFS.stream_ops.read,write:MEMFS.stream_ops.write,mmap:MEMFS.stream_ops.mmap,msync:MEMFS.stream_ops.msync}},link:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,readlink:MEMFS.node_ops.readlink},stream:{}},chrdev:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:FS.chrdev_stream_ops}};mode=FS.createNode(parent,name,mode,dev);return FS.isDir(mode.mode)?(mode.node_ops=MEMFS.ops_table.dir.node,mode.stream_ops=MEMFS.ops_table.dir.stream,mode.contents={}):FS.isFile(mode.mode)?(mode.node_ops=MEMFS.ops_table.file.node,mode.stream_ops=MEMFS.ops_table.file.stream,mode.usedBytes=0,mode.contents=MEMFS.emptyFileContents??=new Uint8Array(0)):FS.isLink(mode.mode)?(mode.node_ops=MEMFS.ops_table.link.node,mode.stream_ops=MEMFS.ops_table.link.stream):FS.isChrdev(mode.mode)&&(mode.node_ops=MEMFS.ops_table.chrdev.node,mode.stream_ops=MEMFS.ops_table.chrdev.stream),mode.atime=mode.mtime=mode.ctime=Date.now(),parent&&(parent.contents[name]=mode,parent.atime=parent.mtime=parent.ctime=mode.atime),mode},getFileDataAsTypedArray(node){return node.contents.subarray(0,node.usedBytes)},expandFileStorage(node,newCapacity){var prevCapacity=node.contents.length;newCapacity<=prevCapacity||(newCapacity=Math.max(newCapacity,prevCapacity*(prevCapacity<1048576?2:1.125)>>>0),prevCapacity&&(newCapacity=Math.max(newCapacity,256)),prevCapacity=MEMFS.getFileDataAsTypedArray(node),node.contents=new Uint8Array(newCapacity),node.contents.set(prevCapacity))},resizeFileStorage(node,newSize){var oldContents;node.usedBytes!=newSize&&(oldContents=node.contents,node.contents=new Uint8Array(newSize),node.contents.set(oldContents.subarray(0,Math.min(newSize,node.usedBytes))),node.usedBytes=newSize)},node_ops:{getattr(node){var attr={};return attr.dev=FS.isChrdev(node.mode)?node.id:1,attr.ino=node.id,attr.mode=node.mode,attr.nlink=1,attr.uid=0,attr.gid=0,attr.rdev=node.rdev,FS.isDir(node.mode)?attr.size=4096:FS.isFile(node.mode)?attr.size=node.usedBytes:FS.isLink(node.mode)?attr.size=node.link.length:attr.size=0,attr.atime=new Date(node.atime),attr.mtime=new Date(node.mtime),attr.ctime=new Date(node.ctime),attr.blksize=4096,attr.blocks=Math.ceil(attr.size/attr.blksize),attr},setattr(node,attr){for(var key of["mode","atime","mtime","ctime"])null!=attr[key]&&(node[key]=attr[key]);void 0!==attr.size&&MEMFS.resizeFileStorage(node,attr.size)},lookup(parent,name){throw MEMFS.doesNotExistError||(MEMFS.doesNotExistError=new FS.ErrnoError(44),MEMFS.doesNotExistError.stack=""),MEMFS.doesNotExistError},mknod(parent,name,mode,dev){return MEMFS.createNode(parent,name,mode,dev)},rename(old_node,new_dir,new_name){var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(new_node){if(FS.isDir(old_node.mode))for(var i in new_node.contents)throw new FS.ErrnoError(55);FS.hashRemoveNode(new_node)}delete old_node.parent.contents[old_node.name],(new_dir.contents[new_name]=old_node).name=new_name,new_dir.ctime=new_dir.mtime=old_node.parent.ctime=old_node.parent.mtime=Date.now()},unlink(parent,name){delete parent.contents[name],parent.ctime=parent.mtime=Date.now()},rmdir(parent,name){for(var i in FS.lookupNode(parent,name).contents)throw new FS.ErrnoError(55);delete parent.contents[name],parent.ctime=parent.mtime=Date.now()},readdir(node){return[".","..",...Object.keys(node.contents)]},symlink(parent,newname,oldpath){parent=MEMFS.createNode(parent,newname,41471,0);return parent.link=oldpath,parent},readlink(node){if(FS.isLink(node.mode))return node.link;throw new FS.ErrnoError(28)}},stream_ops:{read(stream,buffer,offset,length,position){var contents=stream.node.contents;return position>=stream.node.usedBytes?0:(stream=Math.min(stream.node.usedBytes-position,length),buffer.set(contents.subarray(position,position+stream),offset),stream)},write(stream,buffer,offset,length,position,canOwn){return buffer.buffer===HEAP8.buffer&&(canOwn=!1),length?((stream=stream.node).mtime=stream.ctime=Date.now(),canOwn?(stream.contents=buffer.subarray(offset,offset+length),stream.usedBytes=length):0===stream.usedBytes&&0===position?(stream.contents=buffer.slice(offset,offset+length),stream.usedBytes=length):(MEMFS.expandFileStorage(stream,position+length),stream.contents.set(buffer.subarray(offset,offset+length),position),stream.usedBytes=Math.max(stream.usedBytes,position+length)),length):0},llseek(stream,offset,whence){if(1===whence?offset+=stream.position:2===whence&&FS.isFile(stream.node.mode)&&(offset+=stream.node.usedBytes),offset<0)throw new FS.ErrnoError(28);return offset},mmap(stream,length,position,prot,flags){if(!FS.isFile(stream.node.mode))throw new FS.ErrnoError(43);var ptr,allocated,stream=stream.node.contents;if(2&flags||stream.buffer!==HEAP8.buffer){if(allocated=!0,!(ptr=mmapAlloc()))throw new FS.ErrnoError(48);stream&&((0data=(data="string"==typeof data?intArrayFromString(data,!0):data).subarray?data:new Uint8Array(data),FS_getMode=(canRead,canWrite)=>{var mode=0;return canRead&&(mode|=365),canWrite&&(mode|=146),mode},asyncLoad=async url=>{url=await readAsync(url);return new Uint8Array(url)},FS_createDataFile=(...args)=>FS.createDataFile(...args),getUniqueRunDependency=id=>id,runDependencies=0,dependenciesFulfilled=null,removeRunDependency=id=>{var callback;runDependencies--,Module.monitorRunDependencies?.(runDependencies),0==runDependencies&&dependenciesFulfilled&&(callback=dependenciesFulfilled,dependenciesFulfilled=null,callback())},addRunDependency=id=>{runDependencies++,Module.monitorRunDependencies?.(runDependencies)},preloadPlugins=[],FS_handledByPreloadPlugin=async(byteArray,fullname)=>{"undefined"!=typeof Browser&&Browser.init();for(var plugin of preloadPlugins)if(plugin.canHandle(fullname))return plugin.handle(byteArray,fullname);return byteArray},FS_preloadFile=async(parent,name,url,canRead,canWrite,dontCreateFile,canOwn,preFinish)=>{var fullname=name?PATH_FS.resolve(PATH.join2(parent,name)):parent,dep=getUniqueRunDependency("cp "+fullname);addRunDependency(dep);try{var byteArray=url;"string"==typeof url&&(byteArray=await asyncLoad(url)),byteArray=await FS_handledByPreloadPlugin(byteArray,fullname),preFinish?.(),dontCreateFile||FS_createDataFile(parent,name,byteArray,canRead,canWrite,canOwn)}finally{removeRunDependency(dep)}},FS={root:null,mounts:[],devices:{},streams:[],nextInode:1,nameTable:null,currentPath:"/",initialized:!1,ignorePermissions:!0,filesystems:null,syncFSRequests:0,ErrnoError:class{name="ErrnoError";constructor(errno){this.errno=errno}},FSStream:class{shared={};get object(){return this.node}set object(val){this.node=val}get isRead(){return 1!=(2097155&this.flags)}get isWrite(){return 0!=(2097155&this.flags)}get isAppend(){return 1024&this.flags}get flags(){return this.shared.flags}set flags(val){this.shared.flags=val}get position(){return this.shared.position}set position(val){this.shared.position=val}},FSNode:class{node_ops={};stream_ops={};readMode=365;writeMode=146;mounted=null;constructor(parent,name,mode,rdev){this.parent=parent=parent||this,this.mount=parent.mount,this.id=FS.nextInode++,this.name=name,this.mode=mode,this.rdev=rdev,this.atime=this.mtime=this.ctime=Date.now()}get read(){return(this.mode&this.readMode)===this.readMode}set read(val){val?this.mode|=this.readMode:this.mode&=~this.readMode}get write(){return(this.mode&this.writeMode)===this.writeMode}set write(val){val?this.mode|=this.writeMode:this.mode&=~this.writeMode}get isFolder(){return FS.isDir(this.mode)}get isDevice(){return FS.isChrdev(this.mode)}},lookupPath(path,opts={}){if(!path)throw new FS.ErrnoError(44);opts.follow_mount??=!0,PATH.isAbs(path)||(path=FS.cwd()+"/"+path);linkloop:for(var nlinks=0;nlinks<40;nlinks++){for(var parts=path.split("/").filter(p=>!!p),current=FS.root,current_path="/",i=0;i>>0)%FS.nameTable.length},hashAddNode(node){var hash=FS.hashName(node.parent.id,node.name);node.name_next=FS.nameTable[hash],FS.nameTable[hash]=node},hashRemoveNode(node){var hash=FS.hashName(node.parent.id,node.name);if(FS.nameTable[hash]===node)FS.nameTable[hash]=node.name_next;else for(var current=FS.nameTable[hash];current;){if(current.name_next===node){current.name_next=node.name_next;break}current=current.name_next}},lookupNode(parent,name){var errCode=FS.mayLookup(parent);if(errCode)throw new FS.ErrnoError(errCode);for(var errCode=FS.hashName(parent.id,name),node=FS.nameTable[errCode];node;node=node.name_next){var nodeName=node.name;if(node.parent.id===parent.id&&nodeName===name)return node}return FS.lookup(parent,name)},createNode(parent,name,mode,rdev){parent=new FS.FSNode(parent,name,mode,rdev);return FS.hashAddNode(parent),parent},destroyNode(node){FS.hashRemoveNode(node)},isRoot(node){return node===node.parent},isMountpoint(node){return!!node.mounted},isFile(mode){return 32768==(61440&mode)},isDir(mode){return 16384==(61440&mode)},isLink(mode){return 40960==(61440&mode)},isChrdev(mode){return 8192==(61440&mode)},isBlkdev(mode){return 24576==(61440&mode)},isFIFO(mode){return 4096==(61440&mode)},isSocket(mode){return 49152==(49152&mode)},flagsToPermissionString(flag){var perms=["r","w","rw"][3&flag];return 512&flag&&(perms+="w"),perms},nodePermissions(node,perms){return FS.ignorePermissions||(!perms.includes("r")||292&node.mode)&&(!perms.includes("w")||146&node.mode)&&(!perms.includes("x")||73&node.mode)?0:2},mayLookup(dir){return FS.isDir(dir.mode)?FS.nodePermissions(dir,"x")||(dir.node_ops.lookup?0:2):54},mayCreate(dir,name){if(!FS.isDir(dir.mode))return 54;try{FS.lookupNode(dir,name);return 20}catch(e){}return FS.nodePermissions(dir,"wx")},mayDelete(dir,name,isdir){var node;try{node=FS.lookupNode(dir,name)}catch(e){return e.errno}name=FS.nodePermissions(dir,"wx");if(name)return name;if(isdir){if(!FS.isDir(node.mode))return 54;if(FS.isRoot(node)||FS.getPath(node)===FS.cwd())return 10}else if(FS.isDir(node.mode))return 31;return 0},mayOpen(node,flags){var mode;return node?FS.isLink(node.mode)?32:(mode=FS.flagsToPermissionString(flags),FS.isDir(node.mode)&&("r"!==mode||576&flags)?31:FS.nodePermissions(node,mode)):44},checkOpExists(op,err){if(op)return op;throw new FS.ErrnoError(err)},MAX_OPEN_FDS:4096,nextfd(){for(var fd=0;fd<=FS.MAX_OPEN_FDS;fd++)if(!FS.streams[fd])return fd;throw new FS.ErrnoError(33)},getStreamChecked(fd){fd=FS.getStream(fd);if(fd)return fd;throw new FS.ErrnoError(8)},getStream:fd=>FS.streams[fd],createStream(stream,fd=-1){return stream=Object.assign(new FS.FSStream,stream),-1==fd&&(fd=FS.nextfd()),stream.fd=fd,FS.streams[fd]=stream},closeStream(fd){FS.streams[fd]=null},dupStream(origStream,fd=-1){origStream=FS.createStream(origStream,fd);return origStream.stream_ops?.dup?.(origStream),origStream},doSetAttr(stream,node,attr){var setattr=stream?.stream_ops.setattr,stream=setattr?stream:node;setattr??=node.node_ops.setattr,FS.checkOpExists(setattr,63);try{setattr(stream,attr)}catch(e){if(e instanceof RangeError)throw new FS.ErrnoError(22);throw e}},chrdev_stream_ops:{open(stream){var device=FS.getDevice(stream.node.rdev);stream.stream_ops=device.stream_ops,stream.stream_ops.open?.(stream)},llseek(){throw new FS.ErrnoError(70)}},major:dev=>dev>>8,minor:dev=>255&dev,makedev:(ma,mi)=>ma<<8|mi,registerDevice(dev,ops){FS.devices[dev]={stream_ops:ops}},getDevice:dev=>FS.devices[dev],getMounts(mount){for(var mounts=[],check=[mount];check.length;){var m=check.pop();mounts.push(m),check.push(...m.mounts)}return mounts},syncfs(populate,callback){"function"==typeof populate&&(callback=populate,populate=!1),FS.syncFSRequests++,1=mounts.length&&doCallback(null)}for(mount of mounts)mount.type.syncfs?mount.type.syncfs(mount,populate,done):done(null)},mount(type,opts,mountpoint){var node,root="/"===mountpoint,pseudo=!mountpoint;if(root&&FS.root)throw new FS.ErrnoError(10);if(!root&&!pseudo){pseudo=FS.lookupPath(mountpoint,{follow_mount:!1});if(mountpoint=pseudo.path,FS.isMountpoint(node=pseudo.node))throw new FS.ErrnoError(10);if(!FS.isDir(node.mode))throw new FS.ErrnoError(54)}pseudo={type:type,opts:opts,mountpoint:mountpoint,mounts:[]},opts=type.mount(pseudo);return(opts.mount=pseudo).root=opts,root?FS.root=opts:node&&(node.mounted=pseudo,node.mount)&&node.mount.mounts.push(pseudo),opts},unmount(mountpoint){mountpoint=FS.lookupPath(mountpoint,{follow_mount:!1});if(!FS.isMountpoint(mountpoint.node))throw new FS.ErrnoError(28);var hash,current,mountpoint=mountpoint.node,mount=mountpoint.mounted,mounts=FS.getMounts(mount);for([hash,current]of Object.entries(FS.nameTable))for(;current;){var next=current.name_next;mounts.includes(current.mount)&&FS.destroyNode(current),current=next}mountpoint.mounted=null;mount=mountpoint.mount.mounts.indexOf(mount);mountpoint.mount.mounts.splice(mount,1)},lookup(parent,name){return parent.node_ops.lookup(parent,name)},mknod(path,mode,dev){var parent=FS.lookupPath(path,{parent:!0}).node,path=PATH.basename(path);if(!path)throw new FS.ErrnoError(28);if("."===path||".."===path)throw new FS.ErrnoError(20);var errCode=FS.mayCreate(parent,path);if(errCode)throw new FS.ErrnoError(errCode);if(parent.node_ops.mknod)return parent.node_ops.mknod(parent,path,mode,dev);throw new FS.ErrnoError(63)},statfs(path){return FS.statfsNode(FS.lookupPath(path,{follow:!0}).node)},statfsStream(stream){return FS.statfsNode(stream.node)},statfsNode(node){var rtn={bsize:4096,frsize:4096,blocks:1e6,bfree:5e5,bavail:5e5,files:FS.nextInode,ffree:FS.nextInode-1,fsid:42,flags:2,namelen:255};return node.node_ops.statfs&&Object.assign(rtn,node.node_ops.statfs(node.mount.opts.root)),rtn},create(path,mode=438){return FS.mknod(path,mode=mode&4095|32768,0)},mkdir(path,mode=511){return FS.mknod(path,mode=mode&1023|16384,0)},mkdirTree(path,mode){var dir,d="";for(dir of path.split("/"))if(dir){(d||PATH.isAbs(path))&&(d+="/"),d+=dir;try{FS.mkdir(d,mode)}catch(e){if(20!=e.errno)throw e}}},mkdev(path,mode,dev){return void 0===dev&&(dev=mode,mode=438),FS.mknod(path,mode|=8192,dev)},symlink(oldpath,newpath){if(!PATH_FS.resolve(oldpath))throw new FS.ErrnoError(44);var parent=FS.lookupPath(newpath,{parent:!0}).node;if(!parent)throw new FS.ErrnoError(44);var newpath=PATH.basename(newpath),errCode=FS.mayCreate(parent,newpath);if(errCode)throw new FS.ErrnoError(errCode);if(parent.node_ops.symlink)return parent.node_ops.symlink(parent,newpath,oldpath);throw new FS.ErrnoError(63)},rename(old_path,new_path){var old_dirname=PATH.dirname(old_path),new_dirname=PATH.dirname(new_path),old_name=PATH.basename(old_path),new_name=PATH.basename(new_path),lookup=FS.lookupPath(old_path,{parent:!0}),lookup=lookup.node,new_dir=FS.lookupPath(new_path,{parent:!0}).node;if(!lookup||!new_dir)throw new FS.ErrnoError(44);if(lookup.mount!==new_dir.mount)throw new FS.ErrnoError(75);var new_node,old_node=FS.lookupNode(lookup,old_name),old_path=PATH_FS.relative(old_path,new_dirname);if("."!==old_path.charAt(0))throw new FS.ErrnoError(28);if("."!==(old_path=PATH_FS.relative(new_path,old_dirname)).charAt(0))throw new FS.ErrnoError(55);try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(old_node!==new_node){new_dirname=FS.isDir(old_node.mode),new_path=FS.mayDelete(lookup,old_name,new_dirname);if(new_path)throw new FS.ErrnoError(new_path);if(new_path=new_node?FS.mayDelete(new_dir,new_name,new_dirname):FS.mayCreate(new_dir,new_name))throw new FS.ErrnoError(new_path);if(!lookup.node_ops.rename)throw new FS.ErrnoError(63);if(FS.isMountpoint(old_node)||new_node&&FS.isMountpoint(new_node))throw new FS.ErrnoError(10);if(new_dir!==lookup&&(new_path=FS.nodePermissions(lookup,"w")))throw new FS.ErrnoError(new_path);FS.hashRemoveNode(old_node);try{lookup.node_ops.rename(old_node,new_dir,new_name),old_node.parent=new_dir}catch(e){throw e}finally{FS.hashAddNode(old_node)}}},rmdir(path){var parent=FS.lookupPath(path,{parent:!0}).node,path=PATH.basename(path),node=FS.lookupNode(parent,path),errCode=FS.mayDelete(parent,path,!0);if(errCode)throw new FS.ErrnoError(errCode);if(!parent.node_ops.rmdir)throw new FS.ErrnoError(63);if(FS.isMountpoint(node))throw new FS.ErrnoError(10);parent.node_ops.rmdir(parent,path),FS.destroyNode(node)},readdir(path){path=FS.lookupPath(path,{follow:!0}).node;return FS.checkOpExists(path.node_ops.readdir,54)(path)},unlink(path){var parent=FS.lookupPath(path,{parent:!0}).node;if(!parent)throw new FS.ErrnoError(44);var path=PATH.basename(path),node=FS.lookupNode(parent,path),errCode=FS.mayDelete(parent,path,!1);if(errCode)throw new FS.ErrnoError(errCode);if(!parent.node_ops.unlink)throw new FS.ErrnoError(63);if(FS.isMountpoint(node))throw new FS.ErrnoError(10);parent.node_ops.unlink(parent,path),FS.destroyNode(node)},readlink(path){path=FS.lookupPath(path).node;if(!path)throw new FS.ErrnoError(44);if(path.node_ops.readlink)return path.node_ops.readlink(path);throw new FS.ErrnoError(28)},stat(path,dontFollow){path=FS.lookupPath(path,{follow:!dontFollow}).node;return FS.checkOpExists(path.node_ops.getattr,63)(path)},fstat(fd){var fd=FS.getStreamChecked(fd),node=fd.node,getattr=fd.stream_ops.getattr,fd=getattr?fd:node;return getattr??=node.node_ops.getattr,FS.checkOpExists(getattr,63),getattr(fd)},lstat(path){return FS.stat(path,!0)},doChmod(stream,node,mode,dontFollow){FS.doSetAttr(stream,node,{mode:4095&mode|-4096&node.mode,ctime:Date.now(),dontFollow:dontFollow})},chmod(path,mode,dontFollow){path="string"==typeof path?FS.lookupPath(path,{follow:!dontFollow}).node:path,FS.doChmod(null,path,mode,dontFollow)},lchmod(path,mode){FS.chmod(path,mode,!0)},fchmod(fd,mode){fd=FS.getStreamChecked(fd);FS.doChmod(fd,fd.node,mode,!1)},doChown(stream,node,dontFollow){FS.doSetAttr(stream,node,{timestamp:Date.now(),dontFollow:dontFollow})},chown(path,uid,gid,dontFollow){path="string"==typeof path?FS.lookupPath(path,{follow:!dontFollow}).node:path,FS.doChown(null,path,dontFollow)},lchown(path,uid,gid){FS.chown(path,uid,gid,!0)},fchown(fd,uid,gid){fd=FS.getStreamChecked(fd);FS.doChown(fd,fd.node,!1)},doTruncate(stream,node,len){if(FS.isDir(node.mode))throw new FS.ErrnoError(31);if(!FS.isFile(node.mode))throw new FS.ErrnoError(28);var errCode=FS.nodePermissions(node,"w");if(errCode)throw new FS.ErrnoError(errCode);FS.doSetAttr(stream,node,{size:len,timestamp:Date.now()})},truncate(path,len){if(len<0)throw new FS.ErrnoError(28);path="string"==typeof path?FS.lookupPath(path,{follow:!0}).node:path,FS.doTruncate(null,path,len)},ftruncate(fd,len){fd=FS.getStreamChecked(fd);if(len<0||0==(2097155&fd.flags))throw new FS.ErrnoError(28);FS.doTruncate(fd,fd.node,len)},utime(path,atime,mtime){path=FS.lookupPath(path,{follow:!0}).node;FS.checkOpExists(path.node_ops.setattr,63)(path,{atime:atime,mtime:mtime})},open(path,flags,mode=438){if(""===path)throw new FS.ErrnoError(44);mode=64&(flags=(str=>{if("string"!=typeof str)return str;var flags={r:0,"r+":2,w:577,"w+":578,a:1089,"a+":1090}[str];if(void 0===flags)throw new Error("Unknown file open mode: "+str);return flags})(flags))?4095&mode|32768:0,"object"==typeof path?node=path:(isDirPath=path.endsWith("/"),node=(lookup=FS.lookupPath(path,{follow:!(131072&flags),noent_okay:!0})).node,path=lookup.path);var node,lookup=!1;if(64&flags)if(node){if(128&flags)throw new FS.ErrnoError(20)}else{if(isDirPath)throw new FS.ErrnoError(31);node=FS.mknod(path,511|mode,0),lookup=!0}if(!node)throw new FS.ErrnoError(44);if(FS.isChrdev(node.mode)&&(flags&=-513),65536&flags&&!FS.isDir(node.mode))throw new FS.ErrnoError(54);if(!lookup){var isDirPath=FS.mayOpen(node,flags);if(isDirPath)throw new FS.ErrnoError(isDirPath)}512&flags&&!lookup&&FS.truncate(node,0),flags&=-131713;path=FS.createStream({node:node,path:FS.getPath(node),flags:flags,seekable:!0,position:0,stream_ops:node.stream_ops,ungotten:[],error:!1});return path.stream_ops.open&&path.stream_ops.open(path),lookup&&FS.chmod(node,511&mode),path},close(stream){if(FS.isClosed(stream))throw new FS.ErrnoError(8);stream.getdents&&(stream.getdents=null);try{stream.stream_ops.close&&stream.stream_ops.close(stream)}catch(e){throw e}finally{FS.closeStream(stream.fd)}stream.fd=null},isClosed(stream){return null===stream.fd},llseek(stream,offset,whence){if(FS.isClosed(stream))throw new FS.ErrnoError(8);if(!stream.seekable||!stream.stream_ops.llseek)throw new FS.ErrnoError(70);if(0!=whence&&1!=whence&&2!=whence)throw new FS.ErrnoError(28);return stream.position=stream.stream_ops.llseek(stream,offset,whence),stream.ungotten=[],stream.position},read(stream,buffer,offset,length,position){if(length<0||position<0)throw new FS.ErrnoError(28);if(FS.isClosed(stream))throw new FS.ErrnoError(8);if(1==(2097155&stream.flags))throw new FS.ErrnoError(8);if(FS.isDir(stream.node.mode))throw new FS.ErrnoError(31);if(!stream.stream_ops.read)throw new FS.ErrnoError(28);var seeking=void 0!==position;if(seeking){if(!stream.seekable)throw new FS.ErrnoError(70)}else position=stream.position;buffer=stream.stream_ops.read(stream,buffer,offset,length,position);return seeking||(stream.position+=buffer),buffer},write(stream,buffer,offset,length,position,canOwn){if(length<0||position<0)throw new FS.ErrnoError(28);if(FS.isClosed(stream))throw new FS.ErrnoError(8);if(0==(2097155&stream.flags))throw new FS.ErrnoError(8);if(FS.isDir(stream.node.mode))throw new FS.ErrnoError(31);if(!stream.stream_ops.write)throw new FS.ErrnoError(28);stream.seekable&&1024&stream.flags&&FS.llseek(stream,0,2);var seeking=void 0!==position;if(seeking){if(!stream.seekable)throw new FS.ErrnoError(70)}else position=stream.position;buffer=stream.stream_ops.write(stream,buffer,offset,length,position,canOwn);return seeking||(stream.position+=buffer),buffer},mmap(stream,length,position,prot,flags){if(0!=(2&prot)&&0==(2&flags)&&2!=(2097155&stream.flags))throw new FS.ErrnoError(2);if(1==(2097155&stream.flags))throw new FS.ErrnoError(2);if(!stream.stream_ops.mmap)throw new FS.ErrnoError(43);if(length)return stream.stream_ops.mmap(stream,length,position,prot,flags);throw new FS.ErrnoError(28)},msync(stream,buffer,offset,length,mmapFlags){return stream.stream_ops.msync?stream.stream_ops.msync(stream,buffer,offset,length,mmapFlags):0},ioctl(stream,cmd,arg){if(stream.stream_ops.ioctl)return stream.stream_ops.ioctl(stream,cmd,arg);throw new FS.ErrnoError(59)},readFile(path,opts={}){opts.flags=opts.flags||0,opts.encoding=opts.encoding||"binary","utf8"!==opts.encoding&&"binary"!==opts.encoding&&abort(`Invalid encoding type "${opts.encoding}"`);var stream=FS.open(path,opts.flags),path=FS.stat(path).size,buf=new Uint8Array(path);return FS.read(stream,buf,0,path,0),"utf8"===opts.encoding&&(buf=UTF8ArrayToString(buf)),FS.close(stream),buf},writeFile(path,data,opts={}){opts.flags=opts.flags||577;path=FS.open(path,opts.flags,opts.mode);data=FS_fileDataToTypedArray(data),FS.write(path,data,0,data.byteLength,void 0,opts.canOwn),FS.close(path)},cwd:()=>FS.currentPath,chdir(path){path=FS.lookupPath(path,{follow:!0});if(null===path.node)throw new FS.ErrnoError(44);if(!FS.isDir(path.node.mode))throw new FS.ErrnoError(54);var errCode=FS.nodePermissions(path.node,"x");if(errCode)throw new FS.ErrnoError(errCode);FS.currentPath=path.path},createDefaultDirectories(){FS.mkdir("/tmp"),FS.mkdir("/home"),FS.mkdir("/home/web_user")},createDefaultDevices(){FS.mkdir("/dev"),FS.registerDevice(FS.makedev(1,3),{read:()=>0,write:(stream,buffer,offset,length,pos)=>length,llseek:()=>0}),FS.mkdev("/dev/null",FS.makedev(1,3)),TTY.register(FS.makedev(5,0),TTY.default_tty_ops),TTY.register(FS.makedev(6,0),TTY.default_tty1_ops),FS.mkdev("/dev/tty",FS.makedev(5,0)),FS.mkdev("/dev/tty1",FS.makedev(6,0));var randomBuffer=new Uint8Array(1024),randomLeft=0,randomByte=()=>(0===randomLeft&&(randomFill(randomBuffer),randomLeft=randomBuffer.byteLength),randomBuffer[--randomLeft]);FS.createDevice("/dev","random",randomByte),FS.createDevice("/dev","urandom",randomByte),FS.mkdir("/dev/shm"),FS.mkdir("/dev/shm/tmp")},createSpecialDirectories(){FS.mkdir("/proc");var proc_self=FS.mkdir("/proc/self");FS.mkdir("/proc/self/fd"),FS.mount({mount(){var node=FS.createNode(proc_self,"fd",16895,73);return node.stream_ops={llseek:MEMFS.stream_ops.llseek},node.node_ops={lookup(parent,name){var name=+name,stream=FS.getStreamChecked(name),name={parent:null,mount:{mountpoint:"fake"},node_ops:{readlink:()=>stream.path},id:1+name};return name.parent=name},readdir(){return Array.from(FS.streams.entries()).filter(([,v])=>v).map(([k])=>k.toString())}},node}},{},"/proc/self/fd")},createStandardStreams(input,output,error){input?FS.createDevice("/dev","stdin",input):FS.symlink("/dev/tty","/dev/stdin"),output?FS.createDevice("/dev","stdout",null,output):FS.symlink("/dev/tty","/dev/stdout"),error?FS.createDevice("/dev","stderr",null,error):FS.symlink("/dev/tty1","/dev/stderr");FS.open("/dev/stdin",0),FS.open("/dev/stdout",1),FS.open("/dev/stderr",1)},staticInit(){FS.nameTable=new Array(4096),FS.mount(MEMFS,{},"/"),FS.createDefaultDirectories(),FS.createDefaultDevices(),FS.createSpecialDirectories(),FS.filesystems={MEMFS:MEMFS}},init(input,output,error){FS.initialized=!0,input??=Module.stdin,output??=Module.stdout,error??=Module.stderr,FS.createStandardStreams(input,output,error)},quit(){FS.initialized=!1;for(var stream of FS.streams)stream&&FS.close(stream)},findObject(path,dontResolveLastLink){path=FS.analyzePath(path,dontResolveLastLink);return path.exists?path.object:null},analyzePath(path,dontResolveLastLink){try{path=(lookup=FS.lookupPath(path,{follow:!dontResolveLastLink})).path}catch(e){}var ret={isRoot:!1,exists:!1,error:0,name:null,path:null,object:null,parentExists:!1,parentPath:null,parentObject:null};try{var lookup=FS.lookupPath(path,{parent:!0});ret.parentExists=!0,ret.parentPath=lookup.path,ret.parentObject=lookup.node,ret.name=PATH.basename(path),lookup=FS.lookupPath(path,{follow:!dontResolveLastLink}),ret.exists=!0,ret.path=lookup.path,ret.object=lookup.node,ret.name=lookup.node.name,ret.isRoot="/"===lookup.path}catch(e){ret.error=e.errno}return ret},createPath(parent,path,canRead,canWrite){parent="string"==typeof parent?parent:FS.getPath(parent);for(var parts=path.split("/").reverse();parts.length;){var part=parts.pop();if(part){var current=PATH.join2(parent,part);try{FS.mkdir(current)}catch(e){if(20!=e.errno)throw e}parent=current}}return current},createFile(parent,name,properties,canRead,canWrite){parent=PATH.join2("string"==typeof parent?parent:FS.getPath(parent),name),name=FS_getMode(canRead,canWrite);return FS.create(parent,name)},createDataFile(parent,name,data,canRead,canWrite,canOwn){var path=name,name=(parent&&(parent="string"==typeof parent?parent:FS.getPath(parent),path=name?PATH.join2(parent,name):parent),FS_getMode(canRead,canWrite)),parent=FS.create(path,name);data&&(data=FS_fileDataToTypedArray(data),FS.chmod(parent,146|name),canRead=FS.open(parent,577),FS.write(canRead,data,0,data.length,0,canOwn),FS.close(canRead),FS.chmod(parent,name))},createDevice(parent,name,input,output){var parent=PATH.join2("string"==typeof parent?parent:FS.getPath(parent),name),name=FS_getMode(!!input,!!output),dev=(FS.createDevice.major??=64,FS.makedev(FS.createDevice.major++,0));return FS.registerDevice(dev,{open(stream){stream.seekable=!1},close(stream){output?.buffer?.length&&output(10)},read(stream,buffer,offset,length,pos){for(var result,bytesRead=0,i=0;ithis.length-1||idx<0))return chunkOffset=idx%this.chunkSize,idx=idx/this.chunkSize|0,this.getter(idx)[chunkOffset]}setDataGetter(getter){this.getter=getter}cacheLength(){var header,xhr=new XMLHttpRequest,datalength=(xhr.open("HEAD",url,!1),xhr.send(null),200<=xhr.status&&xhr.status<300||304===xhr.status||abort("Couldn't load "+url+". Status: "+xhr.status),Number(xhr.getResponseHeader("Content-length"))),hasByteServing=(header=xhr.getResponseHeader("Accept-Ranges"))&&"bytes"===header,xhr=(header=xhr.getResponseHeader("Content-Encoding"))&&"gzip"===header,chunkSize=1048576,lazyArray=(hasByteServing||(chunkSize=datalength),this);lazyArray.setDataGetter(chunkNum=>{var xhr,start=chunkNum*chunkSize,end=(chunkNum+1)*chunkSize-1,end=Math.min(end,datalength-1);return void 0===lazyArray.chunks[chunkNum]&&(lazyArray.chunks[chunkNum]=((end=end)<(start=start)&&abort("invalid range ("+start+", "+end+") or no bytes requested!"),datalength-1(FS.forceLoadFile(node),fn(...args));function writeChunks(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=contents.length)return 0;var size=Math.min(contents.length-position,length);if(contents.slice)for(var i=0;i(FS.forceLoadFile(node),writeChunks(stream,buffer,offset,length,position)),stream_ops.mmap=(stream,length,position,prot,flags)=>{FS.forceLoadFile(node);mmapAlloc();throw new FS.ErrnoError(48)},node.stream_ops=stream_ops,node}},SOCKFS={websocketArgs:{},callbacks:{},on(event,callback){SOCKFS.callbacks[event]=callback},emit(event,param){SOCKFS.callbacks[event]?.(param)},mount(mount){return SOCKFS.websocketArgs=Module.websocket||{},(Module.websocket??={}).on=SOCKFS.on,FS.createNode(null,"/",16895,0)},createSocket(family,type,protocol){if(2!=family)throw new FS.ErrnoError(5);if(1!=(type&=-526337)&&2!=type)throw new FS.ErrnoError(28);if(1==type&&protocol&&6!=protocol)throw new FS.ErrnoError(66);family={family:family,type:type,protocol:protocol,server:null,error:null,peers:{},pending:[],recv_queue:[],sock_ops:SOCKFS.websocket_sock_ops},type=SOCKFS.nextname(),protocol=FS.createNode(SOCKFS.root,type,49152,0),protocol.sock=family,type=FS.createStream({path:type,node:protocol,flags:2,seekable:!1,stream_ops:SOCKFS.stream_ops});return family.stream=type,family},getSocket(fd){fd=FS.getStream(fd);return fd&&FS.isSocket(fd.node.mode)?fd.node.sock:null},stream_ops:{poll(stream){stream=stream.node.sock;return stream.sock_ops.poll(stream)},ioctl(stream,request,varargs){stream=stream.node.sock;return stream.sock_ops.ioctl(stream,request,varargs)},read(stream,buffer,offset,length,position){stream=stream.node.sock,stream=stream.sock_ops.recvmsg(stream,length);return stream?(buffer.set(stream.buffer,offset),stream.buffer.length):0},write(stream,buffer,offset,length,position){stream=stream.node.sock;return stream.sock_ops.sendmsg(stream,buffer,offset,length)},close(stream){stream=stream.node.sock;stream.sock_ops.close(stream)}},nextname(){return SOCKFS.nextname.current||(SOCKFS.nextname.current=0),`socket[${SOCKFS.nextname.current++}]`},websocket_sock_ops:{createPeer(sock,addr,port){var ws;if("object"==typeof addr&&(ws=addr,port=addr=null),ws)if(ws._socket)addr=ws._socket.remoteAddress,port=ws._socket.remotePort;else{var result=/ws[s]?:\/\/([^:]+):(\d+)/.exec(ws.url);if(!result)throw new Error("WebSocket URL must be in the format ws(s)://address:port");addr=result[1],port=parseInt(result[2],10)}else try{var parts,url="ws://".replace("#","//"),subProtocols="binary",opts=void 0;SOCKFS.websocketArgs.url&&(url=SOCKFS.websocketArgs.url),SOCKFS.websocketArgs.subprotocol?subProtocols=SOCKFS.websocketArgs.subprotocol:null===SOCKFS.websocketArgs.subprotocol&&(subProtocols="null"),"ws://"!==url&&"wss://"!==url||(url=url+(parts=addr.split("/"))[0]+":"+port+"/"+parts.slice(1).join("/")),"null"!==subProtocols&&(opts=subProtocols=subProtocols.replace(/^ +| +$/g,"").split(/ *, */)),(ws=new(ENVIRONMENT_IS_NODE?require("ws"):WebSocket)(url,opts)).binaryType="arraybuffer"}catch(e){throw new FS.ErrnoError(23)}result={addr:addr,port:port,socket:ws,msg_send_queue:[]};return SOCKFS.websocket_sock_ops.addPeer(sock,result),SOCKFS.websocket_sock_ops.handlePeerEvents(sock,result),2===sock.type&&void 0!==sock.sport&&result.msg_send_queue.push(new Uint8Array([255,255,255,255,"p".charCodeAt(0),"o".charCodeAt(0),"r".charCodeAt(0),"t".charCodeAt(0),(65280&sock.sport)>>8,255&sock.sport])),result},getPeer(sock,addr,port){return sock.peers[addr+":"+port]},addPeer(sock,peer){sock.peers[peer.addr+":"+peer.port]=peer},removePeer(sock,peer){delete sock.peers[peer.addr+":"+peer.port]},handlePeerEvents(sock,peer){var first=!0;function handleOpen(){sock.connecting=!1,SOCKFS.emit("open",sock.stream.fd);try{for(var queued=peer.msg_send_queue.shift();queued;)peer.socket.send(queued),queued=peer.msg_send_queue.shift()}catch(e){peer.socket.close()}}function handleMessage(data){if("string"==typeof data)data=(new TextEncoder).encode(data);else{if(0==data.byteLength)return;data=new Uint8Array(data)}var wasfirst=first;first=!1,wasfirst&&10===data.length&&255===data[0]&&255===data[1]&&255===data[2]&&255===data[3]&&data[4]==="p".charCodeAt(0)&&data[5]==="o".charCodeAt(0)&&data[6]==="r".charCodeAt(0)&&data[7]==="t".charCodeAt(0)?(wasfirst=data[8]<<8|data[9],SOCKFS.websocket_sock_ops.removePeer(sock,peer),peer.port=wasfirst,SOCKFS.websocket_sock_ops.addPeer(sock,peer)):(sock.recv_queue.push({addr:peer.addr,port:peer.port,data:data}),SOCKFS.emit("message",sock.stream.fd))}ENVIRONMENT_IS_NODE?(peer.socket.on("open",handleOpen),peer.socket.on("message",(data,isBinary)=>{isBinary&&handleMessage(new Uint8Array(data).buffer)}),peer.socket.on("close",()=>SOCKFS.emit("close",sock.stream.fd)),peer.socket.on("error",error=>{sock.error=14,SOCKFS.emit("error",[sock.stream.fd,sock.error,"ECONNREFUSED: Connection refused"])})):(peer.socket.onopen=handleOpen,peer.socket.onclose=()=>SOCKFS.emit("close",sock.stream.fd),peer.socket.onmessage=event=>handleMessage(event.data),peer.socket.onerror=error=>{sock.error=14,SOCKFS.emit("error",[sock.stream.fd,sock.error,"ECONNREFUSED: Connection refused"])})},poll(sock){var mask,dest;return 1===sock.type&&sock.server?sock.pending.length?65:0:(mask=0,dest=1===sock.type?SOCKFS.websocket_sock_ops.getPeer(sock,sock.daddr,sock.dport):null,(sock.recv_queue.length||!dest||dest&&dest.socket.readyState===dest.socket.CLOSING||dest&&dest.socket.readyState===dest.socket.CLOSED)&&(mask|=65),dest&&dest.socket.readyState!==dest.socket.OPEN||(mask|=4),(dest&&dest.socket.readyState===dest.socket.CLOSING||dest&&dest.socket.readyState===dest.socket.CLOSED)&&(sock.connecting?mask|=4:mask|=16),mask)},ioctl(sock,request,arg){switch(request){case 21531:var bytes=0;return sock.recv_queue.length&&(bytes=sock.recv_queue[0].data.length),HEAP32[arg>>2]=bytes,0;case 21537:return HEAP32[arg>>2]?sock.stream.flags|=2048:sock.stream.flags&=-2049,0;default:return 28}},close(sock){if(sock.server){try{sock.server.close()}catch(e){}sock.server=null}for(var peer of Object.values(sock.peers)){try{peer.socket.close()}catch(e){}SOCKFS.websocket_sock_ops.removePeer(sock,peer)}return 0},bind(sock,addr,port){if(void 0!==sock.saddr||void 0!==sock.sport)throw new FS.ErrnoError(28);if(sock.saddr=addr,sock.sport=port,2===sock.type){sock.server&&(sock.server.close(),sock.server=null);try{sock.sock_ops.listen(sock,0)}catch(e){if("ErrnoError"!==e.name)throw e;if(138!==e.errno)throw e}}},connect(sock,addr,port){if(sock.server)throw new FS.ErrnoError(138);if(void 0!==sock.daddr&&void 0!==sock.dport){var dest=SOCKFS.websocket_sock_ops.getPeer(sock,sock.daddr,sock.dport);if(dest)throw dest.socket.readyState===dest.socket.CONNECTING?new FS.ErrnoError(7):new FS.ErrnoError(30)}dest=SOCKFS.websocket_sock_ops.createPeer(sock,addr,port);sock.daddr=dest.addr,sock.dport=dest.port,sock.connecting=!0},listen(sock,backlog){if(!ENVIRONMENT_IS_NODE)throw new FS.ErrnoError(138);if(sock.server)throw new FS.ErrnoError(28);var WebSocketServer=require("ws").Server,host=sock.saddr;sock.server=new WebSocketServer({host:host,port:sock.sport}),SOCKFS.emit("listen",sock.stream.fd),sock.server.on("connection",ws=>{var newsock,peer;1===sock.type?(newsock=SOCKFS.createSocket(sock.family,sock.type,sock.protocol),peer=SOCKFS.websocket_sock_ops.createPeer(newsock,ws),newsock.daddr=peer.addr,newsock.dport=peer.port,sock.pending.push(newsock),SOCKFS.emit("connection",newsock.stream.fd)):(SOCKFS.websocket_sock_ops.createPeer(sock,ws),SOCKFS.emit("connection",sock.stream.fd))}),sock.server.on("close",()=>{SOCKFS.emit("close",sock.stream.fd),sock.server=null}),sock.server.on("error",error=>{sock.error=23,SOCKFS.emit("error",[sock.stream.fd,sock.error,"EHOSTUNREACH: Host is unreachable"])})},accept(listensock){var newsock;if(listensock.server&&listensock.pending.length)return(newsock=listensock.pending.shift()).stream.flags=listensock.stream.flags,newsock;throw new FS.ErrnoError(28)},getname(sock,peer){var addr,port;if(peer){if(void 0===sock.daddr||void 0===sock.dport)throw new FS.ErrnoError(53);addr=sock.daddr,port=sock.dport}else addr=sock.saddr||0,port=sock.sport||0;return{addr:addr,port:port}},sendmsg(sock,buffer,offset,length,addr,port){if(2===sock.type){if(void 0!==addr&&void 0!==port||(addr=sock.daddr,port=sock.dport),void 0===addr||void 0===port)throw new FS.ErrnoError(17)}else addr=sock.daddr,port=sock.dport;var dest=SOCKFS.websocket_sock_ops.getPeer(sock,addr,port);if(1===sock.type&&(!dest||dest.socket.readyState===dest.socket.CLOSING||dest.socket.readyState===dest.socket.CLOSED))throw new FS.ErrnoError(53);ArrayBuffer.isView(buffer)&&(offset+=buffer.byteOffset,buffer=buffer.buffer);buffer=buffer.slice(offset,offset+length);if(!dest||dest.socket.readyState!==dest.socket.OPEN)return(dest=2!==sock.type||dest&&dest.socket.readyState!==dest.socket.CLOSING&&dest.socket.readyState!==dest.socket.CLOSED?dest:SOCKFS.websocket_sock_ops.createPeer(sock,addr,port)).msg_send_queue.push(buffer),length;try{return dest.socket.send(buffer),length}catch(e){throw new FS.ErrnoError(28)}},recvmsg(sock,length){if(1===sock.type&&sock.server)throw new FS.ErrnoError(53);var queuedLength,queuedOffset,res,queued=sock.recv_queue.shift();if(queued)return queuedLength=queued.data.byteLength||queued.data.length,queuedOffset=queued.data.byteOffset||0,queuedBuffer=queued.data.buffer||queued.data,length=Math.min(length,queuedLength),res={buffer:new Uint8Array(queuedBuffer,queuedOffset,length),addr:queued.addr,port:queued.port},1===sock.type&&length(255&addr)+"."+(addr>>8&255)+"."+(addr>>16&255)+"."+(addr>>24&255),inetNtop6=ints=>{for(var str="",word=0,longest=0,lastzero=0,zstart=0,len=0,i=0,parts=[65535&ints[0],ints[0]>>16,65535&ints[1],ints[1]>>16,65535&ints[2],ints[2]>>16,65535&ints[3],ints[3]>>16],hasipv4=!0,ints="",i=0;i<5;i++)if(0!==parts[i]){hasipv4=!1;break}if(hasipv4){if(ints=inetNtop4(parts[6]|parts[7]<<16),-1===parts[5])return str="::ffff:",str+=ints;if(0===parts[5])return str="::",str+=ints="0.0.0.1"===(ints="0.0.0.0"===ints?"":ints)?"1":ints}for(word=0;word<8;word++)0===parts[word]&&(1{var addr,family=HEAP16[sa>>1],port=_ntohs(HEAPU16[sa+2>>1]);switch(family){case 2:if(16!==salen)return{errno:28};addr=HEAP32[sa+4>>2],addr=inetNtop4(addr);break;case 10:if(28!==salen)return{errno:28};addr=[HEAP32[sa+8>>2],HEAP32[sa+12>>2],HEAP32[sa+16>>2],HEAP32[sa+20>>2]],addr=inetNtop6(addr);break;default:return{errno:5}}return{family:family,addr:addr,port:port}},DNS={address_map:{id:1,addrs:{},names:{}},lookup_name(name){var id,addr;return null!==(str=>{for(var b=str.split("."),i=0;i<4;i++){var tmp=Number(b[i]);if(isNaN(tmp))return null;b[i]=tmp}return(b[0]|b[1]<<8|b[2]<<16|b[3]<<24)>>>0})(name)||null!==(str=>{var words,w,offset,z,parts=[];if(!/^((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\3)::|:\b|$))|(?!\2\3)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4})$/i.test(str))return null;if("::"===str)return[0,0,0,0,0,0,0,0];for(words=0<(str=str.startsWith("::")?str.replace("::","Z:"):str.replace("::",":Z:")).indexOf(".")?((words=(str=str.replace(new RegExp("[.]","g"),":")).split(":"))[words.length-4]=Number(words[words.length-4])+256*Number(words[words.length-3]),words[words.length-3]=Number(words[words.length-2])+256*Number(words[words.length-1]),words.slice(0,words.length-2)):str.split(":"),w=z=offset=0;wptr?(maxBytesToRead=findStringEnd(HEAPU8,ptr,maxBytesToRead,ignoreNul),UTF8Decoder.decode(HEAPU8.subarray(ptr,maxBytesToRead))):"",SYSCALLS={currentUmask:18,calculateAt(dirfd,path,allowEmpty){if(PATH.isAbs(path))return path;if(dirfd=-100===dirfd?FS.cwd():SYSCALLS.getStreamFromFD(dirfd).path,0!=path.length)return dirfd+"/"+path;if(allowEmpty)return dirfd;throw new FS.ErrnoError(44)},writeStat(buf,stat){HEAPU32[buf>>2]=stat.dev,HEAPU32[buf+4>>2]=stat.mode,HEAPU32[buf+8>>2]=stat.nlink,HEAPU32[buf+12>>2]=stat.uid,HEAPU32[buf+16>>2]=stat.gid,HEAPU32[buf+20>>2]=stat.rdev,HEAP64[buf+24>>3]=BigInt(stat.size),HEAP32[buf+32>>2]=4096,HEAP32[buf+36>>2]=stat.blocks;var atime=stat.atime.getTime(),mtime=stat.mtime.getTime(),ctime=stat.ctime.getTime();return HEAP64[buf+40>>3]=BigInt(Math.floor(atime/1e3)),HEAPU32[buf+48>>2]=atime%1e3*1e3*1e3,HEAP64[buf+56>>3]=BigInt(Math.floor(mtime/1e3)),HEAPU32[buf+64>>2]=mtime%1e3*1e3*1e3,HEAP64[buf+72>>3]=BigInt(Math.floor(ctime/1e3)),HEAPU32[buf+80>>2]=ctime%1e3*1e3*1e3,HEAP64[buf+88>>3]=BigInt(stat.ino),0},writeStatFs(buf,stats){HEAPU32[buf+4>>2]=stats.bsize,HEAPU32[buf+60>>2]=stats.bsize,HEAP64[buf+8>>3]=BigInt(stats.blocks),HEAP64[buf+16>>3]=BigInt(stats.bfree),HEAP64[buf+24>>3]=BigInt(stats.bavail),HEAP64[buf+32>>3]=BigInt(stats.files),HEAP64[buf+40>>3]=BigInt(stats.ffree),HEAPU32[buf+48>>2]=stats.fsid,HEAPU32[buf+64>>2]=stats.flags,HEAPU32[buf+56>>2]=stats.namelen},doMsync(addr,stream,len,flags,offset){if(!FS.isFile(stream.node.mode))throw new FS.ErrnoError(43);if(2&flags)return 0;addr=HEAPU8.slice(addr,addr+len);FS.msync(stream,addr,offset,len,flags)},getStreamFromFD(fd){return FS.getStreamChecked(fd)},varargs:void 0,getStr(ptr){return UTF8ToString(ptr)}};var syscallGetVarargI=()=>{var ret=HEAP32[+SYSCALLS.varargs>>2];return SYSCALLS.varargs+=4,ret},syscallGetVarargP=syscallGetVarargI;var INT53_MAX=9007199254740992,INT53_MIN=-9007199254740992,bigintToI53Checked=num=>num{for(;destructors.length;){var ptr=destructors.pop();destructors.pop()(ptr)}};function readPointer(pointer){return this.fromWireType(HEAPU32[pointer>>2])}var awaitingDependencies={},registeredTypes={},typeDependencies={},InternalError=class extends Error{constructor(message){super(message),this.name="InternalError"}},throwInternalError=message=>{throw new InternalError(message)},whenDependentTypesAreResolved=(myTypes,dependentTypes,getTypeConverters)=>{function onComplete(typeConverters){var myTypeConverters=getTypeConverters(typeConverters);myTypeConverters.length!==myTypes.length&&throwInternalError("Mismatched type converter count");for(var i=0;itypeDependencies[type]=dependentTypes);var typeConverters=new Array(dependentTypes.length),unregisteredTypes=[],registered=0;for(let[i,dt]of dependentTypes.entries())registeredTypes.hasOwnProperty(dt)?typeConverters[i]=registeredTypes[dt]:(unregisteredTypes.push(dt),awaitingDependencies.hasOwnProperty(dt)||(awaitingDependencies[dt]=[]),awaitingDependencies[dt].push(()=>{typeConverters[i]=registeredTypes[dt],++registered===unregisteredTypes.length&&onComplete(typeConverters)}));0===unregisteredTypes.length&&onComplete(typeConverters)},structRegistrations={},AsciiToString=ptr=>{for(var str="";;){var ch=HEAPU8[ptr++];if(!ch)return str;str+=String.fromCharCode(ch)}},BindingError=class extends Error{constructor(message){super(message),this.name="BindingError"}},throwBindingError=message=>{throw new BindingError(message)};function registerType(rawType,registeredInstance,options={}){return((rawType,registeredInstance,options={})=>{var name=registeredInstance.name;if(rawType||throwBindingError(`type "${name}" must have a positive integer typeid pointer`),registeredTypes.hasOwnProperty(rawType)){if(options.ignoreDuplicateRegistrations)return;throwBindingError(`Cannot register type '${name}' twice`)}registeredTypes[rawType]=registeredInstance,delete typeDependencies[rawType],awaitingDependencies.hasOwnProperty(rawType)&&(options=awaitingDependencies[rawType],delete awaitingDependencies[rawType],options.forEach(cb=>cb()))})(rawType,registeredInstance,options)}var integerReadValueFromPointer=(name,width,signed)=>{switch(width){case 1:return signed?pointer=>HEAP8[pointer]:pointer=>HEAPU8[pointer];case 2:return signed?pointer=>HEAP16[pointer>>1]:pointer=>HEAPU16[pointer>>1];case 4:return signed?pointer=>HEAP32[pointer>>2]:pointer=>HEAPU32[pointer>>2];case 8:return signed?pointer=>HEAP64[pointer>>3]:pointer=>HEAPU64[pointer>>3];default:throw new TypeError(`invalid integer width (${width}): `+name)}},throwInstanceAlreadyDeleted=obj=>{throwBindingError(obj.$$.ptrType.registeredClass.name+" instance already deleted")},finalizationRegistry=!1,detachFinalizer=handle=>{},runDestructor=$$=>{$$.smartPtr?$$.smartPtrType.rawDestructor($$.smartPtr):$$.ptrType.registeredClass.rawDestructor($$.ptr)},releaseClassHandle=$$=>{--$$.count.value,0===$$.count.value&&runDestructor($$)},attachFinalizer=handle=>globalThis.FinalizationRegistry?(finalizationRegistry=new FinalizationRegistry(info=>{releaseClassHandle(info.$$)}),detachFinalizer=handle=>finalizationRegistry.unregister(handle),(attachFinalizer=handle=>{var $$=handle.$$;return!!$$.smartPtr&&finalizationRegistry.register(handle,{$$:$$},handle),handle})(handle)):(attachFinalizer=handle=>handle,handle),deletionQueue=[];function ClassHandle(){}var createNamedFunction=(name,func)=>Object.defineProperty(func,"name",{value:name}),registeredPointers={},ensureOverloadTable=(proto,methodName,humanName)=>{var prevFunc;void 0===proto[methodName].overloadTable&&(prevFunc=proto[methodName],proto[methodName]=function(...args){return proto[methodName].overloadTable.hasOwnProperty(args.length)||throwBindingError(`Function '${humanName}' called with an invalid number of arguments (${args.length}) - expects one of (${proto[methodName].overloadTable})!`),proto[methodName].overloadTable[args.length].apply(this,args)},proto[methodName].overloadTable=[],proto[methodName].overloadTable[prevFunc.argCount]=prevFunc)},exposePublicSymbol=(name,value,numArguments)=>{Module.hasOwnProperty(name)?((void 0===numArguments||void 0!==Module[name].overloadTable&&void 0!==Module[name].overloadTable[numArguments])&&throwBindingError(`Cannot register public name '${name}' twice`),ensureOverloadTable(Module,name,name),Module[name].overloadTable.hasOwnProperty(numArguments)&&throwBindingError(`Cannot register multiple overloads of a function with the same number of arguments (${numArguments})!`),Module[name].overloadTable[numArguments]=value):(Module[name]=value,Module[name].argCount=numArguments)},char_0=48,char_9=57;function RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast){this.name=name,this.constructor=constructor,this.instancePrototype=instancePrototype,this.rawDestructor=rawDestructor,this.baseClass=baseClass,this.getActualType=getActualType,this.upcast=upcast,this.downcast=downcast,this.pureVirtualFunctions=[]}var upcastPointer=(ptr,ptrClass,desiredClass)=>{for(;ptrClass!==desiredClass;)ptrClass.upcast||throwBindingError(`Expected null or instance of ${desiredClass.name}, got an instance of `+ptrClass.name),ptr=ptrClass.upcast(ptr),ptrClass=ptrClass.baseClass;return ptr},embindRepr=v=>{var t;return null===v?"null":"object"==(t=typeof v)||"array"==t||"function"==t?v.toString():""+v};function constNoSmartPtrRawPointerToWireType(destructors,handle){if(null===handle)return this.isReference&&throwBindingError("null is not a valid "+this.name),0;handle.$$||throwBindingError(`Cannot pass "${embindRepr(handle)}" as a `+this.name),handle.$$.ptr||throwBindingError("Cannot pass deleted object as a pointer of type "+this.name);var handleClass=handle.$$.ptrType.registeredClass;return upcastPointer(handle.$$.ptr,handleClass,this.registeredClass)}function genericPointerToWireType(destructors,handle){if(null===handle)return this.isReference&&throwBindingError("null is not a valid "+this.name),this.isSmartPointer?(ptr=this.rawConstructor(),null!==destructors&&destructors.push(this.rawDestructor,ptr),ptr):0;handle&&handle.$$||throwBindingError(`Cannot pass "${embindRepr(handle)}" as a `+this.name),handle.$$.ptr||throwBindingError("Cannot pass deleted object as a pointer of type "+this.name),!this.isConst&&handle.$$.ptrType.isConst&&throwBindingError(`Cannot convert argument of type ${(handle.$$.smartPtrType||handle.$$.ptrType).name} to parameter type `+this.name);var clonedHandle,ptr,handleClass=handle.$$.ptrType.registeredClass;if(ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass),this.isSmartPointer)switch(void 0===handle.$$.smartPtr&&throwBindingError("Passing raw pointer to smart pointer is illegal"),this.sharingPolicy){case 0:handle.$$.smartPtrType===this?ptr=handle.$$.smartPtr:throwBindingError(`Cannot convert argument of type ${(handle.$$.smartPtrType||handle.$$.ptrType).name} to parameter type `+this.name);break;case 1:ptr=handle.$$.smartPtr;break;case 2:handle.$$.smartPtrType===this?ptr=handle.$$.smartPtr:(clonedHandle=handle.clone(),ptr=this.rawShare(ptr,Emval.toHandle(()=>clonedHandle.delete())),null!==destructors&&destructors.push(this.rawDestructor,ptr));break;default:throwBindingError("Unsupported sharing policy")}return ptr}function nonConstNoSmartPtrRawPointerToWireType(destructors,handle){if(null===handle)return this.isReference&&throwBindingError("null is not a valid "+this.name),0;handle.$$||throwBindingError(`Cannot pass "${embindRepr(handle)}" as a `+this.name),handle.$$.ptr||throwBindingError("Cannot pass deleted object as a pointer of type "+this.name),handle.$$.ptrType.isConst&&throwBindingError(`Cannot convert argument of type ${handle.$$.ptrType.name} to parameter type `+this.name);var handleClass=handle.$$.ptrType.registeredClass;return upcastPointer(handle.$$.ptr,handleClass,this.registeredClass)}var downcastPointer=(ptr,ptrClass,desiredClass)=>ptrClass===desiredClass?ptr:void 0===desiredClass.baseClass||null===(ptr=downcastPointer(ptr,ptrClass,desiredClass.baseClass))?null:desiredClass.downcast(ptr),registeredInstances={},getBasestPointer=(class_,ptr)=>{for(void 0===ptr&&throwBindingError("ptr should not be undefined");class_.baseClass;)ptr=class_.upcast(ptr),class_=class_.baseClass;return ptr},getInheritedInstance=(class_,ptr)=>(ptr=getBasestPointer(class_,ptr),registeredInstances[ptr]),makeClassHandle=(prototype,record)=>(record.ptrType&&record.ptr||throwInternalError("makeClassHandle requires ptr and ptrType"),!!record.smartPtrType!=!!record.smartPtr&&throwInternalError("Both smartPtrType and smartPtr must be specified"),record.count={value:1},attachFinalizer(Object.create(prototype,{$$:{value:record,writable:!0}})));function RegisteredPointer_fromWireType(ptr){var registeredInstance,dp,rawPointer=this.getPointee(ptr);return rawPointer?void 0!==(registeredInstance=getInheritedInstance(this.registeredClass,rawPointer))?0===registeredInstance.$$.count.value?(registeredInstance.$$.ptr=rawPointer,registeredInstance.$$.smartPtr=ptr,registeredInstance.clone()):(registeredInstance=registeredInstance.clone(),this.destructor(ptr),registeredInstance):(registeredInstance=this.registeredClass.getActualType(rawPointer),!(registeredInstance=registeredPointers[registeredInstance])||(registeredInstance=this.isConst?registeredInstance.constPointerType:registeredInstance.pointerType,null===(dp=downcastPointer(rawPointer,this.registeredClass,registeredInstance.registeredClass)))?makeDefaultHandle.call(this):this.isSmartPointer?makeClassHandle(registeredInstance.registeredClass.instancePrototype,{ptrType:registeredInstance,ptr:dp,smartPtrType:this,smartPtr:ptr}):makeClassHandle(registeredInstance.registeredClass.instancePrototype,{ptrType:registeredInstance,ptr:dp})):(this.destructor(ptr),null);function makeDefaultHandle(){return this.isSmartPointer?makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this.pointeeType,ptr:rawPointer,smartPtrType:this,smartPtr:ptr}):makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this,ptr:ptr})}}function RegisteredPointer(name,registeredClass,isReference,isConst,isSmartPointer,pointeeType,sharingPolicy,rawGetPointee,rawConstructor,rawShare,rawDestructor){this.name=name,this.registeredClass=registeredClass,this.isReference=isReference,this.isConst=isConst,this.isSmartPointer=isSmartPointer,this.pointeeType=pointeeType,this.sharingPolicy=sharingPolicy,this.rawGetPointee=rawGetPointee,this.rawConstructor=rawConstructor,this.rawShare=rawShare,this.rawDestructor=rawDestructor,isSmartPointer||void 0!==registeredClass.baseClass?this.toWireType=genericPointerToWireType:(this.toWireType=isConst?constNoSmartPtrRawPointerToWireType:nonConstNoSmartPtrRawPointerToWireType,this.destructorFunction=null)}var getWasmTableEntry=funcPtr=>wasmTable.get(funcPtr),embind__requireFunction=(signature,rawFunction,isAsync=0)=>{signature=AsciiToString(signature);var fp=getWasmTableEntry(rawFunction);return"function"!=typeof fp&&throwBindingError(`unknown function pointer with signature ${signature}: `+rawFunction),fp};class UnboundTypeError extends Error{}var getTypeName=type=>{var type=___getTypeName(type),rv=AsciiToString(type);return _free(type),rv},throwUnboundTypeError=(message,types)=>{var unboundTypes=[],seen={};throw types.forEach(function visit(type){seen[type]||registeredTypes[type]||(typeDependencies[type]?typeDependencies[type].forEach(visit):(unboundTypes.push(type),seen[type]=!0))}),new UnboundTypeError(message+": "+unboundTypes.map(getTypeName).join([", "]))};function usesDestructorStack(argTypes){for(var i=1;i{var needsDestructorStack=usesDestructorStack(argTypes),argCount=argTypes.length-2,argsList=[],argsListWired=["fn"];isClassMethodFunc&&argsListWired.push("thisWired");for(var i=0;i{for(var array=[],i=0;i>2]);return array},getFunctionName=signature=>{var argsIndex=(signature=signature.trim()).indexOf("(");return-1===argsIndex?signature:signature.slice(0,argsIndex)},validateThis=(this_,classType,humanName)=>(this_ instanceof Object||throwBindingError(humanName+' with invalid "this": '+this_),this_ instanceof classType.registeredClass.constructor||throwBindingError(humanName+' incompatible with "this" of type '+this_.constructor.name),this_.$$.ptr||throwBindingError(`cannot call emscripten binding method ${humanName} on deleted object`),upcastPointer(this_.$$.ptr,this_.$$.ptrType.registeredClass,classType.registeredClass)),emval_freelist=[],emval_handles=[0,1,,1,null,1,!0,1,!1,1],__emval_decref=handle=>{9(handle||throwBindingError("Cannot use deleted val. handle = "+handle),emval_handles[handle]),toHandle:value=>{switch(value){case void 0:return 2;case null:return 4;case!0:return 6;case!1:return 8;default:var handle=emval_freelist.pop()||emval_handles.length;return emval_handles[handle]=value,emval_handles[handle+1]=1,handle}}},EmValType={name:"emscripten::val",fromWireType:handle=>{var rv=Emval.toValue(handle);return __emval_decref(handle),rv},toWireType:(destructors,value)=>Emval.toHandle(value),readValueFromPointer:readPointer,destructorFunction:null},enumReadValueFromPointer=(name,width,signed)=>{switch(width){case 1:return signed?function(pointer){return this.fromWireType(HEAP8[pointer])}:function(pointer){return this.fromWireType(HEAPU8[pointer])};case 2:return signed?function(pointer){return this.fromWireType(HEAP16[pointer>>1])}:function(pointer){return this.fromWireType(HEAPU16[pointer>>1])};case 4:return signed?function(pointer){return this.fromWireType(HEAP32[pointer>>2])}:function(pointer){return this.fromWireType(HEAPU32[pointer>>2])};default:throw new TypeError(`invalid integer width (${width}): `+name)}};var requireRegisteredType=(rawType,humanName)=>{var impl=registeredTypes[rawType];return void 0===impl&&throwBindingError(humanName+" has unknown type "+getTypeName(rawType)),impl},installIndexedIterator=(proto,sizeMethodName,getMethodName)=>{proto[Symbol.iterator]||(proto[Symbol.iterator]=function(){return((size,getValue)=>{let index=0;return{next(){var current;return index>=size?{done:!0}:(current=index,index++,{value:getValue(current),done:!1})},[Symbol.iterator](){return this}}})(this[sizeMethodName](),i=>this[getMethodName](i))})},EmValOptionalType=Object.assign({optional:!0},EmValType),stringToUTF8=(str,outPtr,maxBytesToWrite)=>stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite),UTF16Decoder=new TextDecoder("utf-16le"),UTF16ToString=(ptr,maxBytesToRead,ignoreNul)=>{ptr>>=1,maxBytesToRead=findStringEnd(HEAPU16,ptr,maxBytesToRead/2,ignoreNul);return UTF16Decoder.decode(HEAPU16.subarray(ptr,maxBytesToRead))},stringToUTF16=(str,outPtr,maxBytesToWrite)=>{if((maxBytesToWrite??=2147483647)<2)return 0;for(var startPtr=outPtr,numCharsToWrite=(maxBytesToWrite-=2)<2*str.length?maxBytesToWrite/2:str.length,i=0;i>1]=codeUnit,outPtr+=2}return HEAP16[outPtr>>1]=0,outPtr-startPtr},lengthBytesUTF16=str=>2*str.length,UTF32ToString=(ptr,maxBytesToRead,ignoreNul)=>{for(var str="",startIdx=ptr>>2,i=0;!(maxBytesToRead/4<=i);i++){var utf32=HEAPU32[startIdx+i];if(!utf32&&!ignoreNul)break;str+=String.fromCodePoint(utf32)}return str},stringToUTF32=(str,outPtr,maxBytesToWrite)=>{if((maxBytesToWrite??=2147483647)<4)return 0;for(var startPtr=outPtr,endPtr=startPtr+maxBytesToWrite-4,i=0;i>2]=codePoint,endPtr<(outPtr+=4)+4)break}return HEAP32[outPtr>>2]=0,outPtr-startPtr},lengthBytesUTF32=str=>{for(var len=0,i=0;i{var destructors=[],toReturnWire=toReturnWire(destructors,handle);return destructors.length&&(HEAPU32[destructorsRef>>2]=Emval.toHandle(destructors)),toReturnWire},emval_symbols={},getStringOrSymbol=address=>{var symbol=emval_symbols[address];return void 0===symbol?AsciiToString(address):symbol};var isLeapYear=year=>year%4==0&&(year%100!=0||year%400==0),MONTH_DAYS_LEAP_CUMULATIVE=[0,31,60,91,121,152,182,213,244,274,305,335],MONTH_DAYS_REGULAR_CUMULATIVE=[0,31,59,90,120,151,181,212,243,273,304,334],ydayFromDate=date=>(isLeapYear(date.getFullYear())?MONTH_DAYS_LEAP_CUMULATIVE:MONTH_DAYS_REGULAR_CUMULATIVE)[date.getMonth()]+date.getDate()-1;var ENV={},getExecutableName=()=>thisProgram||"./this.program",getEnvStrings=()=>{if(!getEnvStrings.strings){var env={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:(globalThis.navigator?.language??"C").replace("-","_")+".UTF-8",_:getExecutableName()};for(x in ENV)void 0===ENV[x]?delete env[x]:env[x]=ENV[x];var x,strings=[];for(x in env)strings.push(x+"="+env[x]);getEnvStrings.strings=strings}return getEnvStrings.strings};var symbolDispose,___getTypeName,_malloc,_free,_htons,_ntohs,wasmMemory,wasmTable,wasmExports,info;if(FS.createPreloadedFile=(parent,name,url,canRead,canWrite,onload,onerror,dontCreateFile,canOwn,preFinish)=>{FS_preloadFile(parent,name,url,canRead,canWrite,dontCreateFile,canOwn,preFinish).then(onload).catch(onerror)},FS.preloadFile=FS_preloadFile,FS.staticInit(),moduleArg=ClassHandle.prototype,Object.assign(moduleArg,{isAliasOf(other){if(!(this instanceof ClassHandle))return!1;if(!(other instanceof ClassHandle))return!1;for(var leftClass=this.$$.ptrType.registeredClass,left=this.$$.ptr,rightClass=(other.$$=other.$$,other.$$.ptrType.registeredClass),right=other.$$.ptr;leftClass.baseClass;)left=leftClass.upcast(left),leftClass=leftClass.baseClass;for(;rightClass.baseClass;)right=rightClass.upcast(right),rightClass=rightClass.baseClass;return leftClass===rightClass&&left===right},clone(){var o;return this.$$.ptr||throwInstanceAlreadyDeleted(this),this.$$.preservePointerOnDelete?(this.$$.count.value+=1,this):((o=attachFinalizer(Object.create(Object.getPrototypeOf(this),{$$:{value:{count:(o=this.$$).count,deleteScheduled:o.deleteScheduled,preservePointerOnDelete:o.preservePointerOnDelete,ptr:o.ptr,ptrType:o.ptrType,smartPtr:o.smartPtr,smartPtrType:o.smartPtrType}}}))).$$.count.value+=1,o.$$.deleteScheduled=!1,o)},delete(){this.$$.ptr||throwInstanceAlreadyDeleted(this),this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete&&throwBindingError("Object already scheduled for deletion"),detachFinalizer(this),releaseClassHandle(this.$$),this.$$.preservePointerOnDelete||(this.$$.smartPtr=void 0,this.$$.ptr=void 0)},isDeleted(){return!this.$$.ptr},deleteLater(){return this.$$.ptr||throwInstanceAlreadyDeleted(this),this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete&&throwBindingError("Object already scheduled for deletion"),deletionQueue.push(this),deletionQueue.length,this.$$.deleteScheduled=!0,this}}),(symbolDispose=Symbol.dispose)&&(moduleArg[symbolDispose]=moduleArg.delete),Object.assign(RegisteredPointer.prototype,{getPointee(ptr){return ptr=this.rawGetPointee?this.rawGetPointee(ptr):ptr},destructor(ptr){this.rawDestructor?.(ptr)},readValueFromPointer:readPointer,fromWireType:RegisteredPointer_fromWireType}),Module.noExitRuntime&&Module.noExitRuntime,Module.preloadPlugins&&(preloadPlugins=Module.preloadPlugins),Module.print&&(out=Module.print),Module.printErr&&(err=Module.printErr),Module.wasmBinary&&(wasmBinary=Module.wasmBinary),Module.arguments&&Module.arguments,Module.thisProgram&&(thisProgram=Module.thisProgram),Module.preInit)for("function"==typeof Module.preInit&&(Module.preInit=[Module.preInit]);0{___getTypeName=wasmExports.qa,_malloc=wasmExports.sa,_free=wasmExports.ta,_htons=wasmExports.ua,_ntohs=wasmExports.va,wasmMemory=wasmExports.oa,wasmTable=wasmExports.ra})(wasmExports=instance.exports),updateMemoryViews(),wasmExports}return info={a:{t:(ptr,type,destructor)=>{new ExceptionInfo(ptr).init(type,destructor),abort()},P:function(fd,addr,addrlen,d1,d2,d3){try{var sock=(fd=>{fd=SOCKFS.getSocket(fd);if(fd)return fd;throw new FS.ErrnoError(8)})(fd),info=((addrp,addrlen)=>{addrp=readSockaddr(addrp,addrlen);if(addrp.errno)throw new FS.ErrnoError(addrp.errno);return addrp.addr=DNS.lookup_addr(addrp.addr)||addrp.addr,addrp})(addr,addrlen);return sock.sock_ops.connect(sock,info.addr,info.port),0}catch(e){if(void 0===FS||"ErrnoError"!==e.name)throw e;return-e.errno}},ea:function(dirfd,path,amode,flags){try{var node,perms;return(path=SYSCALLS.getStr(path),path=SYSCALLS.calculateAt(dirfd,path),-8&amode)?-28:(node=FS.lookupPath(path,{follow:!0}).node)?(perms="",4&amode&&(perms+="r"),2&amode&&(perms+="w"),1&amode&&(perms+="x"),perms&&FS.nodePermissions(node,perms)?-2:0):-44}catch(e){if(void 0===FS||"ErrnoError"!==e.name)throw e;return-e.errno}},m:function(fd,cmd,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(cmd){case 0:if((arg=syscallGetVarargI())<0)return-28;for(;FS.streams[arg];)arg++;return FS.dupStream(stream,arg).fd;case 1:case 2:return 0;case 3:return stream.flags;case 4:var arg=syscallGetVarargI();return stream.flags=-289793&stream.flags|289792&arg,0;case 12:arg=syscallGetVarargP();return HEAP16[arg+0>>1]=2,0;case 13:case 14:return 0}return-28}catch(e){if(void 0===FS||"ErrnoError"!==e.name)throw e;return-e.errno}},ba:function(fd,buf){try{return SYSCALLS.writeStat(buf,FS.fstat(fd))}catch(e){if(void 0===FS||"ErrnoError"!==e.name)throw e;return-e.errno}},Z:function(fd,length){length=bigintToI53Checked(length);try{return isNaN(length)?-22:(FS.ftruncate(fd,length),0)}catch(e){if(void 0===FS||"ErrnoError"!==e.name)throw e;return-e.errno}},z:function(fd,op,varargs){SYSCALLS.varargs=varargs;try{var winsize,stream=SYSCALLS.getStreamFromFD(fd);switch(op){case 21509:return stream.tty?0:-59;case 21505:if(!stream.tty)return-59;if(stream.tty.ops.ioctl_tcgets){var termios=stream.tty.ops.ioctl_tcgets(stream),argp=syscallGetVarargP();HEAP32[argp>>2]=termios.c_iflag||0,HEAP32[argp+4>>2]=termios.c_oflag||0,HEAP32[argp+8>>2]=termios.c_cflag||0,HEAP32[argp+12>>2]=termios.c_lflag||0;for(var i=0;i<32;i++)HEAP8[argp+i+17]=termios.c_cc[i]||0}return 0;case 21510:case 21511:case 21512:return stream.tty?0:-59;case 21506:case 21507:case 21508:if(!stream.tty)return-59;if(stream.tty.ops.ioctl_tcsets){for(var argp=syscallGetVarargP(),c_iflag=HEAP32[argp>>2],c_oflag=HEAP32[argp+4>>2],c_cflag=HEAP32[argp+8>>2],c_lflag=HEAP32[argp+12>>2],c_cc=[],i=0;i<32;i++)c_cc.push(HEAP8[argp+i+17]);return stream.tty.ops.ioctl_tcsets(stream.tty,op,{c_iflag:c_iflag,c_oflag:c_oflag,c_cflag:c_cflag,c_lflag:c_lflag,c_cc:c_cc})}return 0;case 21519:return stream.tty?(argp=syscallGetVarargP(),HEAP32[argp>>2]=0):-59;case 21520:return stream.tty?-28:-59;case 21537:case 21531:argp=syscallGetVarargP();return FS.ioctl(stream,op,argp);case 21523:return stream.tty?(stream.tty.ops.ioctl_tiocgwinsz&&(winsize=stream.tty.ops.ioctl_tiocgwinsz(stream.tty),argp=syscallGetVarargP(),HEAP16[argp>>1]=winsize[0],HEAP16[argp+2>>1]=winsize[1]),0):-59;case 21524:case 21515:return stream.tty?0:-59;default:return-28}}catch(e){if(void 0===FS||"ErrnoError"!==e.name)throw e;return-e.errno}},_:function(path,buf){try{return path=SYSCALLS.getStr(path),SYSCALLS.writeStat(buf,FS.lstat(path))}catch(e){if(void 0===FS||"ErrnoError"!==e.name)throw e;return-e.errno}},$:function(dirfd,path,buf,flags){try{path=SYSCALLS.getStr(path);var nofollow=256&flags,allowEmpty=4096&flags;return flags&=-6401,path=SYSCALLS.calculateAt(dirfd,path,allowEmpty),SYSCALLS.writeStat(buf,nofollow?FS.lstat(path):FS.stat(path))}catch(e){if(void 0===FS||"ErrnoError"!==e.name)throw e;return-e.errno}},G:function(dirfd,path,flags,varargs){SYSCALLS.varargs=varargs;try{path=SYSCALLS.getStr(path),path=SYSCALLS.calculateAt(dirfd,path);var mode=varargs?syscallGetVarargI():0;return 64&flags&&(mode&=~SYSCALLS.currentUmask),FS.open(path,flags,mode).fd}catch(e){if(void 0===FS||"ErrnoError"!==e.name)throw e;return-e.errno}},E:function(domain,type,protocol){try{return SOCKFS.createSocket(domain,type,protocol).stream.fd}catch(e){if(void 0===FS||"ErrnoError"!==e.name)throw e;return-e.errno}},aa:function(path,buf){try{return path=SYSCALLS.getStr(path),SYSCALLS.writeStat(buf,FS.stat(path))}catch(e){if(void 0===FS||"ErrnoError"!==e.name)throw e;return-e.errno}},R:function(mask){try{var old=SYSCALLS.currentUmask;return SYSCALLS.currentUmask=mask,old}catch(e){if(void 0===FS||"ErrnoError"!==e.name)throw e;return-e.errno}},S:()=>abort(""),o:rawTupleType=>{var reg=tupleRegistrations[rawTupleType],elements=(delete tupleRegistrations[rawTupleType],reg.elements),elementsLength=elements.length,elementTypes=elements.map(elt=>elt.getterReturnType).concat(elements.map(elt=>elt.setterArgumentType)),rawConstructor=reg.rawConstructor,rawDestructor=reg.rawDestructor;whenDependentTypesAreResolved([rawTupleType],elementTypes,elementTypes=>{for(var[i,elt]of elements.entries()){let getterReturnType=elementTypes[i],getter=elt.getter,getterContext=elt.getterContext,setterArgumentType=elementTypes[i+elementsLength],setter=elt.setter,setterContext=elt.setterContext;elt.read=ptr=>getterReturnType.fromWireType(getter(getterContext,ptr)),elt.write=(ptr,o)=>{var destructors=[];setter(setterContext,ptr,setterArgumentType.toWireType(destructors,o)),runDestructors(destructors)}}return[{name:reg.name,fromWireType:ptr=>{for(var rv=new Array(elementsLength),i=0;i{if(elementsLength!==o.length)throw new TypeError(`Incorrect number of tuple elements for ${reg.name}: expected=${elementsLength}, actual=`+o.length);for(var ptr=rawConstructor(),i=0;i{var reg=structRegistrations[structType],rawConstructor=(delete structRegistrations[structType],reg.rawConstructor),rawDestructor=reg.rawDestructor,fieldRecords=reg.fields,fieldTypes=fieldRecords.map(field=>field.getterReturnType).concat(fieldRecords.map(field=>field.setterArgumentType));whenDependentTypesAreResolved([structType],fieldTypes,fieldTypes=>{var i,field,fields={};for([i,field]of fieldRecords.entries()){let getterReturnType=fieldTypes[i],getter=field.getter,getterContext=field.getterContext,setterArgumentType=fieldTypes[i+fieldRecords.length],setter=field.setter,setterContext=field.setterContext;fields[field.fieldName]={read:ptr=>getterReturnType.fromWireType(getter(getterContext,ptr)),write:(ptr,o)=>{var destructors=[];setter(setterContext,ptr,setterArgumentType.toWireType(destructors,o)),runDestructors(destructors)},optional:getterReturnType.optional}}return[{name:reg.name,fromWireType:ptr=>{var i,rv={};for(i in fields)rv[i]=fields[i].read(ptr);return rawDestructor(ptr),rv},toWireType:(destructors,o)=>{for(var fieldName in fields)if(!(fieldName in o||fields[fieldName].optional))throw new TypeError(`Missing field: "${fieldName}"`);var ptr=rawConstructor();for(fieldName in fields)fields[fieldName].write(ptr,o[fieldName]);return null!==destructors&&destructors.push(rawDestructor,ptr),ptr},readValueFromPointer:readPointer,destructorFunction:rawDestructor}]})},M:(primitiveType,name,size,minRange,maxRange)=>{name=AsciiToString(name);minRange=0n===minRange;let fromWireType=value=>value;if(minRange){let bitSize=8*size;maxRange=(fromWireType=value=>BigInt.asUintN(bitSize,value))(maxRange)}registerType(primitiveType,{name:name,fromWireType:fromWireType,toWireType:(destructors,value)=>value="number"==typeof value?BigInt(value):value,readValueFromPointer:integerReadValueFromPointer(name,size,!minRange),destructorFunction:null})},N:(rawType,name,trueValue,falseValue)=>{registerType(rawType,{name:name=AsciiToString(name),fromWireType:function(wt){return!!wt},toWireType:function(destructors,o){return o?trueValue:falseValue},readValueFromPointer:function(pointer){return this.fromWireType(HEAPU8[pointer])},destructorFunction:null})},d:(rawType,rawPointerType,rawConstPointerType,baseClassRawType,getActualTypeSignature,getActualType,upcastSignature,upcast,downcastSignature,downcast,name,destructorSignature,rawDestructor)=>{name=AsciiToString(name),getActualType=embind__requireFunction(getActualTypeSignature,getActualType),upcast&&=embind__requireFunction(upcastSignature,upcast),downcast&&=embind__requireFunction(downcastSignature,downcast),rawDestructor=embind__requireFunction(destructorSignature,rawDestructor);var legalFunctionName=(name=>{var f=(name=name.replace(/[^a-zA-Z0-9_]/g,"$")).charCodeAt(0);return char_0<=f&&f<=char_9?"_"+name:name})(name);exposePublicSymbol(legalFunctionName,function(){throwUnboundTypeError(`Cannot construct ${name} due to unbound types`,[baseClassRawType])}),whenDependentTypesAreResolved([rawType,rawPointerType,rawConstPointerType],baseClassRawType?[baseClassRawType]:[],base=>{base=base[0],base=baseClassRawType?(baseClass=base.registeredClass).instancePrototype:ClassHandle.prototype;var constructor=createNamedFunction(name,function(...args){if(Object.getPrototypeOf(this)!==instancePrototype)throw new BindingError("Use 'new' to construct "+name);if(void 0===registeredClass.constructor_body)throw new BindingError(name+" has no accessible constructor");var body=registeredClass.constructor_body[args.length];if(void 0===body)throw new BindingError(`Tried to invoke ctor of ${name} with invalid number of parameters (${args.length}) - expected (${Object.keys(registeredClass.constructor_body).toString()}) parameters instead!`);return body.apply(this,args)}),instancePrototype=Object.create(base,{constructor:{value:constructor}}),registeredClass=(constructor.prototype=instancePrototype,new RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast)),base=(registeredClass.baseClass&&(registeredClass.baseClass.__derivedClasses??=[],registeredClass.baseClass.__derivedClasses.push(registeredClass)),new RegisteredPointer(name,registeredClass,!0,!1,!1)),baseClass=new RegisteredPointer(name+"*",registeredClass,!1,!1,!1),constPointerConverter=new RegisteredPointer(name+" const*",registeredClass,!1,!0,!1);return registeredPointers[rawType]={pointerType:baseClass,constPointerType:constPointerConverter},((name,value,numArguments)=>{Module.hasOwnProperty(name)||throwInternalError("Replacing nonexistent public symbol"),void 0!==Module[name].overloadTable&&void 0!==numArguments?Module[name].overloadTable[numArguments]=value:(Module[name]=value,Module[name].argCount=numArguments)})(legalFunctionName,constructor),[base,baseClass,constPointerConverter]})},e:(rawClassType,methodName,argCount,rawArgTypesAddr,invokerSignature,rawInvoker,fn,isAsync,isNonnullReturn)=>{var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);methodName=AsciiToString(methodName),methodName=getFunctionName(methodName),rawInvoker=embind__requireFunction(invokerSignature,rawInvoker,isAsync),whenDependentTypesAreResolved([],[rawClassType],classType=>{var humanName=(classType=classType[0]).name+"."+methodName;function unboundTypesHandler(){throwUnboundTypeError(`Cannot call ${humanName} due to unbound types`,rawArgTypes)}methodName.startsWith("@@")&&(methodName=Symbol[methodName.substring(2)]);var proto=classType.registeredClass.constructor;return void 0===proto[methodName]?(unboundTypesHandler.argCount=argCount-1,proto[methodName]=unboundTypesHandler):(ensureOverloadTable(proto,methodName,humanName),proto[methodName].overloadTable[argCount-1]=unboundTypesHandler),whenDependentTypesAreResolved([],rawArgTypes,argTypes=>{var argTypes=[argTypes[0],null].concat(argTypes.slice(1)),func=craftInvokerFunction(humanName,argTypes,null,rawInvoker,fn,isAsync);if(void 0===proto[methodName].overloadTable?(func.argCount=argCount-1,proto[methodName]=func):proto[methodName].overloadTable[argCount-1]=func,classType.registeredClass.__derivedClasses)for(var derivedClass of classType.registeredClass.__derivedClasses)derivedClass.constructor.hasOwnProperty(methodName)||(derivedClass.constructor[methodName]=func);return[]}),[]})},f:(rawClassType,argCount,rawArgTypesAddr,invokerSignature,invoker,rawConstructor)=>{var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);invoker=embind__requireFunction(invokerSignature,invoker),whenDependentTypesAreResolved([],[rawClassType],classType=>{var humanName="constructor "+(classType=classType[0]).name;if(void 0===classType.registeredClass.constructor_body&&(classType.registeredClass.constructor_body=[]),void 0!==classType.registeredClass.constructor_body[argCount-1])throw new BindingError(`Cannot register multiple constructors with identical number of parameters (${argCount-1}) for class '${classType.name}'! Overload resolution is currently only performed using the parameter count, not actual type info!`);return classType.registeredClass.constructor_body[argCount-1]=()=>{throwUnboundTypeError(`Cannot construct ${classType.name} due to unbound types`,rawArgTypes)},whenDependentTypesAreResolved([],rawArgTypes,argTypes=>(argTypes.splice(1,0,null),classType.registeredClass.constructor_body[argCount-1]=craftInvokerFunction(humanName,argTypes,null,invoker,rawConstructor),[])),[]})},b:(rawClassType,methodName,argCount,rawArgTypesAddr,invokerSignature,rawInvoker,context,isPureVirtual,isAsync,isNonnullReturn)=>{var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);methodName=AsciiToString(methodName),methodName=getFunctionName(methodName),rawInvoker=embind__requireFunction(invokerSignature,rawInvoker,isAsync),whenDependentTypesAreResolved([],[rawClassType],classType=>{var humanName=(classType=classType[0]).name+"."+methodName;function unboundTypesHandler(){throwUnboundTypeError(`Cannot call ${humanName} due to unbound types`,rawArgTypes)}methodName.startsWith("@@")&&(methodName=Symbol[methodName.substring(2)]),isPureVirtual&&classType.registeredClass.pureVirtualFunctions.push(methodName);var proto=classType.registeredClass.instancePrototype,method=proto[methodName];return void 0===method||void 0===method.overloadTable&&method.className!==classType.name&&method.argCount===argCount-2?(unboundTypesHandler.argCount=argCount-2,unboundTypesHandler.className=classType.name,proto[methodName]=unboundTypesHandler):(ensureOverloadTable(proto,methodName,humanName),proto[methodName].overloadTable[argCount-2]=unboundTypesHandler),whenDependentTypesAreResolved([],rawArgTypes,argTypes=>{argTypes=craftInvokerFunction(humanName,argTypes,classType,rawInvoker,context,isAsync);return void 0===proto[methodName].overloadTable?(argTypes.argCount=argCount-2,proto[methodName]=argTypes):proto[methodName].overloadTable[argCount-2]=argTypes,[]}),[]})},a:(classType,fieldName,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext)=>{fieldName=AsciiToString(fieldName),getter=embind__requireFunction(getterSignature,getter),whenDependentTypesAreResolved([],[classType],classType=>{var humanName=(classType=classType[0]).name+"."+fieldName,desc={get(){throwUnboundTypeError(`Cannot access ${humanName} due to unbound types`,[getterReturnType,setterArgumentType])},enumerable:!0,configurable:!0};return desc.set=setter?()=>throwUnboundTypeError(`Cannot access ${humanName} due to unbound types`,[getterReturnType,setterArgumentType]):v=>throwBindingError(humanName+" is a read-only property"),Object.defineProperty(classType.registeredClass.instancePrototype,fieldName,desc),whenDependentTypesAreResolved([],setter?[getterReturnType,setterArgumentType]:[getterReturnType],types=>{var setterArgumentType,getterReturnType=types[0],desc={get(){var ptr=validateThis(this,classType,humanName+" getter");return getterReturnType.fromWireType(getter(getterContext,ptr))},enumerable:!0};return setter&&(setter=embind__requireFunction(setterSignature,setter),setterArgumentType=types[1],desc.set=function(v){var ptr=validateThis(this,classType,humanName+" setter"),destructors=[];setter(setterContext,ptr,setterArgumentType.toWireType(destructors,v)),runDestructors(destructors)}),Object.defineProperty(classType.registeredClass.instancePrototype,fieldName,desc),[]}),[]})},ka:(name,type,value)=>{name=AsciiToString(name),whenDependentTypesAreResolved([],[type],type=>(type=type[0],Module[name]=type.fromWireType(value),[]))},la:rawType=>registerType(rawType,EmValType),i:(rawType,name,size,isSigned,rawValueType)=>{name=AsciiToString(name);var valueType=(rawValueType=>0===rawValueType?"object":1===rawValueType?"number":"string")(rawValueType);switch(valueType){case"object":function ctor(){}ctor.values={},registerType(rawType,{name:name,constructor:ctor,valueType:valueType,fromWireType:function(c){return this.constructor.values[c]},toWireType:(destructors,c)=>c.value,readValueFromPointer:enumReadValueFromPointer(name,size,isSigned),destructorFunction:null}),exposePublicSymbol(name,ctor);break;case"number":registerType(rawType,{name:name,keysMap:keysMap={},valueType:valueType,fromWireType:c=>c,toWireType:(destructors,c)=>c,readValueFromPointer:enumReadValueFromPointer(name,size,isSigned),destructorFunction:null}),exposePublicSymbol(name,keysMap),delete Module[name].argCount;break;case"string":var keysMap;registerType(rawType,{name:name,valuesMap:{},reverseMap:{},keysMap:keysMap={},valueType:valueType,fromWireType:function(c){return this.reverseMap[c]},toWireType:function(destructors,c){return this.valuesMap[c]},readValueFromPointer:enumReadValueFromPointer(name,size,isSigned),destructorFunction:null}),exposePublicSymbol(name,keysMap),delete Module[name].argCount}},c:(rawEnumType,name,enumValue)=>{var enumType=requireRegisteredType(rawEnumType,"enum");switch(name=AsciiToString(name),enumType.valueType){case"object":var Enum=enumType.constructor,Value=Object.create(enumType.constructor.prototype,{value:{value:enumValue},constructor:{value:createNamedFunction(enumType.name+"_"+name,function(){})}});Enum.values[enumValue]=Value,Enum[name]=Value;break;case"number":enumType.keysMap[name]=enumValue;break;case"string":enumType.valuesMap[name]=enumValue,enumType.reverseMap[enumValue]=name,enumType.keysMap[name]=name}},L:(rawType,name,size)=>{registerType(rawType,{name:name=AsciiToString(name),fromWireType:value=>value,toWireType:(destructors,value)=>value,readValueFromPointer:((name,width)=>{switch(width){case 4:return function(pointer){return this.fromWireType(HEAPF32[pointer>>2])};case 8:return function(pointer){return this.fromWireType(HEAPF64[pointer>>3])};default:throw new TypeError(`invalid float width (${width}): `+name)}})(name,size),destructorFunction:null})},p:(primitiveType,name,size,minRange,maxRange)=>{var bitshift;name=AsciiToString(name);let fromWireType=value=>value;0===minRange&&(bitshift=32-8*size,maxRange=(fromWireType=value=>value<>>bitshift)(maxRange)),registerType(primitiveType,{name:name,fromWireType:fromWireType,toWireType:(destructors,value)=>value,readValueFromPointer:integerReadValueFromPointer(name,size,0!==minRange),destructorFunction:null})},x:(rawClassType,rawElementType,sizeMethodName,getMethodName)=>{sizeMethodName=AsciiToString(sizeMethodName),getMethodName=AsciiToString(getMethodName),whenDependentTypesAreResolved([],[rawClassType,rawElementType],types=>{types=types[0];return installIndexedIterator(types.registeredClass.instancePrototype,sizeMethodName,getMethodName),[]})},k:(rawType,dataTypeIndex,name)=>{var TA=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array,BigInt64Array,BigUint64Array][dataTypeIndex];function decodeMemoryView(handle){var size=HEAPU32[handle>>2],handle=HEAPU32[handle+4>>2];return new TA(HEAP8.buffer,handle,size)}registerType(rawType,{name:name=AsciiToString(name),fromWireType:decodeMemoryView,readValueFromPointer:decodeMemoryView},{ignoreDuplicateRegistrations:!0})},w:(rawOptionalType,rawType)=>{registerType(rawOptionalType,EmValOptionalType)},ma:(rawType,name)=>{name=AsciiToString(name);registerType(rawType,{name:name,fromWireType(value){var length=HEAPU32[value>>2],payload=value+4;return payload=UTF8ToString(payload,length,!0),_free(value),payload},toWireType(destructors,value){var length,valueIsOfTypeString="string"==typeof(value=value instanceof ArrayBuffer?new Uint8Array(value):value),base=(valueIsOfTypeString||ArrayBuffer.isView(value)&&1==value.BYTES_PER_ELEMENT||throwBindingError("Cannot pass non-string to std::string"),length=valueIsOfTypeString?lengthBytesUTF8(value):value.length,_malloc(4+length+1)),ptr=base+4;return HEAPU32[base>>2]=length,valueIsOfTypeString?stringToUTF8(value,ptr,length+1):HEAPU8.set(value,ptr),null!==destructors&&destructors.push(_free,base),base},readValueFromPointer:readPointer,destructorFunction(ptr){_free(ptr)}})},D:(rawType,charSize,name)=>{var decodeString,encodeString,lengthBytesUTF;name=AsciiToString(name),lengthBytesUTF=2===charSize?(decodeString=UTF16ToString,encodeString=stringToUTF16,lengthBytesUTF16):(decodeString=UTF32ToString,encodeString=stringToUTF32,lengthBytesUTF32),registerType(rawType,{name:name,fromWireType:value=>{var length=HEAPU32[value>>2],length=decodeString(value+4,length*charSize,!0);return _free(value),length},toWireType:(destructors,value)=>{"string"!=typeof value&&throwBindingError("Cannot pass non-string to C++ string type "+name);var length=lengthBytesUTF(value),ptr=_malloc(4+length+charSize);return HEAPU32[ptr>>2]=length/charSize,encodeString(value,ptr+4,length+charSize),null!==destructors&&destructors.push(_free,ptr),ptr},readValueFromPointer:readPointer,destructorFunction(ptr){_free(ptr)}})},n:(rawType,name,constructorSignature,rawConstructor,destructorSignature,rawDestructor)=>{tupleRegistrations[rawType]={name:AsciiToString(name),rawConstructor:embind__requireFunction(constructorSignature,rawConstructor),rawDestructor:embind__requireFunction(destructorSignature,rawDestructor),elements:[]}},fa:(rawTupleType,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext)=>{tupleRegistrations[rawTupleType].elements.push({getterReturnType:getterReturnType,getter:embind__requireFunction(getterSignature,getter),getterContext:getterContext,setterArgumentType:setterArgumentType,setter:embind__requireFunction(setterSignature,setter),setterContext:setterContext})},ga:(rawType,name,constructorSignature,rawConstructor,destructorSignature,rawDestructor)=>{structRegistrations[rawType]={name:AsciiToString(name),rawConstructor:embind__requireFunction(constructorSignature,rawConstructor),rawDestructor:embind__requireFunction(destructorSignature,rawDestructor),fields:[]}},K:(structType,fieldName,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext)=>{structRegistrations[structType].fields.push({fieldName:AsciiToString(fieldName),getterReturnType:getterReturnType,getter:embind__requireFunction(getterSignature,getter),getterContext:getterContext,setterArgumentType:setterArgumentType,setter:embind__requireFunction(setterSignature,setter),setterContext:setterContext})},O:(rawType,name)=>{registerType(rawType,{isVoid:!0,name:name=AsciiToString(name),fromWireType:()=>{},toWireType:(destructors,o)=>{}})},h:(argCount,argTypesPtr,kind)=>{var[argTypesPtr,...argTypes]=((argCount,argTypes)=>{for(var a=new Array(argCount),i=0;i>2],"parameter "+i);return a})(argCount,argTypesPtr),toReturnWire=argTypesPtr.toWireType.bind(argTypesPtr),argFromPtr=argTypes.map(type=>type.readValueFromPointer.bind(type)),captures=(argCount--,{toValue:Emval.toValue}),argCount=argFromPtr.map((argFromPtr,i)=>{var captureName="argFromPtr"+i;return captures[captureName]=argFromPtr,captureName+`(args${i?"+"+8*i:""})`});switch(kind){case 0:functionBody="toValue(handle)";break;case 2:functionBody="new (toValue(handle))";break;case 3:functionBody="";break;case 1:captures.getStringOrSymbol=getStringOrSymbol,functionBody="toValue(handle)[getStringOrSymbol(methodName)]"}functionBody+=`(${argCount})`,argTypesPtr.isVoid||(captures.toReturnWire=toReturnWire,captures.emval_returnValue=emval_returnValue,functionBody=`return emval_returnValue(toReturnWire, destructorsRef, ${functionBody})`);var functionBody=`return function (handle, methodName, destructorsRef, args) { +${functionBody} +}`,argFromPtr=new Function(Object.keys(captures),functionBody)(...Object.values(captures)),kind=`methodCaller<(${argTypes.map(t=>t.name)}) => ${argTypesPtr.name}>`;return argCount=createNamedFunction(kind,argFromPtr),toReturnWire=emval_methodCallers.length,emval_methodCallers.push(argCount),toReturnWire},na:__emval_decref,C:(first,second)=>(first=Emval.toValue(first))==(second=Emval.toValue(second)),s:name=>name?(name=getStringOrSymbol(name),Emval.toHandle(globalThis[name])):Emval.toHandle(globalThis),B:name=>(name=getStringOrSymbol(name),Emval.toHandle(Module[name])),u:(handle,key)=>(handle=Emval.toValue(handle),key=Emval.toValue(key),Emval.toHandle(handle[key])),J:handle=>{9(object=Emval.toValue(object))instanceof(constructor=Emval.toValue(constructor)),g:(caller,handle,methodName,destructorsRef,args)=>emval_methodCallers[caller](handle,methodName,destructorsRef,args),ja:handle=>"number"==typeof(handle=Emval.toValue(handle)),ia:handle=>"string"==typeof(handle=Emval.toValue(handle)),l:()=>Emval.toHandle([]),q:v=>Emval.toHandle(getStringOrSymbol(v)),j:()=>Emval.toHandle({}),W:handle=>{var destructors=Emval.toValue(handle);runDestructors(destructors),__emval_decref(handle)},r:(handle,key,value)=>{handle=Emval.toValue(handle),key=Emval.toValue(key),value=Emval.toValue(value),handle[key]=value},T:function(time,tmPtr){time=bigintToI53Checked(time);var time=new Date(1e3*time),start=(HEAP32[tmPtr>>2]=time.getUTCSeconds(),HEAP32[tmPtr+4>>2]=time.getUTCMinutes(),HEAP32[tmPtr+8>>2]=time.getUTCHours(),HEAP32[tmPtr+12>>2]=time.getUTCDate(),HEAP32[tmPtr+16>>2]=time.getUTCMonth(),HEAP32[tmPtr+20>>2]=time.getUTCFullYear()-1900,HEAP32[tmPtr+24>>2]=time.getUTCDay(),Date.UTC(time.getUTCFullYear(),0,1,0,0,0,0)),time=(time.getTime()-start)/864e5|0;HEAP32[tmPtr+28>>2]=time},U:function(time,tmPtr){time=bigintToI53Checked(time);var time=new Date(1e3*time),yday=(HEAP32[tmPtr>>2]=time.getSeconds(),HEAP32[tmPtr+4>>2]=time.getMinutes(),HEAP32[tmPtr+8>>2]=time.getHours(),HEAP32[tmPtr+12>>2]=time.getDate(),HEAP32[tmPtr+16>>2]=time.getMonth(),HEAP32[tmPtr+20>>2]=time.getFullYear()-1900,HEAP32[tmPtr+24>>2]=time.getDay(),0|ydayFromDate(time)),yday=(HEAP32[tmPtr+28>>2]=yday,HEAP32[tmPtr+36>>2]=-60*time.getTimezoneOffset(),new Date(time.getFullYear(),0,1)),summerOffset=new Date(time.getFullYear(),6,1).getTimezoneOffset(),yday=yday.getTimezoneOffset(),time=0|(summerOffset!=yday&&time.getTimezoneOffset()==Math.min(yday,summerOffset));HEAP32[tmPtr+32>>2]=time},V:function(tmPtr){date=new Date(HEAP32[tmPtr+20>>2]+1900,HEAP32[tmPtr+16>>2],HEAP32[tmPtr+12>>2],HEAP32[tmPtr+8>>2],HEAP32[tmPtr+4>>2],HEAP32[tmPtr>>2],0);var guessedOffset,start,summerOffset,dst,date,dstOffset=isNaN(date.getTime())?-1:(dst=HEAP32[tmPtr+32>>2],guessedOffset=date.getTimezoneOffset(),start=new Date(date.getFullYear(),0,1),summerOffset=new Date(date.getFullYear(),6,1).getTimezoneOffset(),start=start.getTimezoneOffset(),dstOffset=Math.min(start,summerOffset),dst<0?HEAP32[tmPtr+32>>2]=Number(summerOffset!=start&&dstOffset==guessedOffset):0>2]=date.getDay(),dst=0|ydayFromDate(date),HEAP32[tmPtr+28>>2]=dst,HEAP32[tmPtr>>2]=date.getSeconds(),HEAP32[tmPtr+4>>2]=date.getMinutes(),HEAP32[tmPtr+8>>2]=date.getHours(),HEAP32[tmPtr+12>>2]=date.getDate(),HEAP32[tmPtr+16>>2]=date.getMonth(),HEAP32[tmPtr+20>>2]=date.getYear(),date.getTime()/1e3);return BigInt(dstOffset)},X:(timezone,daylight,std_name,dst_name)=>{var currentYear=(new Date).getFullYear(),winter=new Date(currentYear,0,1),currentYear=new Date(currentYear,6,1),winter=winter.getTimezoneOffset(),currentYear=currentYear.getTimezoneOffset(),stdTimezoneOffset=Math.max(winter,currentYear),timezone=(HEAPU32[timezone>>2]=60*stdTimezoneOffset,HEAP32[daylight>>2]=Number(winter!=currentYear),timezoneOffset=>{var sign=0<=timezoneOffset?"-":"+",timezoneOffset=Math.abs(timezoneOffset);return"UTC"+sign+String(Math.floor(timezoneOffset/60)).padStart(2,"0")+String(timezoneOffset%60).padStart(2,"0")}),stdTimezoneOffset=timezone(winter),daylight=timezone(currentYear);currentYearDate.now(),F:()=>performance.now(),Q:requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;if(!(2147483648{size=(size-wasmMemory.buffer.byteLength+65535)/65536|0;try{return wasmMemory.grow(size),updateMemoryViews(),1}catch(e){}})(Math.min(2147483648,(overGrownHeapSize=Math.max(requestedSize,overGrownHeapSize),alignment=65536,Math.ceil(overGrownHeapSize/alignment)*alignment))))return!0}return!1},ca:(__environ,environ_buf)=>{var string,bufSize=0,envp=0;for(string of getEnvStrings()){var ptr=environ_buf+bufSize;HEAPU32[__environ+envp>>2]=ptr,bufSize+=stringToUTF8(string,ptr,1/0)+1,envp+=4}return 0},da:(penviron_count,penviron_buf_size)=>{var string,strings=getEnvStrings(),bufSize=(HEAPU32[penviron_count>>2]=strings.length,0);for(string of strings)bufSize+=lengthBytesUTF8(string)+1;return HEAPU32[penviron_buf_size>>2]=bufSize,0},v:function(fd){try{var stream=SYSCALLS.getStreamFromFD(fd);return FS.close(stream),0}catch(e){if(void 0===FS||"ErrnoError"!==e.name)throw e;return e.errno}},I:function(fd,iov,iovcnt,pnum){try{var num=((stream,iov,iovcnt,offset)=>{for(var ret=0,i=0;i>2],len=HEAPU32[iov+4>>2],ptr=(iov+=8,FS.read(stream,HEAP8,ptr,len,offset));if(ptr<0)return-1;if(ret+=ptr,ptr>2]=num,0}catch(e){if(void 0===FS||"ErrnoError"!==e.name)throw e;return e.errno}},Y:function(fd,offset,whence,newOffset){offset=bigintToI53Checked(offset);try{var stream;return isNaN(offset)?22:(stream=SYSCALLS.getStreamFromFD(fd),FS.llseek(stream,offset,whence),HEAP64[newOffset>>3]=BigInt(stream.position),stream.getdents&&0===offset&&0===whence&&(stream.getdents=null),0)}catch(e){if(void 0===FS||"ErrnoError"!==e.name)throw e;return e.errno}},y:function(fd,iov,iovcnt,pnum){try{var num=((stream,iov,iovcnt,offset)=>{for(var ret=0,i=0;i>2],len=HEAPU32[iov+4>>2],ptr=(iov+=8,FS.write(stream,HEAP8,ptr,len,offset));if(ptr<0)return-1;if(ret+=ptr,ptr>2]=num,0}catch(e){if(void 0===FS||"ErrnoError"!==e.name)throw e;return e.errno}}}},wasmExports=await(Module.instantiateWasm?new Promise((resolve,reject)=>{Module.instantiateWasm(info,(inst,mod)=>{resolve(receiveInstance(inst))})}):(wasmBinaryFile??=findWasmBinary(),(result=>receiveInstance(result.instance))(await instantiateAsync(wasmBinary,wasmBinaryFile,info)))),function run(){if(0{setTimeout(()=>Module.setStatus(""),1),doRun()},1)):doRun()}function doRun(){if(Module.calledRun=!0,!ABORT){if(runtimeInitialized=!0,SOCKFS.root=FS.mount(SOCKFS,{},null),Module.noFSInit||FS.initialized||FS.init(),TTY.init(),wasmExports.pa(),FS.ignorePermissions=!1,readyPromiseResolve?.(Module),Module.onRuntimeInitialized?.(),Module.postRun)for("function"==typeof Module.postRun&&(Module.postRun=[Module.postRun]);Module.postRun.length;)addOnPostRun(Module.postRun.shift());callRuntimeCallbacks(onPostRuns)}}}(),runtimeInitialized?Module:new Promise((resolve,reject)=>{readyPromiseResolve=resolve,readyPromiseReject=reject})}})();"object"==typeof exports&&"object"==typeof module?(module.exports=rhino3dm,module.exports.default=rhino3dm):"function"==typeof define&&define.amd&&define([],()=>rhino3dm); \ No newline at end of file