加入采样精度、平底铣刀及平底铣刀余量参数
This commit is contained in:
@@ -148,3 +148,34 @@
|
|||||||
min-width: 100px;
|
min-width: 100px;
|
||||||
text-align: right;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ function ControlPanel({ model, upperEnvelope, onModelImport, onGenerateUpper, on
|
|||||||
const fileInputRef = useRef(null)
|
const fileInputRef = useRef(null)
|
||||||
const workerRef = useRef(null)
|
const workerRef = useRef(null)
|
||||||
const selectedToolRef = useRef('ball')
|
const selectedToolRef = useRef('ball')
|
||||||
|
const upperEnvelopeRef = useRef(null)
|
||||||
const [isGenerating, setIsGenerating] = useState(false)
|
const [isGenerating, setIsGenerating] = useState(false)
|
||||||
const [isRepairing, setIsRepairing] = useState(false)
|
const [isRepairing, setIsRepairing] = useState(false)
|
||||||
const [repairProgress, setRepairProgress] = useState(0)
|
const [repairProgress, setRepairProgress] = useState(0)
|
||||||
@@ -19,6 +20,8 @@ function ControlPanel({ model, upperEnvelope, onModelImport, onGenerateUpper, on
|
|||||||
const [selectedTool, setSelectedTool] = useState('ball')
|
const [selectedTool, setSelectedTool] = useState('ball')
|
||||||
const [computeMode, setComputeMode] = useState('worker')
|
const [computeMode, setComputeMode] = useState('worker')
|
||||||
const [webgpuAvailable, setWebgpuAvailable] = useState(false)
|
const [webgpuAvailable, setWebgpuAvailable] = useState(false)
|
||||||
|
const [resolutionPercent, setResolutionPercent] = useState(12)
|
||||||
|
const [allowance, setAllowance] = useState(0.5)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (navigator.gpu) {
|
if (navigator.gpu) {
|
||||||
@@ -34,6 +37,10 @@ function ControlPanel({ model, upperEnvelope, onModelImport, onGenerateUpper, on
|
|||||||
selectedToolRef.current = selectedTool
|
selectedToolRef.current = selectedTool
|
||||||
}, [selectedTool])
|
}, [selectedTool])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
upperEnvelopeRef.current = upperEnvelope
|
||||||
|
}, [upperEnvelope])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
workerRef.current = new Worker(
|
workerRef.current = new Worker(
|
||||||
new URL('../workers/repairWorker.js', import.meta.url),
|
new URL('../workers/repairWorker.js', import.meta.url),
|
||||||
@@ -51,8 +58,8 @@ function ControlPanel({ model, upperEnvelope, onModelImport, onGenerateUpper, on
|
|||||||
const toolpathPoints = []
|
const toolpathPoints = []
|
||||||
const repairedPoints = []
|
const repairedPoints = []
|
||||||
const halfSize = MATERIAL_SIZE / 2
|
const halfSize = MATERIAL_SIZE / 2
|
||||||
const resolution = 0.1
|
const resolution = upperEnvelopeRef.current.resolution
|
||||||
const gridSize = Math.ceil(MATERIAL_SIZE / resolution)
|
const gridSize = upperEnvelopeRef.current.gridSize
|
||||||
const gridOffset = halfSize
|
const gridOffset = halfSize
|
||||||
|
|
||||||
for (let gy = 0; gy < gridSize; gy++) {
|
for (let gy = 0; gy < gridSize; gy++) {
|
||||||
@@ -199,34 +206,59 @@ function ControlPanel({ model, upperEnvelope, onModelImport, onGenerateUpper, on
|
|||||||
await new Promise(resolve => setTimeout(resolve, 50))
|
await new Promise(resolve => setTimeout(resolve, 50))
|
||||||
|
|
||||||
const geometry = model.geometry
|
const geometry = model.geometry
|
||||||
const resolution = 0.1
|
|
||||||
|
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 gridSize = Math.ceil(MATERIAL_SIZE / resolution)
|
||||||
|
|
||||||
const depthMap = new Float32Array(gridSize * gridSize).fill(-Infinity)
|
const halfSize = MATERIAL_SIZE / 2
|
||||||
|
const gridOffset = halfSize
|
||||||
|
const toGrid = (v) => Math.floor((v + gridOffset) / resolution)
|
||||||
|
|
||||||
const vertices = geometry.attributes.position.array
|
const vertices = geometry.attributes.position.array
|
||||||
const bbox = geometry.boundingBox
|
const bbox = geometry.boundingBox
|
||||||
|
|
||||||
const halfSize = MATERIAL_SIZE / 2
|
const depthMap = new Float32Array(gridSize * gridSize).fill(-Infinity)
|
||||||
const gridOffset = halfSize
|
|
||||||
|
|
||||||
for (let i = 0; i < vertices.length; i += 3) {
|
for (let i = 0; i < vertices.length; i += 9) {
|
||||||
const x = vertices[i]
|
const x0 = vertices[i], y0 = vertices[i + 1], z0 = vertices[i + 2]
|
||||||
const y = vertices[i + 1]
|
const x1 = vertices[i + 3], y1 = vertices[i + 4], z1 = vertices[i + 5]
|
||||||
const z = vertices[i + 2]
|
const x2 = vertices[i + 6], y2 = vertices[i + 7], z2 = vertices[i + 8]
|
||||||
|
|
||||||
const gridX = Math.floor((x + gridOffset) / resolution)
|
const gMinX = Math.max(0, Math.min(toGrid(x0), toGrid(x1), toGrid(x2)))
|
||||||
const gridY = Math.floor((y + gridOffset) / resolution)
|
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)))
|
||||||
|
|
||||||
if (gridX >= 0 && gridX < gridSize && gridY >= 0 && gridY < gridSize) {
|
const det = (y1 - y2) * (x0 - x2) + (x2 - x1) * (y0 - y2)
|
||||||
const idx = gridY * gridSize + gridX
|
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]) {
|
if (z > depthMap[idx]) {
|
||||||
depthMap[idx] = z
|
depthMap[idx] = z
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
console.log('depthMap 采样完成, gridSize:', gridSize, '有效像素:', depthMap.filter(z => z !== -Infinity).length)
|
}
|
||||||
|
|
||||||
const surfacePoints = []
|
const surfacePoints = []
|
||||||
for (let gy = 0; gy < gridSize; gy++) {
|
for (let gy = 0; gy < gridSize; gy++) {
|
||||||
@@ -270,6 +302,14 @@ function ControlPanel({ model, upperEnvelope, onModelImport, onGenerateUpper, on
|
|||||||
toolRadius = 3.175 / 2
|
toolRadius = 3.175 / 2
|
||||||
toolAngle = 0
|
toolAngle = 0
|
||||||
tipRadius = 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') {
|
} else if (selectedTool === 'taper15') {
|
||||||
toolRadius = 3.175 / 2
|
toolRadius = 3.175 / 2
|
||||||
toolAngle = 15
|
toolAngle = 15
|
||||||
@@ -290,6 +330,7 @@ function ControlPanel({ model, upperEnvelope, onModelImport, onGenerateUpper, on
|
|||||||
toolRadius,
|
toolRadius,
|
||||||
toolAngle,
|
toolAngle,
|
||||||
tipRadius,
|
tipRadius,
|
||||||
|
allowance,
|
||||||
(msg) => {
|
(msg) => {
|
||||||
if (msg.type === 'progress') {
|
if (msg.type === 'progress') {
|
||||||
setRepairProgress(msg.progress)
|
setRepairProgress(msg.progress)
|
||||||
@@ -336,7 +377,8 @@ function ControlPanel({ model, upperEnvelope, onModelImport, onGenerateUpper, on
|
|||||||
toolType: selectedTool,
|
toolType: selectedTool,
|
||||||
toolRadius,
|
toolRadius,
|
||||||
toolAngle,
|
toolAngle,
|
||||||
tipRadius
|
tipRadius,
|
||||||
|
allowance
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -367,6 +409,22 @@ function ControlPanel({ model, upperEnvelope, onModelImport, onGenerateUpper, on
|
|||||||
|
|
||||||
<div className="control-section">
|
<div className="control-section">
|
||||||
<h3>2. 生成上包络面</h3>
|
<h3>2. 生成上包络面</h3>
|
||||||
|
<div className="resolution-input">
|
||||||
|
<label>
|
||||||
|
采样精度(刀具直径百分比):
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
step="1"
|
||||||
|
value={resolutionPercent}
|
||||||
|
onChange={(e) => {
|
||||||
|
const val = parseInt(e.target.value)
|
||||||
|
if (val > 0) setResolutionPercent(val)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
%
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<button
|
<button
|
||||||
className="btn btn-success"
|
className="btn btn-success"
|
||||||
onClick={generateUpperEnvelope}
|
onClick={generateUpperEnvelope}
|
||||||
@@ -405,6 +463,26 @@ function ControlPanel({ model, upperEnvelope, onModelImport, onGenerateUpper, on
|
|||||||
/>
|
/>
|
||||||
1/8" 球刀
|
1/8" 球刀
|
||||||
</label>
|
</label>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="tool"
|
||||||
|
value="flat18"
|
||||||
|
checked={selectedTool === 'flat18'}
|
||||||
|
onChange={() => setSelectedTool('flat18')}
|
||||||
|
/>
|
||||||
|
1/8" 平底铣刀
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="tool"
|
||||||
|
value="flat14"
|
||||||
|
checked={selectedTool === 'flat14'}
|
||||||
|
onChange={() => setSelectedTool('flat14')}
|
||||||
|
/>
|
||||||
|
1/4" 平底铣刀
|
||||||
|
</label>
|
||||||
<label>
|
<label>
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
@@ -426,6 +504,23 @@ function ControlPanel({ model, upperEnvelope, onModelImport, onGenerateUpper, on
|
|||||||
1/8" 30° 尖刀 (0.1mm)
|
1/8" 30° 尖刀 (0.1mm)
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
{(selectedTool === 'flat18' || selectedTool === 'flat14') && (
|
||||||
|
<div className="allowance-input">
|
||||||
|
<label>
|
||||||
|
余量 (mm):
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
step="0.1"
|
||||||
|
value={allowance}
|
||||||
|
onChange={(e) => {
|
||||||
|
const val = parseFloat(e.target.value)
|
||||||
|
if (val >= 0) setAllowance(val)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<button
|
<button
|
||||||
className="btn btn-warning"
|
className="btn btn-warning"
|
||||||
onClick={repairAndGenerate}
|
onClick={repairAndGenerate}
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ function EnvelopeViewer({
|
|||||||
geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3))
|
geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3))
|
||||||
|
|
||||||
const material = new THREE.PointsMaterial({
|
const material = new THREE.PointsMaterial({
|
||||||
size: resolution * 0.8,
|
size: Math.max(resolution * 0.8, 0.15),
|
||||||
vertexColors: true,
|
vertexColors: true,
|
||||||
transparent: true,
|
transparent: true,
|
||||||
opacity: 0.8,
|
opacity: 0.8,
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ fn repairedMain(@builtin(global_invocation_id) global_id: vec3<u32>) {
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export async function runWebGPUConvolution(depthMap, gridSize, resolution, toolType, toolRadius, toolAngle, tipRadius, onProgress) {
|
export async function runWebGPUConvolution(depthMap, gridSize, resolution, toolType, toolRadius, toolAngle, tipRadius, allowance, onProgress) {
|
||||||
if (!navigator.gpu) {
|
if (!navigator.gpu) {
|
||||||
throw new Error('WebGPU not supported')
|
throw new Error('WebGPU not supported')
|
||||||
}
|
}
|
||||||
@@ -152,6 +152,10 @@ export async function runWebGPUConvolution(depthMap, gridSize, resolution, toolT
|
|||||||
gpuInputMap[i] = depthMap[i] === -Infinity ? EMPTY_VALUE : depthMap[i]
|
gpuInputMap[i] = depthMap[i] === -Infinity ? EMPTY_VALUE : depthMap[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isFlatTool = toolType === 'flat18' || toolType === 'flat14'
|
||||||
|
const allowanceRadius = isFlatTool ? (toolRadius / 2 + allowance) : 0
|
||||||
|
const allowanceKernelRadius = isFlatTool ? Math.ceil(allowanceRadius / resolution) : 0
|
||||||
|
|
||||||
const kernelRadius = Math.ceil(toolRadius / resolution)
|
const kernelRadius = Math.ceil(toolRadius / resolution)
|
||||||
|
|
||||||
const computeToolProfile = (dist) => {
|
const computeToolProfile = (dist) => {
|
||||||
@@ -160,6 +164,8 @@ export async function runWebGPUConvolution(depthMap, gridSize, resolution, toolT
|
|||||||
return Math.sqrt(toolRadius * toolRadius - dist * dist)
|
return Math.sqrt(toolRadius * toolRadius - dist * dist)
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
|
} else if (toolType === 'flat18' || toolType === 'flat14') {
|
||||||
|
return 0
|
||||||
} else {
|
} else {
|
||||||
const halfAngle = toolAngle * Math.PI / 180 / 2
|
const halfAngle = toolAngle * Math.PI / 180 / 2
|
||||||
const tanHalf = Math.tan(halfAngle)
|
const tanHalf = Math.tan(halfAngle)
|
||||||
@@ -186,6 +192,20 @@ export async function runWebGPUConvolution(depthMap, gridSize, resolution, toolT
|
|||||||
|
|
||||||
const centerProfileZ = kernel.find(k => k.kx === 0 && k.ky === 0)?.profileZ || 0
|
const centerProfileZ = kernel.find(k => k.kx === 0 && k.ky === 0)?.profileZ || 0
|
||||||
|
|
||||||
|
const allowanceKernel = isFlatTool ? [] : null
|
||||||
|
if (isFlatTool) {
|
||||||
|
for (let ky = -allowanceKernelRadius; ky <= allowanceKernelRadius; ky++) {
|
||||||
|
for (let kx = -allowanceKernelRadius; kx <= allowanceKernelRadius; kx++) {
|
||||||
|
const dx = kx * resolution
|
||||||
|
const dy = ky * resolution
|
||||||
|
const dist = Math.sqrt(dx * dx + dy * dy)
|
||||||
|
if (dist <= allowanceRadius) {
|
||||||
|
allowanceKernel.push({ kx, ky })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const kernelData = new ArrayBuffer(kernel.length * 16)
|
const kernelData = new ArrayBuffer(kernel.length * 16)
|
||||||
const kernelView = new DataView(kernelData)
|
const kernelView = new DataView(kernelData)
|
||||||
kernel.forEach((k, i) => {
|
kernel.forEach((k, i) => {
|
||||||
@@ -278,12 +298,68 @@ export async function runWebGPUConvolution(depthMap, gridSize, resolution, toolT
|
|||||||
const convolutionMap = await runPass('convolutionMain', inputBuffer, outputBuffer)
|
const convolutionMap = await runPass('convolutionMain', inputBuffer, outputBuffer)
|
||||||
if (onProgress) onProgress({ type: 'progress', progress: 35, iteration: 1 })
|
if (onProgress) onProgress({ type: 'progress', progress: 35, iteration: 1 })
|
||||||
|
|
||||||
|
let toolpathMap
|
||||||
|
if (isFlatTool) {
|
||||||
|
toolpathMap = new Float32Array(convolutionMap.length)
|
||||||
|
for (let gy = 0; gy < gridSize; gy++) {
|
||||||
|
for (let gx = 0; gx < gridSize; gx++) {
|
||||||
|
const centerIdx = gy * gridSize + gx
|
||||||
|
const currentZ = convolutionMap[centerIdx]
|
||||||
|
if (currentZ < -49.9) {
|
||||||
|
toolpathMap[centerIdx] = EMPTY_VALUE
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let maxZ = currentZ
|
||||||
|
for (const { kx, ky } of allowanceKernel) {
|
||||||
|
const nx = gx + kx
|
||||||
|
const ny = gy + ky
|
||||||
|
if (nx < 0 || nx >= gridSize || ny < 0 || ny >= gridSize) continue
|
||||||
|
const neighborIdx = ny * gridSize + nx
|
||||||
|
const neighborZ = convolutionMap[neighborIdx]
|
||||||
|
if (neighborZ > maxZ) {
|
||||||
|
maxZ = neighborZ
|
||||||
|
}
|
||||||
|
}
|
||||||
|
toolpathMap[centerIdx] = maxZ
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
device.queue.writeBuffer(inputBuffer, 0, convolutionMap)
|
device.queue.writeBuffer(inputBuffer, 0, convolutionMap)
|
||||||
const toolpathMap = await runPass('toolpathMain', inputBuffer, outputBuffer)
|
toolpathMap = await runPass('toolpathMain', inputBuffer, outputBuffer)
|
||||||
|
}
|
||||||
if (onProgress) onProgress({ type: 'progress', progress: 50, iteration: 1 })
|
if (onProgress) onProgress({ type: 'progress', progress: 50, iteration: 1 })
|
||||||
|
|
||||||
|
let repairedMap
|
||||||
|
if (isFlatTool) {
|
||||||
|
repairedMap = new Float32Array(toolpathMap.length)
|
||||||
|
for (let gy = 0; gy < gridSize; gy++) {
|
||||||
|
for (let gx = 0; gx < gridSize; gx++) {
|
||||||
|
const centerIdx = gy * gridSize + gx
|
||||||
|
const toolpathZ = toolpathMap[centerIdx]
|
||||||
|
if (toolpathZ < -49.9) {
|
||||||
|
repairedMap[centerIdx] = EMPTY_VALUE
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let minZ = toolpathZ
|
||||||
|
for (const { kx, ky, profileZ } of kernel) {
|
||||||
|
const nx = gx + kx
|
||||||
|
const ny = gy + ky
|
||||||
|
if (nx < 0 || nx >= gridSize || ny < 0 || ny >= gridSize) continue
|
||||||
|
const neighborIdx = ny * gridSize + nx
|
||||||
|
const neighborToolpathZ = toolpathMap[neighborIdx]
|
||||||
|
if (neighborToolpathZ < -49.9) continue
|
||||||
|
const surfaceZ = neighborToolpathZ - profileZ
|
||||||
|
if (surfaceZ < minZ) {
|
||||||
|
minZ = surfaceZ
|
||||||
|
}
|
||||||
|
}
|
||||||
|
repairedMap[centerIdx] = minZ
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
device.queue.writeBuffer(inputBuffer, 0, convolutionMap)
|
device.queue.writeBuffer(inputBuffer, 0, convolutionMap)
|
||||||
const repairedMap = await runPass('repairedMain', inputBuffer, outputBuffer)
|
repairedMap = await runPass('repairedMain', inputBuffer, outputBuffer)
|
||||||
|
}
|
||||||
if (onProgress) onProgress({ type: 'progress', progress: 95, iteration: 1 })
|
if (onProgress) onProgress({ type: 'progress', progress: 95, iteration: 1 })
|
||||||
|
|
||||||
inputBuffer.destroy()
|
inputBuffer.destroy()
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
|
const MAX_CHUNK_SIZE = 2000
|
||||||
|
|
||||||
self.onmessage = function(e) {
|
self.onmessage = function(e) {
|
||||||
const { depthMap, gridSize, resolution, toolType, toolRadius, toolAngle, tipRadius } = e.data
|
const { depthMap, gridSize, resolution, toolType, toolRadius, toolAngle, tipRadius, allowance } = e.data
|
||||||
|
|
||||||
const inputMap = new Float32Array(depthMap)
|
const isFlatTool = toolType === 'flat18' || toolType === 'flat14'
|
||||||
const convolutionMap = new Float32Array(depthMap.length)
|
const allowanceRadius = isFlatTool ? (toolRadius / 2 + allowance) : 0
|
||||||
convolutionMap.fill(-Infinity)
|
const allowanceKernelRadius = isFlatTool ? Math.ceil(allowanceRadius / resolution) : 0
|
||||||
const repairedMap = new Float32Array(depthMap.length)
|
|
||||||
repairedMap.fill(-Infinity)
|
|
||||||
|
|
||||||
const kernelRadius = toolType === 'ball'
|
const kernelRadius = Math.ceil(toolRadius / resolution)
|
||||||
? Math.ceil(toolRadius / resolution)
|
|
||||||
: Math.ceil(toolRadius / resolution)
|
|
||||||
|
|
||||||
const computeToolProfile = (dist) => {
|
const computeToolProfile = (dist) => {
|
||||||
if (toolType === 'ball') {
|
if (toolType === 'ball') {
|
||||||
@@ -17,6 +15,8 @@ self.onmessage = function(e) {
|
|||||||
return Math.sqrt(toolRadius * toolRadius - dist * dist)
|
return Math.sqrt(toolRadius * toolRadius - dist * dist)
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
|
} else if (toolType === 'flat18' || toolType === 'flat14') {
|
||||||
|
return 0
|
||||||
} else {
|
} else {
|
||||||
const halfAngle = toolAngle * Math.PI / 180 / 2
|
const halfAngle = toolAngle * Math.PI / 180 / 2
|
||||||
const tanHalf = Math.tan(halfAngle)
|
const tanHalf = Math.tan(halfAngle)
|
||||||
@@ -45,19 +45,94 @@ self.onmessage = function(e) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const kernel = precomputeKernel()
|
const kernel = precomputeKernel()
|
||||||
|
const centerProfileZ = kernel.find(k => k.kx === 0 && k.ky === 0)?.profileZ || 0
|
||||||
|
|
||||||
let processedCount = 0
|
const precomputeAllowanceKernel = () => {
|
||||||
const totalPixels = gridSize * gridSize
|
if (!isFlatTool) return null
|
||||||
|
const allowanceKernel = []
|
||||||
|
for (let ky = -allowanceKernelRadius; ky <= allowanceKernelRadius; ky++) {
|
||||||
|
for (let kx = -allowanceKernelRadius; kx <= allowanceKernelRadius; kx++) {
|
||||||
|
const dx = kx * resolution
|
||||||
|
const dy = ky * resolution
|
||||||
|
const dist = Math.sqrt(dx * dx + dy * dy)
|
||||||
|
if (dist <= allowanceRadius) {
|
||||||
|
allowanceKernel.push({ kx, ky })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return allowanceKernel
|
||||||
|
}
|
||||||
|
|
||||||
for (let gy = 0; gy < gridSize; gy++) {
|
const allowanceKernel = precomputeAllowanceKernel()
|
||||||
for (let gx = 0; gx < gridSize; gx++) {
|
|
||||||
const centerIdx = gy * gridSize + gx
|
const runAllowanceConvolution = (inputMap, gs) => {
|
||||||
|
if (!isFlatTool) return inputMap
|
||||||
|
const outputMap = new Float32Array(gs * gs)
|
||||||
|
outputMap.fill(-Infinity)
|
||||||
|
|
||||||
|
for (let gy = 0; gy < gs; gy++) {
|
||||||
|
for (let gx = 0; gx < gs; gx++) {
|
||||||
|
const centerIdx = gy * gs + gx
|
||||||
|
const currentZ = inputMap[centerIdx]
|
||||||
|
if (currentZ === -Infinity) continue
|
||||||
|
|
||||||
|
let maxZ = currentZ
|
||||||
|
for (const { kx, ky } of allowanceKernel) {
|
||||||
|
const nx = gx + kx
|
||||||
|
const ny = gy + ky
|
||||||
|
if (nx < 0 || nx >= gs || ny < 0 || ny >= gs) continue
|
||||||
|
const neighborIdx = ny * gs + nx
|
||||||
|
const neighborZ = inputMap[neighborIdx]
|
||||||
|
if (neighborZ > maxZ) {
|
||||||
|
maxZ = neighborZ
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outputMap[centerIdx] = maxZ
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return outputMap
|
||||||
|
}
|
||||||
|
|
||||||
|
const runFlatToolpathRepaired = (toolpathMap, gs) => {
|
||||||
|
if (!isFlatTool) return toolpathMap
|
||||||
|
const outputMap = new Float32Array(gs * gs)
|
||||||
|
outputMap.fill(-Infinity)
|
||||||
|
|
||||||
|
for (let gy = 0; gy < gs; gy++) {
|
||||||
|
for (let gx = 0; gx < gs; gx++) {
|
||||||
|
const centerIdx = gy * gs + gx
|
||||||
|
const toolpathZ = toolpathMap[centerIdx]
|
||||||
|
if (toolpathZ === -Infinity) continue
|
||||||
|
|
||||||
|
let minZ = toolpathZ
|
||||||
|
for (const { kx, ky, profileZ } of kernel) {
|
||||||
|
const nx = gx + kx
|
||||||
|
const ny = gy + ky
|
||||||
|
if (nx < 0 || nx >= gs || ny < 0 || ny >= gs) continue
|
||||||
|
const neighborIdx = ny * gs + nx
|
||||||
|
const neighborToolpathZ = toolpathMap[neighborIdx]
|
||||||
|
if (neighborToolpathZ === -Infinity) continue
|
||||||
|
const surfaceZ = neighborToolpathZ - profileZ
|
||||||
|
if (surfaceZ < minZ) {
|
||||||
|
minZ = surfaceZ
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outputMap[centerIdx] = minZ
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return outputMap
|
||||||
|
}
|
||||||
|
|
||||||
|
const runConvolution = (inputMap, gs) => {
|
||||||
|
const outputMap = new Float32Array(gs * gs)
|
||||||
|
outputMap.fill(-Infinity)
|
||||||
|
|
||||||
|
for (let gy = 0; gy < gs; gy++) {
|
||||||
|
for (let gx = 0; gx < gs; gx++) {
|
||||||
|
const centerIdx = gy * gs + gx
|
||||||
const currentZ = inputMap[centerIdx]
|
const currentZ = inputMap[centerIdx]
|
||||||
|
|
||||||
if (currentZ === -Infinity) {
|
if (currentZ === -Infinity) continue
|
||||||
processedCount++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
let convolutionZ = currentZ
|
let convolutionZ = currentZ
|
||||||
|
|
||||||
@@ -65,9 +140,9 @@ self.onmessage = function(e) {
|
|||||||
const nx = gx + kx
|
const nx = gx + kx
|
||||||
const ny = gy + ky
|
const ny = gy + ky
|
||||||
|
|
||||||
if (nx < 0 || nx >= gridSize || ny < 0 || ny >= gridSize) continue
|
if (nx < 0 || nx >= gs || ny < 0 || ny >= gs) continue
|
||||||
|
|
||||||
const neighborIdx = ny * gridSize + nx
|
const neighborIdx = ny * gs + nx
|
||||||
const neighborZ = inputMap[neighborIdx]
|
const neighborZ = inputMap[neighborIdx]
|
||||||
if (neighborZ === -Infinity) continue
|
if (neighborZ === -Infinity) continue
|
||||||
|
|
||||||
@@ -77,41 +152,23 @@ self.onmessage = function(e) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
convolutionMap[centerIdx] = convolutionZ
|
outputMap[centerIdx] = convolutionZ
|
||||||
processedCount++
|
|
||||||
|
|
||||||
if (gy % 10 === 0) {
|
|
||||||
const progress = (processedCount / totalPixels) * 33
|
|
||||||
self.postMessage({
|
|
||||||
type: 'progress',
|
|
||||||
progress: Math.min(progress, 32.9),
|
|
||||||
iteration: 1
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const centerProfileZ = kernel.find(k => k.kx === 0 && k.ky === 0)?.profileZ || 0
|
|
||||||
const toolpathMap = new Float32Array(depthMap.length)
|
|
||||||
for (let i = 0; i < depthMap.length; i++) {
|
|
||||||
if (convolutionMap[i] === -Infinity) {
|
|
||||||
toolpathMap[i] = -Infinity
|
|
||||||
} else {
|
|
||||||
toolpathMap[i] = convolutionMap[i] - centerProfileZ
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
processedCount = 0
|
return outputMap
|
||||||
|
}
|
||||||
|
|
||||||
for (let gy = 0; gy < gridSize; gy++) {
|
const runRepaired = (convolutionMap, gs) => {
|
||||||
for (let gx = 0; gx < gridSize; gx++) {
|
const outputMap = new Float32Array(gs * gs)
|
||||||
const centerIdx = gy * gridSize + gx
|
outputMap.fill(-Infinity)
|
||||||
|
|
||||||
|
for (let gy = 0; gy < gs; gy++) {
|
||||||
|
for (let gx = 0; gx < gs; gx++) {
|
||||||
|
const centerIdx = gy * gs + gx
|
||||||
const convolutionZ = convolutionMap[centerIdx]
|
const convolutionZ = convolutionMap[centerIdx]
|
||||||
|
|
||||||
if (convolutionZ === -Infinity) {
|
if (convolutionZ === -Infinity) continue
|
||||||
processedCount++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
let repairedZ = convolutionZ
|
let repairedZ = convolutionZ
|
||||||
|
|
||||||
@@ -119,9 +176,9 @@ self.onmessage = function(e) {
|
|||||||
const nx = gx + kx
|
const nx = gx + kx
|
||||||
const ny = gy + ky
|
const ny = gy + ky
|
||||||
|
|
||||||
if (nx < 0 || nx >= gridSize || ny < 0 || ny >= gridSize) continue
|
if (nx < 0 || nx >= gs || ny < 0 || ny >= gs) continue
|
||||||
|
|
||||||
const neighborIdx = ny * gridSize + nx
|
const neighborIdx = ny * gs + nx
|
||||||
const neighborConvolutionZ = convolutionMap[neighborIdx]
|
const neighborConvolutionZ = convolutionMap[neighborIdx]
|
||||||
if (neighborConvolutionZ === -Infinity) continue
|
if (neighborConvolutionZ === -Infinity) continue
|
||||||
|
|
||||||
@@ -131,19 +188,55 @@ self.onmessage = function(e) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
repairedMap[centerIdx] = repairedZ
|
outputMap[centerIdx] = repairedZ
|
||||||
processedCount++
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (gy % 10 === 0) {
|
return outputMap
|
||||||
const progress = 50 + (processedCount / totalPixels) * 50
|
}
|
||||||
|
|
||||||
|
const runToolpath = (convolutionMap, gs) => {
|
||||||
|
const outputMap = new Float32Array(gs * gs)
|
||||||
|
for (let i = 0; i < gs * gs; i++) {
|
||||||
|
if (convolutionMap[i] === -Infinity) {
|
||||||
|
outputMap[i] = -Infinity
|
||||||
|
} else {
|
||||||
|
outputMap[i] = convolutionMap[i] - centerProfileZ
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return outputMap
|
||||||
|
}
|
||||||
|
|
||||||
|
const inputMap = new Float32Array(depthMap)
|
||||||
|
const totalPixels = gridSize * gridSize
|
||||||
|
let processedCount = 0
|
||||||
|
|
||||||
|
if (gridSize <= MAX_CHUNK_SIZE) {
|
||||||
|
const convolutionMap = runConvolution(inputMap, gridSize)
|
||||||
|
processedCount = totalPixels
|
||||||
self.postMessage({
|
self.postMessage({
|
||||||
type: 'progress',
|
type: 'progress',
|
||||||
progress: Math.min(progress, 99.9),
|
progress: 33,
|
||||||
|
iteration: 1
|
||||||
|
})
|
||||||
|
|
||||||
|
const toolpathMap = isFlatTool
|
||||||
|
? runAllowanceConvolution(convolutionMap, gridSize)
|
||||||
|
: runToolpath(convolutionMap, gridSize)
|
||||||
|
self.postMessage({
|
||||||
|
type: 'progress',
|
||||||
|
progress: 50,
|
||||||
|
iteration: 1
|
||||||
|
})
|
||||||
|
|
||||||
|
const repairedMap = isFlatTool
|
||||||
|
? runFlatToolpathRepaired(toolpathMap, gridSize)
|
||||||
|
: runRepaired(convolutionMap, gridSize)
|
||||||
|
self.postMessage({
|
||||||
|
type: 'progress',
|
||||||
|
progress: 99.9,
|
||||||
iteration: 1
|
iteration: 1
|
||||||
})
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.postMessage({
|
self.postMessage({
|
||||||
type: 'complete',
|
type: 'complete',
|
||||||
@@ -151,5 +244,88 @@ self.onmessage = function(e) {
|
|||||||
toolpathMap,
|
toolpathMap,
|
||||||
repairedMap,
|
repairedMap,
|
||||||
iterations: 1
|
iterations: 1
|
||||||
|
}, [convolutionMap.buffer, toolpathMap.buffer, repairedMap.buffer])
|
||||||
|
} else {
|
||||||
|
const numChunks = 2
|
||||||
|
const chunkSize = Math.ceil(gridSize / numChunks)
|
||||||
|
const overlap = kernelRadius
|
||||||
|
|
||||||
|
const convolutionMap = new Float32Array(totalPixels)
|
||||||
|
convolutionMap.fill(-Infinity)
|
||||||
|
|
||||||
|
for (let cy = 0; cy < numChunks; cy++) {
|
||||||
|
for (let cx = 0; cx < numChunks; cx++) {
|
||||||
|
const startY = cy * chunkSize
|
||||||
|
const startX = cx * chunkSize
|
||||||
|
const endY = Math.min(startY + chunkSize, gridSize)
|
||||||
|
const endX = Math.min(startX + chunkSize, gridSize)
|
||||||
|
|
||||||
|
const chunkH = endY - startY
|
||||||
|
const chunkW = endX - startX
|
||||||
|
const chunkSizeWithOverlap = (chunkH + overlap * 2) * (chunkW + overlap * 2)
|
||||||
|
const chunkInput = new Float32Array(chunkSizeWithOverlap)
|
||||||
|
chunkInput.fill(-Infinity)
|
||||||
|
|
||||||
|
for (let oy = -overlap; oy < chunkH + overlap; oy++) {
|
||||||
|
for (let ox = -overlap; ox < chunkW + overlap; ox++) {
|
||||||
|
const gy = startY + oy
|
||||||
|
const gx = startX + ox
|
||||||
|
if (gy < 0 || gy >= gridSize || gx < 0 || gx >= gridSize) continue
|
||||||
|
|
||||||
|
const chunkIdx = (oy + overlap) * (chunkW + overlap * 2) + (ox + overlap)
|
||||||
|
const globalIdx = gy * gridSize + gx
|
||||||
|
chunkInput[chunkIdx] = inputMap[globalIdx]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const chunkConvolution = runConvolution(chunkInput, chunkW + overlap * 2)
|
||||||
|
|
||||||
|
for (let oy = 0; oy < chunkH; oy++) {
|
||||||
|
for (let ox = 0; ox < chunkW; ox++) {
|
||||||
|
const chunkIdx = (oy + overlap) * (chunkW + overlap * 2) + (ox + overlap)
|
||||||
|
const globalIdx = (startY + oy) * gridSize + (startX + ox)
|
||||||
|
if (chunkConvolution[chunkIdx] > convolutionMap[globalIdx]) {
|
||||||
|
convolutionMap[globalIdx] = chunkConvolution[chunkIdx]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
processedCount += chunkH * chunkW
|
||||||
|
const progress = (processedCount / totalPixels) * 33
|
||||||
|
self.postMessage({
|
||||||
|
type: 'progress',
|
||||||
|
progress: Math.min(progress, 32.9),
|
||||||
|
iteration: 1
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.postMessage({
|
||||||
|
type: 'progress',
|
||||||
|
progress: 33,
|
||||||
|
iteration: 1
|
||||||
|
})
|
||||||
|
|
||||||
|
const toolpathMap = isFlatTool
|
||||||
|
? runAllowanceConvolution(convolutionMap, gridSize)
|
||||||
|
: new Float32Array(totalPixels).map((_, i) => convolutionMap[i] === -Infinity ? -Infinity : convolutionMap[i] - centerProfileZ)
|
||||||
|
|
||||||
|
self.postMessage({
|
||||||
|
type: 'progress',
|
||||||
|
progress: 50,
|
||||||
|
iteration: 1
|
||||||
|
})
|
||||||
|
|
||||||
|
const repairedMap = isFlatTool
|
||||||
|
? runFlatToolpathRepaired(toolpathMap, gridSize)
|
||||||
|
: runRepaired(convolutionMap, gridSize)
|
||||||
|
|
||||||
|
self.postMessage({
|
||||||
|
type: 'complete',
|
||||||
|
convolutionMap,
|
||||||
|
toolpathMap,
|
||||||
|
repairedMap,
|
||||||
|
iterations: 1
|
||||||
|
}, [convolutionMap.buffer, toolpathMap.buffer, repairedMap.buffer])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user