(shape: THREE.Shape, offset: number)
| 10 | * @returns 膨胀后的 THREE.Shape 数组 |
| 11 | */ |
| 12 | export const offsetShape = (shape: THREE.Shape, offset: number): THREE.Shape[] => { |
| 13 | // 1. 转换主轮廓 |
| 14 | const clipperPaths: ClipperLib.Paths = []; |
| 15 | const mainPath: ClipperLib.Path = shape.getPoints().map(p => ({ |
| 16 | X: Math.round(p.x * OFFSET_SCALE), |
| 17 | Y: Math.round(p.y * OFFSET_SCALE) |
| 18 | })); |
| 19 | clipperPaths.push(mainPath); |
| 20 | |
| 21 | // 2. 转换所有孔洞 |
| 22 | if (shape.holes && shape.holes.length > 0) { |
| 23 | shape.holes.forEach(hole => { |
| 24 | const holePath: ClipperLib.Path = hole.getPoints().map(p => ({ |
| 25 | X: Math.round(p.x * OFFSET_SCALE), |
| 26 | Y: Math.round(p.y * OFFSET_SCALE) |
| 27 | })); |
| 28 | clipperPaths.push(holePath); |
| 29 | }); |
| 30 | } |
| 31 | |
| 32 | // 3. 执行偏移操作 |
| 33 | const co = new ClipperLib.ClipperOffset(); |
| 34 | co.AddPaths(clipperPaths, ClipperLib.JoinType.jtMiter, ClipperLib.EndType.etClosedPolygon); |
| 35 | |
| 36 | const solution: ClipperLib.Paths = []; |
| 37 | co.Execute(solution, offset * OFFSET_SCALE); |
| 38 | |
| 39 | // 4. 将结果转回 THREE.Shape |
| 40 | return solution.map(path => { |
| 41 | const points = path.map(p => new THREE.Vector2(p.X / OFFSET_SCALE, p.Y / OFFSET_SCALE)); |
| 42 | return new THREE.Shape(points); |
| 43 | }); |
| 44 | }; |
no outgoing calls
no test coverage detected