diff --git a/src/lib/components/App.svelte b/src/lib/components/App.svelte index 758e406..bd1bfc5 100644 --- a/src/lib/components/App.svelte +++ b/src/lib/components/App.svelte @@ -1,8 +1,22 @@ - + + +
+ +
+ + diff --git a/src/lib/components/Scene.svelte b/src/lib/components/Scene.svelte index 6ad2be5..8336bdf 100644 --- a/src/lib/components/Scene.svelte +++ b/src/lib/components/Scene.svelte @@ -10,31 +10,34 @@ Mesh, DoubleSide, Color, - Box3, - Matrix4 + Plane, + Line3, + Float32BufferAttribute, + Box3 } from 'three'; import { ExtendedTriangle, MeshBVH } from 'three-mesh-bvh'; import type { Readable } from 'svelte/store'; - import type { HitPointInfo } from 'three-mesh-bvh'; - import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js'; export let buildSurface = [300, 300, 300]; export let layerHeight = 0.2; export let nozzleSize = 0.4; export let tolerance = 0.005; + export let progress = 1; export let maxNonPlanarAngle = MathUtils.degToRad(20); export let bedNormal = new Vector3(0, 0, 1); + export let extruderNormal = new Vector3(0, 0, -1); export let origin = new Vector3(150, 150, 0); const stl: Readable = useLoader(STLLoader).load('/benchy.stl'); let mesh: Mesh; - let layers: BufferGeometry[] = []; + let layers: { type: 'line' | 'surface'; geometry: BufferGeometry }[] = []; $: if ($stl) { const bvh = new MeshBVH($stl); const positions = $stl.getAttribute('position'); + const normals = $stl.getAttribute('normal'); const index = $stl.index!; const qualifyingTriangles = Array.from({ length: index.count / 3 }, () => false); @@ -50,11 +53,14 @@ ); triangle.getNormal(normal); const angle = normal.angleTo(bedNormal); - if ((angle > Math.PI / 2 ? Math.PI - angle : angle) < maxNonPlanarAngle) { + // TODO: bottom layers + if (angle < maxNonPlanarAngle) { qualifyingTriangles[i] = true; qualifyingTrianglesCount++; } } + const includedTriangles = [...qualifyingTriangles]; + const includedTrianglesCount = qualifyingTrianglesCount; const surfaces: number[][] = []; while (qualifyingTrianglesCount > 0) { @@ -92,10 +98,10 @@ surfaces.push(surface); } - layers = surfaces.map((surface) => { + const nonPlanarSurfaces = surfaces.map((surface) => { const geometry = new BufferGeometry(); geometry.setAttribute('position', positions); - geometry.setAttribute('normal', $stl.getAttribute('normal')); + geometry.setAttribute('normal', normals); const indices: number[] = Array.from({ length: surface.length * 3 }); for (let i = 0; i < surface.length; i++) { const pos = surface[i] * 3; @@ -104,8 +110,104 @@ indices[i * 3 + 2] = $stl.index!.array[pos + 2]; } geometry.setIndex(indices); - return geometry; + return new MeshBVH(geometry); }); + const activeNonPlanarSurfaces: [number, MeshBVH][] = []; + const consumedNonPlanarSurfaces = nonPlanarSurfaces.map(() => false); + const withheldLines: number[][][] = nonPlanarSurfaces.map(() => [[]]); + const blacklist = Array.from({ length: index.count / 3 }).map(() => false); + + const line = new Line3(); + const targetVector1 = new Vector3(); + const targetVector2 = new Vector3(); + const targetVector3 = new Vector3(); + const layerPlane = new Plane(); + const layerBox = new Box3(); + for (let layer = 0; layer < $stl.boundingBox!.max.z; layer += layerHeight) { + layerPlane.set(bedNormal, -layer); + layerBox.set( + new Vector3(0, 0, layer), + new Vector3(buildSurface[0], buildSurface[1], layer + layerHeight) + ); + const layerGeometry = new BufferGeometry(); + const positions: number[] = []; + for (let i = 0; i < nonPlanarSurfaces.length; i++) { + if (consumedNonPlanarSurfaces[i]) continue; + if (layerBox.intersectsBox(nonPlanarSurfaces[i].geometry.boundingBox!)) { + activeNonPlanarSurfaces.push([i, nonPlanarSurfaces[i]]); + consumedNonPlanarSurfaces[i] = true; + } + } + for (let i = 0; i < activeNonPlanarSurfaces.length; i++) { + const [index, surface] = activeNonPlanarSurfaces[i]; + withheldLines[index].push([]); + if (!layerBox.intersectsBox(surface.geometry.boundingBox!)) { + activeNonPlanarSurfaces.splice(i, 1); + i--; + layers.push({ type: 'surface', geometry: surface.geometry }); + for (const lines of withheldLines[index]) { + if (lines.length === 0) continue; + const additionalGeometry = new BufferGeometry(); + additionalGeometry.setAttribute('position', new Float32BufferAttribute(lines, 3)); + layers.push({ type: 'line', geometry: additionalGeometry }); + } + delete withheldLines[index]; + } + } + + bvh.shapecast({ + intersectsBounds(box, _isLeaf, _score, _depth, _nodeIndex) { + return layerPlane.intersectsBox(box); + }, + intersectsTriangle(target, triangleIndex, _contained, _depth) { + if (includedTriangles[triangleIndex] || blacklist[triangleIndex]) return; + function intersect(a: Vector3, b: Vector3, targetVector: Vector3) { + line.set(a, b); + return layerPlane.intersectLine(line, targetVector); + } + const a = intersect(target.a, target.b, targetVector1); + const b = intersect(target.b, target.c, targetVector2); + const c = intersect(target.c, target.a, targetVector3); + + function add(a: Vector3, b: Vector3) { + for (let i = 0; i < activeNonPlanarSurfaces.length; i++) { + const [index, surface] = activeNonPlanarSurfaces[i]; + const h1 = surface.closestPointToPoint(a); + if ( + h1 && + h1.point.z < a.z && + h1.point.clone().sub(a).angleTo(bedNormal) > maxNonPlanarAngle + ) { + withheldLines[index].at(-1)!.push(a.x, a.y, a.z, b.x, b.y, b.z); + return; + } + const h2 = surface.closestPointToPoint(b); + if ( + h2 && + h2.point.z < b.z && + h2.point.clone().sub(b).angleTo(bedNormal) > maxNonPlanarAngle + ) { + withheldLines[index].at(-1)!.push(a.x, a.y, a.z, b.x, b.y, b.z); + return; + } + } + positions.push(a.x, a.y, a.z, b.x, b.y, b.z); + } + + if (a && b) { + add(a, b); + } else if (b && c) { + add(b, c); + } else if (c && a) { + add(c, a); + } + } + }); + layerGeometry.setAttribute('position', new Float32BufferAttribute(positions, 3)); + layers.push({ type: 'line', geometry: layerGeometry }); + console.log('layer', Math.round(layer / layerHeight), positions.length); + } + layers = [...layers]; } @@ -126,10 +228,18 @@ gridSize={[buildSurface[0], buildSurface[1]]} /> -{#each layers as surface} - - - +{#each layers as { geometry, type }, i} + {@const visible = progress >= i / layers.length} + {@const color = new Color(0, i / layers.length, 0.2)} + {#if type === 'line'} + + + + {:else if type === 'surface'} + + + + {/if} {/each} {#if $stl && false}