[READ-ONLY] Mirror of https://github.com/flo-bit/shapecraft. flo-bit.dev/shapecraft/
0

Configure Feed

Select the types of activity you want to include in your feed.

shapecraft / src / ops / thicken.ts
4.0 kB 122 lines
1import * as THREE from 'three' 2import { Mesh } from '../core/mesh' 3 4/** 5 * Give a mesh thickness by duplicating it, offsetting along normals, 6 * and stitching boundary edges. 7 */ 8export function thicken(mesh: Mesh, thickness: number): Mesh { 9 let geo = mesh.geometry.clone() 10 // Keep indexed if possible — we need shared edge info 11 if (!geo.getIndex()) { 12 // Non-indexed: can't reliably find boundary edges, just do top+bottom 13 return thickenSimple(geo, thickness) 14 } 15 16 geo.computeVertexNormals() 17 const pos = geo.getAttribute('position') 18 const norm = geo.getAttribute('normal') 19 const index = geo.getIndex()! 20 const vertCount = pos.count 21 const half = thickness / 2 22 23 // Build new positions: top (offset +normal) then bottom (offset -normal) 24 const newPos = new Float32Array(vertCount * 2 * 3) 25 for (let i = 0; i < vertCount; i++) { 26 const nx = norm.getX(i), ny = norm.getY(i), nz = norm.getZ(i) 27 // Top 28 newPos[i * 3] = pos.getX(i) + nx * half 29 newPos[i * 3 + 1] = pos.getY(i) + ny * half 30 newPos[i * 3 + 2] = pos.getZ(i) + nz * half 31 // Bottom 32 const bi = (i + vertCount) * 3 33 newPos[bi] = pos.getX(i) - nx * half 34 newPos[bi + 1] = pos.getY(i) - ny * half 35 newPos[bi + 2] = pos.getZ(i) - nz * half 36 } 37 38 const indexArr = index.array 39 const triCount = indexArr.length / 3 40 const newIndices: number[] = [] 41 42 // Top faces (same winding) 43 for (let i = 0; i < indexArr.length; i++) { 44 newIndices.push(indexArr[i]) 45 } 46 47 // Bottom faces (reversed winding) 48 for (let t = 0; t < triCount; t++) { 49 const i = t * 3 50 newIndices.push( 51 indexArr[i] + vertCount, 52 indexArr[i + 2] + vertCount, 53 indexArr[i + 1] + vertCount, 54 ) 55 } 56 57 // Find boundary edges: edges that appear in only one triangle 58 const edgeCount = new Map<string, { a: number; b: number; count: number }>() 59 for (let t = 0; t < triCount; t++) { 60 const i = t * 3 61 const v0 = indexArr[i], v1 = indexArr[i + 1], v2 = indexArr[i + 2] 62 for (const [a, b] of [[v0, v1], [v1, v2], [v2, v0]] as [number, number][]) { 63 const key = a < b ? `${a},${b}` : `${b},${a}` 64 const existing = edgeCount.get(key) 65 if (existing) { 66 existing.count++ 67 } else { 68 edgeCount.set(key, { a, b, count: 1 }) 69 } 70 } 71 } 72 73 // Stitch boundary edges with side quads 74 for (const { a, b, count } of edgeCount.values()) { 75 if (count === 1) { 76 const a2 = a + vertCount 77 const b2 = b + vertCount 78 newIndices.push(a, b2, b) 79 newIndices.push(a, a2, b2) 80 } 81 } 82 83 const result = new THREE.BufferGeometry() 84 result.setAttribute('position', new THREE.BufferAttribute(newPos, 3)) 85 result.setIndex(newIndices) 86 result.computeVertexNormals() 87 return new Mesh(result) 88} 89 90/** Fallback for non-indexed geometry: just top + bottom, no side stitching */ 91function thickenSimple(geo: THREE.BufferGeometry, thickness: number): Mesh { 92 geo.computeVertexNormals() 93 const pos = geo.getAttribute('position') 94 const norm = geo.getAttribute('normal') 95 const vertCount = pos.count 96 const half = thickness / 2 97 98 const newPos: number[] = [] 99 const indices: number[] = [] 100 101 for (let i = 0; i < vertCount; i++) { 102 const nx = norm.getX(i), ny = norm.getY(i), nz = norm.getZ(i) 103 newPos.push(pos.getX(i) + nx * half, pos.getY(i) + ny * half, pos.getZ(i) + nz * half) 104 } 105 for (let i = 0; i < vertCount; i++) { 106 const nx = norm.getX(i), ny = norm.getY(i), nz = norm.getZ(i) 107 newPos.push(pos.getX(i) - nx * half, pos.getY(i) - ny * half, pos.getZ(i) - nz * half) 108 } 109 110 const triCount = vertCount / 3 111 for (let t = 0; t < triCount; t++) { 112 const i = t * 3 113 indices.push(i, i + 1, i + 2) 114 indices.push(i + vertCount, i + 2 + vertCount, i + 1 + vertCount) 115 } 116 117 const result = new THREE.BufferGeometry() 118 result.setAttribute('position', new THREE.BufferAttribute(new Float32Array(newPos), 3)) 119 result.setIndex(indices) 120 result.computeVertexNormals() 121 return new Mesh(result) 122}