diff --git a/src/components/ControlPanel.css b/src/components/ControlPanel.css index 3a6f5ce..f420ccb 100644 --- a/src/components/ControlPanel.css +++ b/src/components/ControlPanel.css @@ -148,3 +148,34 @@ min-width: 100px; text-align: right; } + +.resolution-input, +.allowance-input { + margin-bottom: 12px; +} + +.resolution-input label, +.allowance-input label { + color: #fff; + font-size: 13px; + display: flex; + align-items: center; + gap: 8px; +} + +.resolution-input input, +.allowance-input input { + width: 60px; + padding: 4px 8px; + border: 1px solid #444; + border-radius: 4px; + background: #1a1a2e; + color: #fff; + font-size: 13px; +} + +.resolution-input input:focus, +.allowance-input input:focus { + outline: none; + border-color: #e94560; +} diff --git a/src/components/ControlPanel.jsx b/src/components/ControlPanel.jsx index 782f93c..21c8719 100644 --- a/src/components/ControlPanel.jsx +++ b/src/components/ControlPanel.jsx @@ -12,6 +12,7 @@ function ControlPanel({ model, upperEnvelope, onModelImport, onGenerateUpper, on const fileInputRef = useRef(null) const workerRef = useRef(null) const selectedToolRef = useRef('ball') + const upperEnvelopeRef = useRef(null) const [isGenerating, setIsGenerating] = useState(false) const [isRepairing, setIsRepairing] = useState(false) const [repairProgress, setRepairProgress] = useState(0) @@ -19,6 +20,8 @@ function ControlPanel({ model, upperEnvelope, onModelImport, onGenerateUpper, on const [selectedTool, setSelectedTool] = useState('ball') const [computeMode, setComputeMode] = useState('worker') const [webgpuAvailable, setWebgpuAvailable] = useState(false) + const [resolutionPercent, setResolutionPercent] = useState(12) + const [allowance, setAllowance] = useState(0.5) useEffect(() => { if (navigator.gpu) { @@ -34,6 +37,10 @@ function ControlPanel({ model, upperEnvelope, onModelImport, onGenerateUpper, on selectedToolRef.current = selectedTool }, [selectedTool]) + useEffect(() => { + upperEnvelopeRef.current = upperEnvelope + }, [upperEnvelope]) + useEffect(() => { workerRef.current = new Worker( new URL('../workers/repairWorker.js', import.meta.url), @@ -51,8 +58,8 @@ function ControlPanel({ model, upperEnvelope, onModelImport, onGenerateUpper, on const toolpathPoints = [] const repairedPoints = [] const halfSize = MATERIAL_SIZE / 2 - const resolution = 0.1 - const gridSize = Math.ceil(MATERIAL_SIZE / resolution) + const resolution = upperEnvelopeRef.current.resolution + const gridSize = upperEnvelopeRef.current.gridSize const gridOffset = halfSize for (let gy = 0; gy < gridSize; gy++) { @@ -199,35 +206,60 @@ function ControlPanel({ model, upperEnvelope, onModelImport, onGenerateUpper, on await new Promise(resolve => setTimeout(resolve, 50)) const geometry = model.geometry - const resolution = 0.1 - const gridSize = Math.ceil(MATERIAL_SIZE / resolution) - const depthMap = new Float32Array(gridSize * gridSize).fill(-Infinity) - - const vertices = geometry.attributes.position.array - const bbox = geometry.boundingBox + let resolution + const tool = selectedToolRef.current + const toolDiameter = (tool === 'flat14') ? 6.35 : 3.175 + if (tool === 'ball' || tool === 'flat18' || tool === 'flat14') { + resolution = toolDiameter * (resolutionPercent / 100) + } else { + resolution = 0.1 + } + + const gridSize = Math.ceil(MATERIAL_SIZE / resolution) const halfSize = MATERIAL_SIZE / 2 const gridOffset = halfSize + const toGrid = (v) => Math.floor((v + gridOffset) / resolution) - for (let i = 0; i < vertices.length; i += 3) { - const x = vertices[i] - const y = vertices[i + 1] - const z = vertices[i + 2] + const vertices = geometry.attributes.position.array + const bbox = geometry.boundingBox - const gridX = Math.floor((x + gridOffset) / resolution) - const gridY = Math.floor((y + gridOffset) / resolution) + const depthMap = new Float32Array(gridSize * gridSize).fill(-Infinity) - if (gridX >= 0 && gridX < gridSize && gridY >= 0 && gridY < gridSize) { - const idx = gridY * gridSize + gridX - if (z > depthMap[idx]) { - depthMap[idx] = z + for (let i = 0; i < vertices.length; i += 9) { + const x0 = vertices[i], y0 = vertices[i + 1], z0 = vertices[i + 2] + const x1 = vertices[i + 3], y1 = vertices[i + 4], z1 = vertices[i + 5] + const x2 = vertices[i + 6], y2 = vertices[i + 7], z2 = vertices[i + 8] + + const gMinX = Math.max(0, Math.min(toGrid(x0), toGrid(x1), toGrid(x2))) + const gMaxX = Math.min(gridSize - 1, Math.max(toGrid(x0), toGrid(x1), toGrid(x2))) + const gMinY = Math.max(0, Math.min(toGrid(y0), toGrid(y1), toGrid(y2))) + const gMaxY = Math.min(gridSize - 1, Math.max(toGrid(y0), toGrid(y1), toGrid(y2))) + + const det = (y1 - y2) * (x0 - x2) + (x2 - x1) * (y0 - y2) + if (Math.abs(det) < 1e-10) continue + + for (let gy = gMinY; gy <= gMaxY; gy++) { + for (let gx = gMinX; gx <= gMaxX; gx++) { + const px = gx * resolution - gridOffset + resolution / 2 + const py = gy * resolution - gridOffset + resolution / 2 + + const lambda1 = ((y1 - y2) * (px - x2) + (x2 - x1) * (py - y2)) / det + const lambda2 = ((y2 - y0) * (px - x2) + (x0 - x2) * (py - y2)) / det + const lambda3 = 1 - lambda1 - lambda2 + + if (lambda1 >= 0 && lambda2 >= 0 && lambda3 >= 0) { + const z = lambda1 * z0 + lambda2 * z1 + lambda3 * z2 + const idx = gy * gridSize + gx + if (z > depthMap[idx]) { + depthMap[idx] = z + } + } } } } - console.log('depthMap 采样完成, gridSize:', gridSize, '有效像素:', depthMap.filter(z => z !== -Infinity).length) - const surfacePoints = [] for (let gy = 0; gy < gridSize; gy++) { for (let gx = 0; gx < gridSize; gx++) { @@ -270,6 +302,14 @@ function ControlPanel({ model, upperEnvelope, onModelImport, onGenerateUpper, on toolRadius = 3.175 / 2 toolAngle = 0 tipRadius = 0 + } else if (selectedTool === 'flat18') { + toolRadius = 3.175 / 2 + toolAngle = 0 + tipRadius = 0 + } else if (selectedTool === 'flat14') { + toolRadius = 6.35 / 2 + toolAngle = 0 + tipRadius = 0 } else if (selectedTool === 'taper15') { toolRadius = 3.175 / 2 toolAngle = 15 @@ -290,6 +330,7 @@ function ControlPanel({ model, upperEnvelope, onModelImport, onGenerateUpper, on toolRadius, toolAngle, tipRadius, + allowance, (msg) => { if (msg.type === 'progress') { setRepairProgress(msg.progress) @@ -336,7 +377,8 @@ function ControlPanel({ model, upperEnvelope, onModelImport, onGenerateUpper, on toolType: selectedTool, toolRadius, toolAngle, - tipRadius + tipRadius, + allowance }) } } @@ -367,6 +409,22 @@ function ControlPanel({ model, upperEnvelope, onModelImport, onGenerateUpper, on

2. 生成上包络面

+
+ +
+ {(selectedTool === 'flat18' || selectedTool === 'flat14') && ( +
+ +
+ )}