up
This commit is contained in:
parent
1a66dce5c4
commit
7f4948fa03
13 changed files with 456 additions and 623 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
cmake_minimum_required(VERSION 3.18)
|
cmake_minimum_required(VERSION 3.18)
|
||||||
project(cudaslicer LANGUAGES C CXX CUDA)
|
project(cudaslicer VERSION 1.0.0 LANGUAGES C CXX CUDA)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
|
||||||
5
Makefile
5
Makefile
|
|
@ -45,4 +45,7 @@ $(BUILD_DIR) $(LIB_DIR) $(BIN_DIR):
|
||||||
clean:
|
clean:
|
||||||
rm -rf $(BUILD_DIR) $(LIB_DIR) $(BIN_DIR)
|
rm -rf $(BUILD_DIR) $(LIB_DIR) $(BIN_DIR)
|
||||||
|
|
||||||
.PHONY: all clean
|
wheel:
|
||||||
|
python3 setup.py bdist_wheel
|
||||||
|
|
||||||
|
.PHONY: all clean wheel
|
||||||
|
|
|
||||||
74
README.md
74
README.md
|
|
@ -141,6 +141,80 @@ perims, infill_mask = layer_0
|
||||||
print(f"Layer 0 (Z = {layer_0.z} mm): {len(perims)} perimeters, infill mask: {infill_mask.shape}")
|
print(f"Layer 0 (Z = {layer_0.z} mm): {len(perims)} perimeters, infill mask: {infill_mask.shape}")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### E. GPU Signed Distance Fields (SDF), Boolean CSG & Gyroid Infill
|
||||||
|
Execute boolean CSG operations (Union, Difference, Intersection, Smooth Blending) and 3D procedural infill directly on GPU SDF stacks with zero CPU memory roundtrips:
|
||||||
|
|
||||||
|
```python
|
||||||
|
import trimesh
|
||||||
|
import cudaslicer
|
||||||
|
|
||||||
|
mesh_a = trimesh.load("benchy.stl")
|
||||||
|
cutter_box = trimesh.creation.box(extents=(12.0, 60.0, 25.0))
|
||||||
|
|
||||||
|
# 1. Direct GPU slice to continuous SDF stacks
|
||||||
|
sdf_a = cudaslicer.slice_mesh_to_sdf(mesh_a, layer_height=0.20, pitch_mm=0.10)
|
||||||
|
sdf_b = cudaslicer.slice_mesh_to_sdf(cutter_box, layer_height=0.20, pitch_mm=0.10)
|
||||||
|
|
||||||
|
# 2. Boolean CSG subtraction entirely in GPU VRAM
|
||||||
|
sdf_carved = sdf_a.difference(sdf_b.offset(delta_mm=0.5))
|
||||||
|
|
||||||
|
# 3. GPU procedural 3D gyroid infill generation
|
||||||
|
sdf_infill = sdf_carved.generate_infill(pattern="gyroid", cell_size_mm=4.0, density=0.25)
|
||||||
|
|
||||||
|
# 4. Direct vectorization with sub-pixel LERP and RDP polyline simplification
|
||||||
|
layers = sdf_infill.vectorize(
|
||||||
|
perimeters_count=2,
|
||||||
|
layer_width=0.45,
|
||||||
|
subpixel_lerp=True, # Natural arbitrary angles (no 45° quantization)
|
||||||
|
simplify_tolerance_mm=0.02 # RDP polyline decimation
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📐 Sub-Pixel Linear Interpolation (LERP) vs. Standard Marching Squares
|
||||||
|
|
||||||
|
### The Problem: 45° & 90° Angle Quantization in Standard Marching Squares
|
||||||
|
Standard discrete Marching Squares (and binary mask polygon extractors) evaluates binary cell states (0 or 1 at each voxel corner). When an edge intersects a boundary, it snaps the intersection point to the **geometric midpoint** of the grid cell edge ($t = 0.5$):
|
||||||
|
|
||||||
|
```
|
||||||
|
Discrete Marching Squares (Snap to t = 0.5):
|
||||||
|
(x, y+1) +---------+ (x+1, y+1)
|
||||||
|
| * | <- Midpoint snapping forces line segments to connect
|
||||||
|
| / | only at (0.5, 0), (0, 0.5), (1, 0.5), etc.
|
||||||
|
| * | <- Result: angles restricted to 0°, 45°, 90°, 135°!
|
||||||
|
(x, y) +---------+ (x+1, y)
|
||||||
|
```
|
||||||
|
|
||||||
|
Because segment vectors between grid edge midpoints are strictly constrained to Cartesian diagonals, diagonal walls and curves are quantized into jagged 45° chamfers and stair-stepped facets.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### The Solution: Continuous SDF Linear Interpolation (LERP)
|
||||||
|
In CUDASlicer, the vectorizer uses continuous floating-point Signed Distance Field (SDF) values ($d_0, d_1$) at the cell vertices to solve for the exact sub-pixel boundary crossing where the distance field equals the target isovalue ($iso\_val = 0.0$ for outer perimeter, $-d$ for offset contours):
|
||||||
|
|
||||||
|
$$t = \frac{iso\_val - d_0}{d_1 - d_0}, \quad t \in [0.0, 1.0]$$
|
||||||
|
|
||||||
|
```
|
||||||
|
Continuous SDF LERP (Sub-Pixel Placement):
|
||||||
|
(x, y+1) +---------+ (x+1, y+1)
|
||||||
|
| |
|
||||||
|
| * | <- Exact root crossing t = (0 - d0) / (d1 - d0)
|
||||||
|
| / | <- Arbitrary natural angles (e.g. 12.3°, 37.8°, smooth curves)
|
||||||
|
(x, y) +-----*---+ (x+1, y)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Horizontal Edge:** $P_x = \text{min\_x} + (x + t) \cdot \text{pitch}, \quad P_y = \text{min\_y} + y \cdot \text{pitch}$
|
||||||
|
- **Vertical Edge:** $P_x = \text{min\_x} + x \cdot \text{pitch}, \quad P_y = \text{min\_y} + (y + t) \cdot \text{pitch}$
|
||||||
|
|
||||||
|
Since $t$ varies continuously in $[0, 1]$, vertices fall at their true Euclidean geometric boundaries, eliminating jagged 45° artifacts.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Polyline Simplification & Decimation
|
||||||
|
1. **On-the-Fly Collinear Decimation:** During loop traversal, collinear vertices ($\Delta x_1 \Delta y_2 - \Delta y_1 \Delta x_2 \approx 0$) along straight spans are pruned on the fly with zero memory overhead.
|
||||||
|
2. **Ramer-Douglas-Peucker (RDP) Polyline Simplification:** Traversed closed loops are post-processed with `simplify_contour_rdp(loop, tolerance_mm)` (default tolerance `0.02 mm`), removing sub-pixel jitter while preserving smooth curved contours and exact sharp corners.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🛠️ Building Standalone C/C++ Libraries (Windows & Linux)
|
## 🛠️ Building Standalone C/C++ Libraries (Windows & Linux)
|
||||||
|
|
|
||||||
16
build.sh
16
build.sh
|
|
@ -36,7 +36,7 @@ NC='\033[0m' # No Color
|
||||||
DO_CLEAN=false
|
DO_CLEAN=false
|
||||||
DO_TEST=false
|
DO_TEST=false
|
||||||
DO_INSTALL=false
|
DO_INSTALL=false
|
||||||
DO_WHEEL=false
|
DO_WHEEL=true
|
||||||
BUILD_TYPE="Release"
|
BUILD_TYPE="Release"
|
||||||
BUILD_C_ONLY=false
|
BUILD_C_ONLY=false
|
||||||
NUM_JOBS=$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)
|
NUM_JOBS=$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)
|
||||||
|
|
@ -60,8 +60,13 @@ while [[ $# -gt 0 ]]; do
|
||||||
DO_WHEEL=true
|
DO_WHEEL=true
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
|
--no-wheel)
|
||||||
|
DO_WHEEL=false
|
||||||
|
shift
|
||||||
|
;;
|
||||||
--c-only)
|
--c-only)
|
||||||
BUILD_C_ONLY=true
|
BUILD_C_ONLY=true
|
||||||
|
DO_WHEEL=false
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
--debug)
|
--debug)
|
||||||
|
|
@ -84,7 +89,8 @@ while [[ $# -gt 0 ]]; do
|
||||||
echo " --clean Remove build directory, libraries, and binaries first"
|
echo " --clean Remove build directory, libraries, and binaries first"
|
||||||
echo " --test Run unit tests and benchmarks upon completion"
|
echo " --test Run unit tests and benchmarks upon completion"
|
||||||
echo " --install Install Python package into active environment via pip"
|
echo " --install Install Python package into active environment via pip"
|
||||||
echo " --wheel Build standalone Python .whl distribution package in dist/"
|
echo " --wheel Build standalone Python .whl distribution package in dist/ (default: enabled)"
|
||||||
|
echo " --no-wheel Skip building Python .whl distribution package"
|
||||||
echo " --c-only Build only C/C++ libraries (lib/ and bin/)"
|
echo " --c-only Build only C/C++ libraries (lib/ and bin/)"
|
||||||
echo " --debug Compile with Debug flags"
|
echo " --debug Compile with Debug flags"
|
||||||
echo " --release Compile with Release flags (default)"
|
echo " --release Compile with Release flags (default)"
|
||||||
|
|
@ -173,9 +179,9 @@ if [ "$BUILD_C_ONLY" = false ]; then
|
||||||
echo -e " * Python Backend: ${CYAN}python/cudaslicer/_cudaslicer_backend*.so${NC}"
|
echo -e " * Python Backend: ${CYAN}python/cudaslicer/_cudaslicer_backend*.so${NC}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Optional Python Wheel Package Build
|
# Python Wheel Package Build
|
||||||
if [ "$DO_WHEEL" = true ]; then
|
if [ "$DO_WHEEL" = true ]; then
|
||||||
echo -e "\n${BLUE}[Optional] Building Python Wheel distribution (.whl)...${NC}"
|
echo -e "\n${BLUE}[5/5] Building Python Wheel package (.whl)...${NC}"
|
||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
python3 setup.py bdist_wheel
|
python3 setup.py bdist_wheel
|
||||||
WHL_FILE=$(ls -t dist/*.whl 2>/dev/null | head -n 1 || true)
|
WHL_FILE=$(ls -t dist/*.whl 2>/dev/null | head -n 1 || true)
|
||||||
|
|
@ -193,7 +199,7 @@ fi
|
||||||
|
|
||||||
# Optional Tests & Benchmarks
|
# Optional Tests & Benchmarks
|
||||||
if [ "$DO_TEST" = true ]; then
|
if [ "$DO_TEST" = true ]; then
|
||||||
echo -e "\n${BLUE}[5/5] Running Test Suite & Benchmarks...${NC}"
|
echo -e "\n${BLUE}[Test Suite] Running Tests & Benchmarks...${NC}"
|
||||||
|
|
||||||
echo -e "\n--- [A] Running Python GPU SDF & CSG Unit Tests ---"
|
echo -e "\n--- [A] Running Python GPU SDF & CSG Unit Tests ---"
|
||||||
python3 tests_and_tools/test_gpu_sdf.py
|
python3 tests_and_tools/test_gpu_sdf.py
|
||||||
|
|
|
||||||
|
|
@ -40,16 +40,7 @@ typedef enum {
|
||||||
GPU_SDF_LAYER_MATCH_INTERSECTION = 2 // Output only exact matching layer Z heights
|
GPU_SDF_LAYER_MATCH_INTERSECTION = 2 // Output only exact matching layer Z heights
|
||||||
} GpuSdfLayerMatchMode;
|
} GpuSdfLayerMatchMode;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Procedural 3D Infill Pattern Types
|
|
||||||
*/
|
|
||||||
typedef enum {
|
|
||||||
GPU_SDF_INFILL_SOLID = 0,
|
|
||||||
GPU_SDF_INFILL_GRID = 1,
|
|
||||||
GPU_SDF_INFILL_GYROID = 2,
|
|
||||||
GPU_SDF_INFILL_SCHWARZ_P = 3,
|
|
||||||
GPU_SDF_INFILL_SCHWARZ_D = 4
|
|
||||||
} GpuSdfInfillPattern;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Spatial metadata for a 2D/3D GPU Signed Distance Field stack
|
* @brief Spatial metadata for a 2D/3D GPU Signed Distance Field stack
|
||||||
|
|
@ -180,16 +171,7 @@ GPU_SDF_API GpuSdfStackHandle gpu_sdf_shell(
|
||||||
float thickness_mm
|
float thickness_mm
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Generate a procedural 3D infill pattern intersected with the interior region.
|
|
||||||
*/
|
|
||||||
GPU_SDF_API GpuSdfStackHandle gpu_sdf_generate_infill(
|
|
||||||
GpuSdfStackHandle stack,
|
|
||||||
GpuSdfInfillPattern pattern,
|
|
||||||
float cell_size_mm,
|
|
||||||
float density_ratio,
|
|
||||||
float wall_offset_mm
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Vectorize GPU SDF stack directly to polygon perimeter contours and infill mask.
|
* @brief Vectorize GPU SDF stack directly to polygon perimeter contours and infill mask.
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,20 @@ build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "cudaslicer"
|
name = "cudaslicer"
|
||||||
version = "1.1.0"
|
version = "1.0.0"
|
||||||
description = "GPU-Accelerated 2D/3D Slicing, Signed Distance Fields (SDF) & Polygon Offsetting Library"
|
description = "GPU-Accelerated 2D/3D Slicing, Signed Distance Fields (SDF) & Polygon Offsetting Library"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.8"
|
requires-python = ">=3.8"
|
||||||
|
authors = [
|
||||||
|
{ name = "CUDA Slicer Team" }
|
||||||
|
]
|
||||||
|
classifiers = [
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"Programming Language :: C++",
|
||||||
|
"Operating System :: POSIX :: Linux",
|
||||||
|
"Operating System :: Microsoft :: Windows",
|
||||||
|
"Topic :: Scientific/Engineering",
|
||||||
|
]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"numpy>=1.20.0"
|
"numpy>=1.20.0"
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ with automatic fallback and dynamic backend switching.
|
||||||
from typing import List, Dict, Union, Any, Optional, Sequence, Tuple
|
from typing import List, Dict, Union, Any, Optional, Sequence, Tuple
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
__version__ = "1.1.0"
|
__version__ = "1.0.0"
|
||||||
|
|
||||||
# Try importing the compiled C++/CUDA pybind11 module
|
# Try importing the compiled C++/CUDA pybind11 module
|
||||||
try:
|
try:
|
||||||
|
|
@ -269,7 +269,9 @@ def slice_mesh_and_offset(
|
||||||
pad_mm: float = 1.5,
|
pad_mm: float = 1.5,
|
||||||
return_infill_mask: bool = True,
|
return_infill_mask: bool = True,
|
||||||
offset_distances: Optional[Sequence[float]] = None,
|
offset_distances: Optional[Sequence[float]] = None,
|
||||||
backend: Optional[str] = "auto"
|
backend: Optional[str] = "auto",
|
||||||
|
simplify_tolerance_mm: float = 0.0,
|
||||||
|
subpixel_lerp: bool = True
|
||||||
) -> List[LayerResult]:
|
) -> List[LayerResult]:
|
||||||
"""
|
"""
|
||||||
3D mesh slicing, rasterization, distance field, and perimeter offsetting (CUDA or CPU).
|
3D mesh slicing, rasterization, distance field, and perimeter offsetting (CUDA or CPU).
|
||||||
|
|
@ -296,6 +298,10 @@ def slice_mesh_and_offset(
|
||||||
Custom offset distances.
|
Custom offset distances.
|
||||||
backend : str, optional, default='auto'
|
backend : str, optional, default='auto'
|
||||||
Compute backend: 'auto' (CUDA if available, else CPU), 'cuda' (require CUDA), or 'cpu'.
|
Compute backend: 'auto' (CUDA if available, else CPU), 'cuda' (require CUDA), or 'cpu'.
|
||||||
|
simplify_tolerance_mm : float, default=0.0
|
||||||
|
Ramer-Douglas-Peucker (RDP) polygon simplification tolerance in mm (0.0 to disable).
|
||||||
|
subpixel_lerp : bool, default=True
|
||||||
|
Continuous distance-field sub-pixel linear interpolation for smooth arbitrary angles.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
|
|
@ -338,7 +344,9 @@ def slice_mesh_and_offset(
|
||||||
int(perimeters_count), float(layer_width),
|
int(perimeters_count), float(layer_width),
|
||||||
float(pitch_mm), float(pad_mm),
|
float(pitch_mm), float(pad_mm),
|
||||||
bool(return_infill_mask), offsets_float,
|
bool(return_infill_mask), offsets_float,
|
||||||
backend_str
|
backend_str,
|
||||||
|
float(simplify_tolerance_mm),
|
||||||
|
bool(subpixel_lerp)
|
||||||
)
|
)
|
||||||
|
|
||||||
return [LayerResult(r) for r in raw_results]
|
return [LayerResult(r) for r in raw_results]
|
||||||
|
|
@ -412,15 +420,7 @@ class SdfStack:
|
||||||
"""Hollow / shell the volume with specified wall thickness on GPU."""
|
"""Hollow / shell the volume with specified wall thickness on GPU."""
|
||||||
return SdfStack(self._stack.shell(float(thickness_mm)))
|
return SdfStack(self._stack.shell(float(thickness_mm)))
|
||||||
|
|
||||||
def generate_infill(
|
|
||||||
self,
|
|
||||||
pattern: str = "gyroid",
|
|
||||||
cell_size_mm: float = 5.0,
|
|
||||||
density: float = 0.20,
|
|
||||||
wall_offset_mm: float = 1.0
|
|
||||||
) -> "SdfStack":
|
|
||||||
"""Generate continuous 3D procedural lattice (gyroid, schwarz_p, schwarz_d, grid) on GPU."""
|
|
||||||
return SdfStack(self._stack.generate_infill(str(pattern), float(cell_size_mm), float(density), float(wall_offset_mm)))
|
|
||||||
|
|
||||||
def vectorize(
|
def vectorize(
|
||||||
self,
|
self,
|
||||||
|
|
|
||||||
|
|
@ -252,7 +252,9 @@ py::list py_slice_mesh_and_offset(
|
||||||
float pad_mm,
|
float pad_mm,
|
||||||
bool export_infill_mask,
|
bool export_infill_mask,
|
||||||
const std::vector<float>& offset_distances,
|
const std::vector<float>& offset_distances,
|
||||||
const std::string& backend
|
const std::string& backend,
|
||||||
|
float simplify_tolerance_mm,
|
||||||
|
bool subpixel_lerp
|
||||||
) {
|
) {
|
||||||
py::buffer_info buf = triangles_arr.request();
|
py::buffer_info buf = triangles_arr.request();
|
||||||
size_t num_tris = 0;
|
size_t num_tris = 0;
|
||||||
|
|
@ -275,6 +277,8 @@ py::list py_slice_mesh_and_offset(
|
||||||
config.layer_width = layer_width;
|
config.layer_width = layer_width;
|
||||||
config.first_layer_height = first_layer_height;
|
config.first_layer_height = first_layer_height;
|
||||||
config.export_infill_mask = export_infill_mask ? 1 : 0;
|
config.export_infill_mask = export_infill_mask ? 1 : 0;
|
||||||
|
config.simplify_tolerance_mm = simplify_tolerance_mm;
|
||||||
|
config.subpixel_lerp = subpixel_lerp ? 1 : 0;
|
||||||
config.use_gpu_rasterizer = 1;
|
config.use_gpu_rasterizer = 1;
|
||||||
config.backend = parse_backend_str(backend);
|
config.backend = parse_backend_str(backend);
|
||||||
|
|
||||||
|
|
@ -505,24 +509,6 @@ public:
|
||||||
return std::make_shared<PyGpuSdfStack>(res);
|
return std::make_shared<PyGpuSdfStack>(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<PyGpuSdfStack> generate_infill(
|
|
||||||
const std::string& pattern_str = "gyroid",
|
|
||||||
float cell_size_mm = 5.0f,
|
|
||||||
float density_ratio = 0.20f,
|
|
||||||
float wall_offset_mm = 1.0f
|
|
||||||
) const {
|
|
||||||
GpuSdfInfillPattern pat = GPU_SDF_INFILL_GYROID;
|
|
||||||
if (pattern_str == "gyroid") pat = GPU_SDF_INFILL_GYROID;
|
|
||||||
else if (pattern_str == "schwarz_p" || pattern_str == "schwarz-p") pat = GPU_SDF_INFILL_SCHWARZ_P;
|
|
||||||
else if (pattern_str == "schwarz_d" || pattern_str == "schwarz-d") pat = GPU_SDF_INFILL_SCHWARZ_D;
|
|
||||||
else if (pattern_str == "grid") pat = GPU_SDF_INFILL_GRID;
|
|
||||||
else throw std::invalid_argument("Unknown infill pattern: " + pattern_str);
|
|
||||||
|
|
||||||
GpuSdfStackHandle res = gpu_sdf_generate_infill(handle_, pat, cell_size_mm, density_ratio, wall_offset_mm);
|
|
||||||
if (!res) throw std::runtime_error("SDF infill generation failed.");
|
|
||||||
return std::make_shared<PyGpuSdfStack>(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
py::list vectorize(
|
py::list vectorize(
|
||||||
int perimeters_count = 2,
|
int perimeters_count = 2,
|
||||||
float layer_width = 0.45f,
|
float layer_width = 0.45f,
|
||||||
|
|
@ -760,6 +746,8 @@ PYBIND11_MODULE(_cudaslicer_backend, m) {
|
||||||
py::arg("export_infill_mask") = true,
|
py::arg("export_infill_mask") = true,
|
||||||
py::arg("offset_distances") = std::vector<float>(),
|
py::arg("offset_distances") = std::vector<float>(),
|
||||||
py::arg("backend") = "auto",
|
py::arg("backend") = "auto",
|
||||||
|
py::arg("simplify_tolerance_mm") = 0.0f,
|
||||||
|
py::arg("subpixel_lerp") = true,
|
||||||
"Mesh slicing, perimeter offsetting, and infill mask generation.");
|
"Mesh slicing, perimeter offsetting, and infill mask generation.");
|
||||||
|
|
||||||
// GPU SDF Stack Factory Functions
|
// GPU SDF Stack Factory Functions
|
||||||
|
|
@ -798,12 +786,6 @@ PYBIND11_MODULE(_cudaslicer_backend, m) {
|
||||||
.def("op_difference", &PyGpuSdfStack::op_difference, py::arg("other"), py::arg("blend_radius_mm") = 0.0f, "Compute A \\ B on GPU.")
|
.def("op_difference", &PyGpuSdfStack::op_difference, py::arg("other"), py::arg("blend_radius_mm") = 0.0f, "Compute A \\ B on GPU.")
|
||||||
.def("offset", &PyGpuSdfStack::offset, py::arg("delta_mm"), "Offset/dilate (delta > 0) or inset/erode (delta < 0) the SDF.")
|
.def("offset", &PyGpuSdfStack::offset, py::arg("delta_mm"), "Offset/dilate (delta > 0) or inset/erode (delta < 0) the SDF.")
|
||||||
.def("shell", &PyGpuSdfStack::shell, py::arg("thickness_mm"), "Hollow/shell the SDF with specified wall thickness.")
|
.def("shell", &PyGpuSdfStack::shell, py::arg("thickness_mm"), "Hollow/shell the SDF with specified wall thickness.")
|
||||||
.def("generate_infill", &PyGpuSdfStack::generate_infill,
|
|
||||||
py::arg("pattern_str") = "gyroid",
|
|
||||||
py::arg("cell_size_mm") = 5.0f,
|
|
||||||
py::arg("density_ratio") = 0.20f,
|
|
||||||
py::arg("wall_offset_mm") = 1.0f,
|
|
||||||
"Intersect interior volume with 3D procedural infill lattice directly on GPU.")
|
|
||||||
.def("vectorize", &PyGpuSdfStack::vectorize,
|
.def("vectorize", &PyGpuSdfStack::vectorize,
|
||||||
py::arg("perimeters_count") = 2,
|
py::arg("perimeters_count") = 2,
|
||||||
py::arg("layer_width") = 0.45f,
|
py::arg("layer_width") = 0.45f,
|
||||||
|
|
|
||||||
17
setup.py
17
setup.py
|
|
@ -39,24 +39,9 @@ class CMakeBuild(build_ext):
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="cudaslicer",
|
name="cudaslicer",
|
||||||
version="1.1.0",
|
version="1.0.0",
|
||||||
author="CUDA Slicer Team",
|
|
||||||
description="GPU-Accelerated 2D/3D Slicing & Polygon Offsetting Library",
|
|
||||||
long_description=open("README.md").read() if os.path.exists("README.md") else "",
|
|
||||||
long_description_content_type="text/markdown",
|
|
||||||
package_dir={"": "python"},
|
package_dir={"": "python"},
|
||||||
packages=find_packages(where="python"),
|
packages=find_packages(where="python"),
|
||||||
ext_modules=[CMakeExtension("cudaslicer._cudaslicer_backend")],
|
ext_modules=[CMakeExtension("cudaslicer._cudaslicer_backend")],
|
||||||
cmdclass={"build_ext": CMakeBuild},
|
cmdclass={"build_ext": CMakeBuild},
|
||||||
python_requires=">=3.8",
|
|
||||||
install_requires=[
|
|
||||||
"numpy>=1.20.0",
|
|
||||||
],
|
|
||||||
classifiers=[
|
|
||||||
"Programming Language :: Python :: 3",
|
|
||||||
"Programming Language :: C++",
|
|
||||||
"Operating System :: POSIX :: Linux",
|
|
||||||
"Operating System :: Microsoft :: Windows",
|
|
||||||
"Topic :: Scientific/Engineering",
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -532,7 +532,7 @@ int cpu_offset_slice_mesh_and_offset(
|
||||||
auto t_idx_1 = std::chrono::high_resolution_clock::now();
|
auto t_idx_1 = std::chrono::high_resolution_clock::now();
|
||||||
double t_idx_ms = std::chrono::duration<double, std::milli>(t_idx_1 - t_idx_0).count();
|
double t_idx_ms = std::chrono::duration<double, std::milli>(t_idx_1 - t_idx_0).count();
|
||||||
|
|
||||||
// 2. Parallel Layer Slicing, Parity Scan, EDT, Thresholding, Vectorization
|
// 2. Parallel Layer Slicing, Parity Scan, Exact Segment SDF, Thresholding, Vectorization
|
||||||
GpuLayerResult* res_array = (GpuLayerResult*)malloc(num_layers * sizeof(GpuLayerResult));
|
GpuLayerResult* res_array = (GpuLayerResult*)malloc(num_layers * sizeof(GpuLayerResult));
|
||||||
memset(res_array, 0, num_layers * sizeof(GpuLayerResult));
|
memset(res_array, 0, num_layers * sizeof(GpuLayerResult));
|
||||||
|
|
||||||
|
|
@ -549,16 +549,18 @@ int cpu_offset_slice_mesh_and_offset(
|
||||||
|
|
||||||
std::vector<unsigned int> parity_words((size_t)rows * row_stride_words, 0);
|
std::vector<unsigned int> parity_words((size_t)rows * row_stride_words, 0);
|
||||||
std::vector<uint8_t> layer_mask(layer_pixels, 0);
|
std::vector<uint8_t> layer_mask(layer_pixels, 0);
|
||||||
std::vector<float> dist_sq(layer_pixels);
|
std::vector<float> layer_sdf(layer_pixels);
|
||||||
std::vector<float> g_col_buf(layer_pixels);
|
|
||||||
std::vector<float> row_f(cols), row_d(cols);
|
struct CpuSegment2D {
|
||||||
std::vector<int> v_buf(cols + 1);
|
float x0, y0, x1, y1;
|
||||||
std::vector<float> z_buf(cols + 2);
|
};
|
||||||
std::vector<unsigned int> bit_words((size_t)rows * row_stride_words);
|
std::vector<CpuSegment2D> layer_segs;
|
||||||
|
layer_segs.reserve(256);
|
||||||
|
|
||||||
for (size_t l = start_layer; l < end_layer; ++l) {
|
for (size_t l = start_layer; l < end_layer; ++l) {
|
||||||
float z0 = z_first + l * layer_height;
|
float z0 = z_first + l * layer_height;
|
||||||
std::fill(parity_words.begin(), parity_words.end(), 0u);
|
std::fill(parity_words.begin(), parity_words.end(), 0u);
|
||||||
|
layer_segs.clear();
|
||||||
|
|
||||||
// Slicing & Parity Rasterization for Layer l
|
// Slicing & Parity Rasterization for Layer l
|
||||||
const auto& tri_indices = layer_tri_indices[l];
|
const auto& tri_indices = layer_tri_indices[l];
|
||||||
|
|
@ -588,29 +590,33 @@ int cpu_offset_slice_mesh_and_offset(
|
||||||
|
|
||||||
if (count != 2) continue;
|
if (count != 2) continue;
|
||||||
|
|
||||||
float gx0 = (pts_x[0] - bounds_min_x) / pitch - 0.5f;
|
layer_segs.push_back({ pts_x[0], pts_y[0], pts_x[1], pts_y[1] });
|
||||||
float gy0 = (pts_y[0] - bounds_min_y) / pitch - 0.5f;
|
|
||||||
float gx1 = (pts_x[1] - bounds_min_x) / pitch - 0.5f;
|
float gx0 = (pts_x[0] - bounds_min_x) / pitch;
|
||||||
float gy1 = (pts_y[1] - bounds_min_y) / pitch - 0.5f;
|
float gy0 = (pts_y[0] - bounds_min_y) / pitch;
|
||||||
|
float gx1 = (pts_x[1] - bounds_min_x) / pitch;
|
||||||
|
float gy1 = (pts_y[1] - bounds_min_y) / pitch;
|
||||||
|
|
||||||
|
if (gy0 == gy1) continue;
|
||||||
|
|
||||||
if (gy0 > gy1) {
|
if (gy0 > gy1) {
|
||||||
std::swap(gx0, gx1);
|
std::swap(gx0, gx1);
|
||||||
std::swap(gy0, gy1);
|
std::swap(gy0, gy1);
|
||||||
}
|
}
|
||||||
|
|
||||||
float dy = gy1 - gy0;
|
|
||||||
if (dy < 1e-6f) continue;
|
|
||||||
|
|
||||||
int y_start = (int)std::ceil(gy0);
|
int y_start = (int)std::ceil(gy0);
|
||||||
int y_end = (int)std::floor(gy1 - 1e-6f);
|
int y_end = (int)std::ceil(gy1) - 1;
|
||||||
|
|
||||||
if (y_start < 0) y_start = 0;
|
if (y_start < 0) y_start = 0;
|
||||||
if (y_end >= rows) y_end = rows - 1;
|
if (y_end >= rows) y_end = rows - 1;
|
||||||
|
|
||||||
|
float dy = gy1 - gy0;
|
||||||
|
if (dy < 1e-7f) continue;
|
||||||
|
|
||||||
for (int y = y_start; y <= y_end; ++y) {
|
for (int y = y_start; y <= y_end; ++y) {
|
||||||
float t = ((float)y - gy0) / dy;
|
float t = ((float)y - gy0) / dy;
|
||||||
float gx = gx0 + t * (gx1 - gx0);
|
float gx = gx0 + t * (gx1 - gx0);
|
||||||
int x_int = (int)std::round(gx + 0.5f);
|
int x_int = (int)std::ceil(gx);
|
||||||
if (x_int < 0) x_int = 0;
|
if (x_int < 0) x_int = 0;
|
||||||
if (x_int >= cols) x_int = cols - 1;
|
if (x_int >= cols) x_int = cols - 1;
|
||||||
|
|
||||||
|
|
@ -635,42 +641,45 @@ int cpu_offset_slice_mesh_and_offset(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute exact 2D EDT
|
// Compute exact continuous signed distance field from analytical layer segments
|
||||||
edt_2d_layer(layer_mask.data(), cols, rows, dist_sq.data(),
|
int num_segs = (int)layer_segs.size();
|
||||||
g_col_buf, row_f, row_d, v_buf, z_buf);
|
for (int y = 0; y < rows; ++y) {
|
||||||
|
float py = bounds_min_y + (float)y * pitch;
|
||||||
|
for (int x = 0; x < cols; ++x) {
|
||||||
|
float px = bounds_min_x + (float)x * pitch;
|
||||||
|
float min_d2 = 1e20f;
|
||||||
|
for (int s = 0; s < num_segs; ++s) {
|
||||||
|
float sx0 = layer_segs[s].x0;
|
||||||
|
float sy0 = layer_segs[s].y0;
|
||||||
|
float sx1 = layer_segs[s].x1;
|
||||||
|
float sy1 = layer_segs[s].y1;
|
||||||
|
float dx = sx1 - sx0;
|
||||||
|
float dy = sy1 - sy0;
|
||||||
|
float l2 = dx * dx + dy * dy;
|
||||||
|
float t = (l2 > 1e-12f) ? ((px - sx0) * dx + (py - sy0) * dy) / l2 : 0.0f;
|
||||||
|
t = std::max(0.0f, std::min(1.0f, t));
|
||||||
|
float qx = sx0 + t * dx;
|
||||||
|
float qy = sy0 + t * dy;
|
||||||
|
float d2 = (px - qx) * (px - qx) + (py - qy) * (py - qy);
|
||||||
|
if (d2 < min_d2) min_d2 = d2;
|
||||||
|
}
|
||||||
|
float d = sqrtf(min_d2);
|
||||||
|
uint8_t m = layer_mask[(size_t)y * cols + x];
|
||||||
|
layer_sdf[(size_t)y * cols + x] = (m > 0) ? d : -d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
res_array[l].z_height = z0;
|
res_array[l].z_height = z0;
|
||||||
res_array[l].num_perimeters = P;
|
res_array[l].num_perimeters = P;
|
||||||
res_array[l].perimeters = (GpuOffsetPerimeter*)malloc(P * sizeof(GpuOffsetPerimeter));
|
res_array[l].perimeters = (GpuOffsetPerimeter*)malloc(P * sizeof(GpuOffsetPerimeter));
|
||||||
|
|
||||||
// Perimeters
|
// Perimeters with continuous SDF and sub-pixel LERP
|
||||||
for (int o = 0; o < P; ++o) {
|
for (int o = 0; o < P; ++o) {
|
||||||
float thresh = resolved_offsets[o];
|
float iso_thresh = resolved_offsets[o];
|
||||||
float thresh_sq_px = (thresh / pitch) * (thresh / pitch);
|
|
||||||
|
|
||||||
for (int y = 0; y < rows; ++y) {
|
|
||||||
const uint8_t* row_m = layer_mask.data() + (size_t)y * cols;
|
|
||||||
const float* row_d2 = dist_sq.data() + (size_t)y * cols;
|
|
||||||
unsigned int* row_w = bit_words.data() + (size_t)y * row_stride_words;
|
|
||||||
|
|
||||||
for (int w = 0; w < row_stride_words; ++w) {
|
|
||||||
unsigned int packed = 0;
|
|
||||||
int x_base = w * 32;
|
|
||||||
for (int b = 0; b < 32; ++b) {
|
|
||||||
int x = x_base + b;
|
|
||||||
if (x < cols) {
|
|
||||||
if (row_m[x] > 0 && row_d2[x] >= thresh_sq_px) {
|
|
||||||
packed |= (1u << b);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
row_w[w] = packed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<GpuContour> contours;
|
std::vector<GpuContour> contours;
|
||||||
vectorize_bitpack_layer(bit_words.data(), cols, rows, row_stride_words,
|
vectorize_sdf_layer(layer_sdf.data(), cols, rows, bounds_min_x, bounds_min_y, pitch,
|
||||||
bounds_min_x, bounds_min_y, pitch,
|
iso_thresh, config->simplify_tolerance_mm, config->subpixel_lerp,
|
||||||
next_edge_buf, active_edges_buf, visited_buf, contours);
|
next_edge_buf, active_edges_buf, visited_buf, contours);
|
||||||
|
|
||||||
res_array[l].perimeters[o].offset_distance_mm = resolved_offsets[o];
|
res_array[l].perimeters[o].offset_distance_mm = resolved_offsets[o];
|
||||||
|
|
@ -684,35 +693,13 @@ int cpu_offset_slice_mesh_and_offset(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Infill pass
|
// Infill pass
|
||||||
float infill_thresh = resolved_offsets[P];
|
float infill_iso_thresh = resolved_offsets[P];
|
||||||
float infill_thresh_sq_px = (infill_thresh / pitch) * (infill_thresh / pitch);
|
|
||||||
|
|
||||||
for (int y = 0; y < rows; ++y) {
|
|
||||||
const uint8_t* row_m = layer_mask.data() + (size_t)y * cols;
|
|
||||||
const float* row_d2 = dist_sq.data() + (size_t)y * cols;
|
|
||||||
unsigned int* row_w = bit_words.data() + (size_t)y * row_stride_words;
|
|
||||||
|
|
||||||
for (int w = 0; w < row_stride_words; ++w) {
|
|
||||||
unsigned int packed = 0;
|
|
||||||
int x_base = w * 32;
|
|
||||||
for (int b = 0; b < 32; ++b) {
|
|
||||||
int x = x_base + b;
|
|
||||||
if (x < cols) {
|
|
||||||
if (row_m[x] > 0 && row_d2[x] >= infill_thresh_sq_px) {
|
|
||||||
packed |= (1u << b);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
row_w[w] = packed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<GpuContour> infill_contours;
|
std::vector<GpuContour> infill_contours;
|
||||||
vectorize_bitpack_layer(bit_words.data(), cols, rows, row_stride_words,
|
vectorize_sdf_layer(layer_sdf.data(), cols, rows, bounds_min_x, bounds_min_y, pitch,
|
||||||
bounds_min_x, bounds_min_y, pitch,
|
infill_iso_thresh, config->simplify_tolerance_mm, config->subpixel_lerp,
|
||||||
next_edge_buf, active_edges_buf, visited_buf, infill_contours);
|
next_edge_buf, active_edges_buf, visited_buf, infill_contours);
|
||||||
|
|
||||||
res_array[l].infill.offset_distance_mm = resolved_offsets[P];
|
res_array[l].infill.offset_distance_mm = infill_iso_thresh;
|
||||||
res_array[l].infill.width = cols;
|
res_array[l].infill.width = cols;
|
||||||
res_array[l].infill.height = rows;
|
res_array[l].infill.height = rows;
|
||||||
res_array[l].infill.num_contours = infill_contours.size();
|
res_array[l].infill.num_contours = infill_contours.size();
|
||||||
|
|
@ -724,17 +711,9 @@ int cpu_offset_slice_mesh_and_offset(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config->export_infill_mask) {
|
if (config->export_infill_mask) {
|
||||||
uint8_t* infill_mask = (uint8_t*)malloc((size_t)cols * rows);
|
uint8_t* infill_mask = (uint8_t*)malloc(layer_pixels);
|
||||||
for (int y = 0; y < rows; ++y) {
|
for (size_t i = 0; i < layer_pixels; ++i) {
|
||||||
const unsigned int* row_words = bit_words.data() + (size_t)y * row_stride_words;
|
infill_mask[i] = (layer_sdf[i] >= infill_iso_thresh) ? 1 : 0;
|
||||||
uint8_t* row_out = infill_mask + (size_t)y * cols;
|
|
||||||
int x_out = 0;
|
|
||||||
for (int w = 0; w < row_stride_words && x_out < cols; ++w) {
|
|
||||||
unsigned int word = row_words[w];
|
|
||||||
for (int b = 0; b < 32 && x_out < cols; ++b, ++x_out) {
|
|
||||||
row_out[x_out] = (uint8_t)((word >> b) & 1u);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
res_array[l].infill.mask = infill_mask;
|
res_array[l].infill.mask = infill_mask;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
#include "gpu_offset.h"
|
#include "gpu_offset.h"
|
||||||
|
#include "gpu_sdf.h"
|
||||||
#include "cpu_offset.h"
|
#include "cpu_offset.h"
|
||||||
#include "vectorizer.h"
|
#include "vectorizer.h"
|
||||||
#include <cuda_runtime.h>
|
#include <cuda_runtime.h>
|
||||||
|
|
@ -763,295 +764,25 @@ GPU_OFFSET_API int gpu_offset_slice_mesh_and_offset(
|
||||||
if (gpu_offset_init(0) != GPU_OFFSET_SUCCESS) return GPU_OFFSET_ERR_GENERIC;
|
if (gpu_offset_init(0) != GPU_OFFSET_SUCCESS) return GPU_OFFSET_ERR_GENERIC;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto t_total_start = std::chrono::high_resolution_clock::now();
|
|
||||||
|
|
||||||
float pad = (config->pad_mm > 0.0f) ? config->pad_mm : 1.5f;
|
float pad = (config->pad_mm > 0.0f) ? config->pad_mm : 1.5f;
|
||||||
float pitch = (config->pitch_mm > 0.0f) ? config->pitch_mm : 0.10f;
|
float pitch = (config->pitch_mm > 0.0f) ? config->pitch_mm : 0.10f;
|
||||||
float bounds_min_x = min_x - pad;
|
|
||||||
float bounds_min_y = min_y - pad;
|
|
||||||
float bounds_max_x = max_x + pad;
|
|
||||||
float bounds_max_y = max_y + pad;
|
|
||||||
|
|
||||||
int cols = (int)std::ceil((bounds_max_x - bounds_min_x) / pitch);
|
|
||||||
int rows = (int)std::ceil((bounds_max_y - bounds_min_y) / pitch);
|
|
||||||
|
|
||||||
float h_first = first_layer_height;
|
float h_first = first_layer_height;
|
||||||
if (h_first <= 0.0f) h_first = config->first_layer_height;
|
if (h_first <= 0.0f) h_first = config->first_layer_height;
|
||||||
if (h_first <= 0.0f) h_first = layer_height;
|
if (h_first <= 0.0f) h_first = layer_height;
|
||||||
|
|
||||||
float z_first = min_z + h_first;
|
GpuSdfStackHandle stack = gpu_sdf_from_mesh(
|
||||||
int num_layers = 0;
|
triangles, num_triangles,
|
||||||
if (max_z >= z_first) {
|
min_x, min_y, min_z, max_x, max_y, max_z,
|
||||||
num_layers = (int)std::floor((max_z - z_first) / layer_height) + 1;
|
layer_height, h_first,
|
||||||
}
|
pitch, pad
|
||||||
|
|
||||||
if (num_layers <= 0) return GPU_OFFSET_ERR_INVALID_ARG;
|
|
||||||
if (out_num_layers) *out_num_layers = num_layers;
|
|
||||||
|
|
||||||
int P = (config->perimeters_count >= 0) ? config->perimeters_count : 2;
|
|
||||||
float W = (config->layer_width > 0.0f) ? config->layer_width : 0.45f;
|
|
||||||
std::vector<float> resolved_offsets;
|
|
||||||
if (config->offset_distances_mm && config->num_offsets > 0) {
|
|
||||||
resolved_offsets.assign(config->offset_distances_mm, config->offset_distances_mm + config->num_offsets);
|
|
||||||
P = (config->perimeters_count > 0) ? config->perimeters_count : (int)resolved_offsets.size() - 1;
|
|
||||||
if (P < 1) P = (int)resolved_offsets.size();
|
|
||||||
} else {
|
|
||||||
resolved_offsets.resize(P + 1);
|
|
||||||
for (int i = 0; i < P; ++i) {
|
|
||||||
resolved_offsets[i] = (i + 0.5f) * W;
|
|
||||||
}
|
|
||||||
resolved_offsets[P] = (float)P * W;
|
|
||||||
}
|
|
||||||
size_t num_offsets = resolved_offsets.size();
|
|
||||||
|
|
||||||
int row_stride_words = (cols + 31) / 32;
|
|
||||||
size_t total_words = (size_t)num_layers * rows * row_stride_words;
|
|
||||||
size_t layer_pixels = (size_t)cols * rows;
|
|
||||||
size_t total_volume = (size_t)num_layers * layer_pixels;
|
|
||||||
size_t total_bit_words = num_offsets * total_words;
|
|
||||||
size_t total_bit_bytes = total_bit_words * sizeof(unsigned int);
|
|
||||||
|
|
||||||
cudaEvent_t ev_start, ev_h2d_done, ev_slice_done, ev_jfa_done, ev_thresh_done, ev_d2h_done;
|
|
||||||
CUDA_CHECK(cudaEventCreate(&ev_start));
|
|
||||||
CUDA_CHECK(cudaEventCreate(&ev_h2d_done));
|
|
||||||
CUDA_CHECK(cudaEventCreate(&ev_slice_done));
|
|
||||||
CUDA_CHECK(cudaEventCreate(&ev_jfa_done));
|
|
||||||
CUDA_CHECK(cudaEventCreate(&ev_thresh_done));
|
|
||||||
CUDA_CHECK(cudaEventCreate(&ev_d2h_done));
|
|
||||||
|
|
||||||
GpuTriangle3D* d_triangles;
|
|
||||||
unsigned int* d_parity;
|
|
||||||
uint8_t* d_masks;
|
|
||||||
int* d_seeds_a;
|
|
||||||
int* d_seeds_b;
|
|
||||||
unsigned int* d_out_bit_words;
|
|
||||||
float* d_thresholds;
|
|
||||||
|
|
||||||
size_t tri_bytes = num_triangles * sizeof(GpuTriangle3D);
|
|
||||||
CUDA_CHECK(cudaMalloc(&d_triangles, tri_bytes));
|
|
||||||
CUDA_CHECK(cudaMalloc(&d_parity, total_words * sizeof(unsigned int)));
|
|
||||||
CUDA_CHECK(cudaMalloc(&d_masks, total_volume * sizeof(uint8_t)));
|
|
||||||
CUDA_CHECK(cudaMalloc(&d_seeds_a, total_volume * sizeof(int)));
|
|
||||||
CUDA_CHECK(cudaMalloc(&d_seeds_b, total_volume * sizeof(int)));
|
|
||||||
CUDA_CHECK(cudaMalloc(&d_out_bit_words, total_bit_bytes));
|
|
||||||
CUDA_CHECK(cudaMalloc(&d_thresholds, num_offsets * sizeof(float)));
|
|
||||||
|
|
||||||
unsigned int* h_pinned_bit_words = nullptr;
|
|
||||||
CUDA_CHECK(cudaMallocHost(&h_pinned_bit_words, total_bit_bytes));
|
|
||||||
|
|
||||||
// 1. Upload Triangles & Thresholds (H2D)
|
|
||||||
CUDA_CHECK(cudaEventRecord(ev_start, g_cuda_stream));
|
|
||||||
CUDA_CHECK(cudaMemcpyAsync(d_triangles, triangles, tri_bytes, cudaMemcpyHostToDevice, g_cuda_stream));
|
|
||||||
CUDA_CHECK(cudaMemcpyAsync(d_thresholds, resolved_offsets.data(),
|
|
||||||
num_offsets * sizeof(float), cudaMemcpyHostToDevice, g_cuda_stream));
|
|
||||||
CUDA_CHECK(cudaEventRecord(ev_h2d_done, g_cuda_stream));
|
|
||||||
|
|
||||||
// 2. GPU Direct Mesh Slicing & Scanline Rasterization
|
|
||||||
CUDA_CHECK(cudaMemsetAsync(d_parity, 0, total_words * sizeof(unsigned int), g_cuda_stream));
|
|
||||||
|
|
||||||
int block_sz = 256;
|
|
||||||
int grid_sz = (num_triangles + block_sz - 1) / block_sz;
|
|
||||||
gpu_mesh_slice_raster_kernel<<<grid_sz, block_sz, 0, g_cuda_stream>>>(
|
|
||||||
d_triangles, num_triangles, min_z, h_first, layer_height, num_layers,
|
|
||||||
bounds_min_x, bounds_min_y, pitch, cols, rows, row_stride_words, d_parity
|
|
||||||
);
|
);
|
||||||
|
if (!stack) {
|
||||||
dim3 scan_grid(rows, num_layers);
|
return GPU_OFFSET_ERR_GENERIC;
|
||||||
gpu_parity_scan_to_mask_kernel<<<scan_grid, 1, 0, g_cuda_stream>>>(
|
|
||||||
d_parity, d_masks, cols, rows, num_layers, row_stride_words
|
|
||||||
);
|
|
||||||
CUDA_CHECK(cudaEventRecord(ev_slice_done, g_cuda_stream));
|
|
||||||
|
|
||||||
// 3. Batched 3D GPU JFA Distance Transform
|
|
||||||
dim3 jfa_block(16, 16, 1);
|
|
||||||
dim3 jfa_grid((cols + 15) / 16, (rows + 15) / 16, num_layers);
|
|
||||||
|
|
||||||
batched_jfa_init_kernel<<<jfa_grid, jfa_block, 0, g_cuda_stream>>>(
|
|
||||||
d_masks, d_seeds_a, cols, rows, num_layers
|
|
||||||
);
|
|
||||||
|
|
||||||
float max_offset_d = 0.0f;
|
|
||||||
for (size_t o = 0; o < num_offsets; ++o) {
|
|
||||||
if (resolved_offsets[o] > max_offset_d) {
|
|
||||||
max_offset_d = resolved_offsets[o];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
int max_radius_px = (int)std::ceil(max_offset_d / pitch) + 2;
|
|
||||||
|
|
||||||
int step = 1;
|
|
||||||
while (step * 2 <= max_radius_px && step < 512) step *= 2;
|
|
||||||
if (step < 8) step = 8;
|
|
||||||
|
|
||||||
int* in_seeds = d_seeds_a;
|
|
||||||
int* out_seeds = d_seeds_b;
|
|
||||||
|
|
||||||
while (step >= 1) {
|
|
||||||
batched_jfa_step_kernel<<<jfa_grid, jfa_block, 0, g_cuda_stream>>>(
|
|
||||||
in_seeds, out_seeds, cols, rows, num_layers, step
|
|
||||||
);
|
|
||||||
std::swap(in_seeds, out_seeds);
|
|
||||||
step /= 2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
batched_jfa_step_kernel<<<jfa_grid, jfa_block, 0, g_cuda_stream>>>(
|
int ret = gpu_sdf_vectorize(stack, config, out_results, out_num_layers, stats);
|
||||||
in_seeds, out_seeds, cols, rows, num_layers, 2
|
gpu_sdf_destroy(stack);
|
||||||
);
|
return ret;
|
||||||
std::swap(in_seeds, out_seeds);
|
|
||||||
|
|
||||||
batched_jfa_step_kernel<<<jfa_grid, jfa_block, 0, g_cuda_stream>>>(
|
|
||||||
in_seeds, out_seeds, cols, rows, num_layers, 1
|
|
||||||
);
|
|
||||||
std::swap(in_seeds, out_seeds);
|
|
||||||
|
|
||||||
CUDA_CHECK(cudaEventRecord(ev_jfa_done, g_cuda_stream));
|
|
||||||
|
|
||||||
// 4. GPU Fused Multi-Perimeter Offset Thresholding & 32-bit Bit-Packing
|
|
||||||
dim3 thresh_block(32, 1, 1);
|
|
||||||
dim3 thresh_grid((row_stride_words + 31) / 32, rows, num_layers);
|
|
||||||
|
|
||||||
batched_jfa_threshold_bitpack_kernel<<<thresh_grid, thresh_block, 0, g_cuda_stream>>>(
|
|
||||||
in_seeds, d_masks, d_out_bit_words,
|
|
||||||
cols, rows, num_layers,
|
|
||||||
pitch, d_thresholds, (int)num_offsets, row_stride_words
|
|
||||||
);
|
|
||||||
CUDA_CHECK(cudaEventRecord(ev_thresh_done, g_cuda_stream));
|
|
||||||
|
|
||||||
// 5. Ultra-Fast D2H Transfer using Pinned Host Memory DMA
|
|
||||||
CUDA_CHECK(cudaMemcpyAsync(h_pinned_bit_words, d_out_bit_words, total_bit_bytes,
|
|
||||||
cudaMemcpyDeviceToHost, g_cuda_stream));
|
|
||||||
CUDA_CHECK(cudaEventRecord(ev_d2h_done, g_cuda_stream));
|
|
||||||
CUDA_CHECK(cudaStreamSynchronize(g_cuda_stream));
|
|
||||||
|
|
||||||
// 6. Multi-Threaded C++ Bit-Packed Vectorization
|
|
||||||
auto t_vec_0 = std::chrono::high_resolution_clock::now();
|
|
||||||
|
|
||||||
GpuLayerResult* res_array = (GpuLayerResult*)malloc(num_layers * sizeof(GpuLayerResult));
|
|
||||||
memset(res_array, 0, num_layers * sizeof(GpuLayerResult));
|
|
||||||
|
|
||||||
unsigned int num_threads = std::max(1u, std::thread::hardware_concurrency());
|
|
||||||
std::vector<std::thread> workers;
|
|
||||||
|
|
||||||
auto vec_worker = [&](size_t start_layer, size_t end_layer) {
|
|
||||||
int stride_w = cols + 1;
|
|
||||||
size_t max_edges = 2 * (size_t)(rows + 1) * stride_w;
|
|
||||||
std::vector<int> next_edge_buf(max_edges, -1);
|
|
||||||
std::vector<int> active_edges_buf;
|
|
||||||
active_edges_buf.reserve(cols * rows / 16);
|
|
||||||
std::vector<uint8_t> visited_buf(max_edges, 0);
|
|
||||||
|
|
||||||
for (size_t l = start_layer; l < end_layer; ++l) {
|
|
||||||
res_array[l].z_height = z_first + l * layer_height;
|
|
||||||
res_array[l].num_perimeters = P;
|
|
||||||
res_array[l].perimeters = (GpuOffsetPerimeter*)malloc(P * sizeof(GpuOffsetPerimeter));
|
|
||||||
|
|
||||||
for (int o = 0; o < P; ++o) {
|
|
||||||
size_t offset_layer_word_idx = (o * num_layers + l) * ((size_t)rows * row_stride_words);
|
|
||||||
const unsigned int* bit_ptr = h_pinned_bit_words + offset_layer_word_idx;
|
|
||||||
|
|
||||||
std::vector<GpuContour> contours;
|
|
||||||
vectorize_bitpack_layer(bit_ptr, cols, rows, row_stride_words, bounds_min_x, bounds_min_y, pitch,
|
|
||||||
next_edge_buf, active_edges_buf, visited_buf, contours);
|
|
||||||
|
|
||||||
res_array[l].perimeters[o].offset_distance_mm = resolved_offsets[o];
|
|
||||||
res_array[l].perimeters[o].num_contours = contours.size();
|
|
||||||
if (!contours.empty()) {
|
|
||||||
res_array[l].perimeters[o].contours = (GpuContour*)malloc(contours.size() * sizeof(GpuContour));
|
|
||||||
memcpy(res_array[l].perimeters[o].contours, contours.data(), contours.size() * sizeof(GpuContour));
|
|
||||||
} else {
|
|
||||||
res_array[l].perimeters[o].contours = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Infill
|
|
||||||
size_t infill_offset_layer_word_idx = (P * num_layers + l) * ((size_t)rows * row_stride_words);
|
|
||||||
const unsigned int* infill_bit_ptr = h_pinned_bit_words + infill_offset_layer_word_idx;
|
|
||||||
|
|
||||||
std::vector<GpuContour> infill_contours;
|
|
||||||
vectorize_bitpack_layer(infill_bit_ptr, cols, rows, row_stride_words, bounds_min_x, bounds_min_y, pitch,
|
|
||||||
next_edge_buf, active_edges_buf, visited_buf, infill_contours);
|
|
||||||
|
|
||||||
res_array[l].infill.offset_distance_mm = resolved_offsets[P];
|
|
||||||
res_array[l].infill.width = cols;
|
|
||||||
res_array[l].infill.height = rows;
|
|
||||||
res_array[l].infill.num_contours = infill_contours.size();
|
|
||||||
if (!infill_contours.empty()) {
|
|
||||||
res_array[l].infill.contours = (GpuContour*)malloc(infill_contours.size() * sizeof(GpuContour));
|
|
||||||
memcpy(res_array[l].infill.contours, infill_contours.data(), infill_contours.size() * sizeof(GpuContour));
|
|
||||||
} else {
|
|
||||||
res_array[l].infill.contours = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config->export_infill_mask) {
|
|
||||||
uint8_t* infill_mask = (uint8_t*)malloc((size_t)cols * rows);
|
|
||||||
for (int y = 0; y < rows; ++y) {
|
|
||||||
const unsigned int* row_words = infill_bit_ptr + (size_t)y * row_stride_words;
|
|
||||||
uint8_t* row_out = infill_mask + (size_t)y * cols;
|
|
||||||
int x_out = 0;
|
|
||||||
for (int w = 0; w < row_stride_words && x_out < cols; ++w) {
|
|
||||||
unsigned int word = row_words[w];
|
|
||||||
for (int b = 0; b < 32 && x_out < cols; ++b, ++x_out) {
|
|
||||||
row_out[x_out] = (uint8_t)((word >> b) & 1u);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
res_array[l].infill.mask = infill_mask;
|
|
||||||
} else {
|
|
||||||
res_array[l].infill.mask = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
size_t chunk = (num_layers + num_threads - 1) / num_threads;
|
|
||||||
for (unsigned int t = 0; t < num_threads; ++t) {
|
|
||||||
size_t s = t * chunk;
|
|
||||||
size_t e = std::min(s + chunk, (size_t)num_layers);
|
|
||||||
if (s < e) workers.emplace_back(vec_worker, s, e);
|
|
||||||
}
|
|
||||||
for (auto& w : workers) w.join();
|
|
||||||
|
|
||||||
auto t_vec_1 = std::chrono::high_resolution_clock::now();
|
|
||||||
double t_vec_ms = std::chrono::duration<double, std::milli>(t_vec_1 - t_vec_0).count();
|
|
||||||
|
|
||||||
auto t_total_end = std::chrono::high_resolution_clock::now();
|
|
||||||
double t_total_ms = std::chrono::duration<double, std::milli>(t_total_end - t_total_start).count();
|
|
||||||
|
|
||||||
if (stats) {
|
|
||||||
float h2d_ms = 0, slice_ms = 0, jfa_ms = 0, thresh_ms = 0, d2h_ms = 0;
|
|
||||||
cudaEventElapsedTime(&h2d_ms, ev_start, ev_h2d_done);
|
|
||||||
cudaEventElapsedTime(&slice_ms, ev_h2d_done, ev_slice_done);
|
|
||||||
cudaEventElapsedTime(&jfa_ms, ev_slice_done, ev_jfa_done);
|
|
||||||
cudaEventElapsedTime(&thresh_ms, ev_jfa_done, ev_thresh_done);
|
|
||||||
cudaEventElapsedTime(&d2h_ms, ev_thresh_done, ev_d2h_done);
|
|
||||||
|
|
||||||
stats->t_h2d_ms = h2d_ms;
|
|
||||||
stats->t_mesh_slice_ms = slice_ms;
|
|
||||||
stats->t_raster_ms = slice_ms;
|
|
||||||
stats->t_gpu_jfa_ms = jfa_ms;
|
|
||||||
stats->t_gpu_thresh_ms = thresh_ms;
|
|
||||||
stats->t_d2h_ms = d2h_ms;
|
|
||||||
stats->t_vectorize_ms = t_vec_ms;
|
|
||||||
stats->t_total_pipeline_ms = t_total_ms;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Free CUDA & Pinned resources
|
|
||||||
cudaFree(d_triangles);
|
|
||||||
cudaFree(d_parity);
|
|
||||||
cudaFree(d_masks);
|
|
||||||
cudaFree(d_seeds_a);
|
|
||||||
cudaFree(d_seeds_b);
|
|
||||||
cudaFree(d_out_bit_words);
|
|
||||||
cudaFree(d_thresholds);
|
|
||||||
cudaFreeHost(h_pinned_bit_words);
|
|
||||||
|
|
||||||
cudaEventDestroy(ev_start);
|
|
||||||
cudaEventDestroy(ev_h2d_done);
|
|
||||||
cudaEventDestroy(ev_slice_done);
|
|
||||||
cudaEventDestroy(ev_jfa_done);
|
|
||||||
cudaEventDestroy(ev_thresh_done);
|
|
||||||
cudaEventDestroy(ev_d2h_done);
|
|
||||||
|
|
||||||
*out_results = res_array;
|
|
||||||
return GPU_OFFSET_SUCCESS;
|
|
||||||
} else {
|
} else {
|
||||||
return cpu_offset_slice_mesh_and_offset(triangles, num_triangles, min_x, min_y, min_z, max_x, max_y, max_z,
|
return cpu_offset_slice_mesh_and_offset(triangles, num_triangles, min_x, min_y, min_z, max_x, max_y, max_z,
|
||||||
layer_height, first_layer_height, config, out_results, out_num_layers, stats);
|
layer_height, first_layer_height, config, out_results, out_num_layers, stats);
|
||||||
|
|
|
||||||
413
src/gpu_sdf.cu
413
src/gpu_sdf.cu
|
|
@ -345,61 +345,7 @@ __global__ void gpu_sdf_shell_kernel(
|
||||||
out_sdf[idx] = half_t - fabsf(d - half_t);
|
out_sdf[idx] = half_t - fabsf(d - half_t);
|
||||||
}
|
}
|
||||||
|
|
||||||
__global__ void gpu_sdf_infill_kernel(
|
|
||||||
const float* __restrict__ in_sdf,
|
|
||||||
float* __restrict__ out_sdf,
|
|
||||||
int width, int height, int num_layers,
|
|
||||||
int32_t min_x_um, int32_t min_y_um, int32_t pitch_um,
|
|
||||||
const int32_t* __restrict__ z_heights_um,
|
|
||||||
int pattern,
|
|
||||||
float cell_size_mm,
|
|
||||||
float density_ratio,
|
|
||||||
float wall_offset_mm
|
|
||||||
) {
|
|
||||||
int x = blockIdx.x * blockDim.x + threadIdx.x;
|
|
||||||
int y = blockIdx.y * blockDim.y + threadIdx.y;
|
|
||||||
int l = blockIdx.z;
|
|
||||||
|
|
||||||
if (x >= width || y >= height || l >= num_layers) return;
|
|
||||||
|
|
||||||
size_t idx = ((size_t)l * height + y) * width + x;
|
|
||||||
float dist = in_sdf[idx];
|
|
||||||
|
|
||||||
// Outside boundary or within shell wall offset -> retain wall distance
|
|
||||||
if (dist <= wall_offset_mm) {
|
|
||||||
out_sdf[idx] = dist;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
float world_x = (float)(min_x_um + x * pitch_um) * 0.001f;
|
|
||||||
float world_y = (float)(min_y_um + y * pitch_um) * 0.001f;
|
|
||||||
float world_z = (float)(z_heights_um[l]) * 0.001f;
|
|
||||||
|
|
||||||
float cell_sz = (cell_size_mm > 0.1f) ? cell_size_mm : 5.0f;
|
|
||||||
float freq = 6.28318530718f / cell_sz;
|
|
||||||
|
|
||||||
float px = world_x * freq;
|
|
||||||
float py = world_y * freq;
|
|
||||||
float pz = world_z * freq;
|
|
||||||
|
|
||||||
float field = 0.0f;
|
|
||||||
if (pattern == GPU_SDF_INFILL_GYROID) {
|
|
||||||
field = sinf(px) * cosf(py) + sinf(py) * cosf(pz) + sinf(pz) * cosf(px);
|
|
||||||
} else if (pattern == GPU_SDF_INFILL_SCHWARZ_P) {
|
|
||||||
field = cosf(px) + cosf(py) + cosf(pz);
|
|
||||||
} else if (pattern == GPU_SDF_INFILL_SCHWARZ_D) {
|
|
||||||
field = cosf(px)*cosf(py)*cosf(pz) - sinf(px)*sinf(py)*sinf(pz);
|
|
||||||
} else if (pattern == GPU_SDF_INFILL_GRID) {
|
|
||||||
float fx = fabsf(fmodf(world_x, cell_sz) - cell_sz * 0.5f);
|
|
||||||
float fy = fabsf(fmodf(world_y, cell_sz) - cell_sz * 0.5f);
|
|
||||||
field = fminf(fx, fy) / (cell_sz * 0.5f) - 0.5f;
|
|
||||||
}
|
|
||||||
|
|
||||||
float threshold = (density_ratio - 0.5f) * 1.5f;
|
|
||||||
// Combine lattice with the infill bounding region
|
|
||||||
float lattice_sdf = (field >= threshold) ? 1.0f : -1.0f;
|
|
||||||
out_sdf[idx] = fminf(dist - wall_offset_mm, lattice_sdf);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// Direct Bitpack Vectorizer Kernel from SDF
|
// Direct Bitpack Vectorizer Kernel from SDF
|
||||||
|
|
@ -597,16 +543,210 @@ GPU_SDF_API GpuSdfStackHandle gpu_sdf_from_masks(
|
||||||
return stack;
|
return stack;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Forward declare GPU mesh rasterizer helper from gpu_offset.cu
|
struct GpuSegment2D {
|
||||||
extern int gpu_slice_mesh_to_device_mask(
|
float x0, y0;
|
||||||
const GpuTriangle3D* triangles,
|
float x1, y1;
|
||||||
|
};
|
||||||
|
|
||||||
|
__global__ void gpu_mesh_slice_raster_and_segments_kernel(
|
||||||
|
const GpuTriangle3D* __restrict__ triangles,
|
||||||
size_t num_triangles,
|
size_t num_triangles,
|
||||||
float min_z, float h_first, float layer_height, int num_layers,
|
float z_min_base,
|
||||||
float bounds_min_x, float bounds_min_y, float pitch,
|
float first_layer_height,
|
||||||
int cols, int rows,
|
float layer_height,
|
||||||
uint8_t* d_masks,
|
int num_layers,
|
||||||
cudaStream_t stream
|
float bounds_min_x,
|
||||||
);
|
float bounds_min_y,
|
||||||
|
float pitch_mm,
|
||||||
|
int cols,
|
||||||
|
int rows,
|
||||||
|
int row_stride_words,
|
||||||
|
unsigned int* __restrict__ parity_words,
|
||||||
|
GpuSegment2D* __restrict__ layer_segments,
|
||||||
|
int* __restrict__ layer_seg_counts,
|
||||||
|
int max_segs_per_layer
|
||||||
|
) {
|
||||||
|
size_t i = (size_t)blockIdx.x * blockDim.x + threadIdx.x;
|
||||||
|
if (i >= num_triangles) return;
|
||||||
|
|
||||||
|
GpuTriangle3D tri = triangles[i];
|
||||||
|
|
||||||
|
float tz_min = fminf(tri.v0[2], fminf(tri.v1[2], tri.v2[2]));
|
||||||
|
float tz_max = fmaxf(tri.v0[2], fmaxf(tri.v1[2], tri.v2[2]));
|
||||||
|
|
||||||
|
float z_first = z_min_base + first_layer_height;
|
||||||
|
int l_start = (tz_min <= z_first) ? 0 : (int)ceilf((tz_min - z_first) / layer_height);
|
||||||
|
int l_end = (int)floorf((tz_max - z_first) / layer_height);
|
||||||
|
|
||||||
|
if (l_start < 0) l_start = 0;
|
||||||
|
if (l_end >= num_layers) l_end = num_layers - 1;
|
||||||
|
if (l_start > l_end) return;
|
||||||
|
|
||||||
|
const float* v[3] = { tri.v0, tri.v1, tri.v2 };
|
||||||
|
|
||||||
|
for (int l = l_start; l <= l_end; ++l) {
|
||||||
|
float z0 = z_first + l * layer_height;
|
||||||
|
if (z0 < tz_min || z0 > tz_max) continue;
|
||||||
|
|
||||||
|
float pts_x[3];
|
||||||
|
float pts_y[3];
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
#pragma unroll
|
||||||
|
for (int e = 0; e < 3; ++e) {
|
||||||
|
int next = (e + 1) % 3;
|
||||||
|
float za = v[e][2];
|
||||||
|
float zb = v[next][2];
|
||||||
|
|
||||||
|
if ((za <= z0 && zb >= z0) || (zb <= z0 && za >= z0)) {
|
||||||
|
if (fabsf(zb - za) > 1e-7f) {
|
||||||
|
float t = (z0 - za) / (zb - za);
|
||||||
|
pts_x[count] = v[e][0] + t * (v[next][0] - v[e][0]);
|
||||||
|
pts_y[count] = v[e][1] + t * (v[next][1] - v[e][1]);
|
||||||
|
count++;
|
||||||
|
if (count == 2) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count != 2) continue;
|
||||||
|
|
||||||
|
if (layer_segments && layer_seg_counts) {
|
||||||
|
int seg_idx = atomicAdd(&layer_seg_counts[l], 1);
|
||||||
|
if (seg_idx < max_segs_per_layer) {
|
||||||
|
GpuSegment2D seg;
|
||||||
|
seg.x0 = pts_x[0];
|
||||||
|
seg.y0 = pts_y[0];
|
||||||
|
seg.x1 = pts_x[1];
|
||||||
|
seg.y1 = pts_y[1];
|
||||||
|
layer_segments[(size_t)l * max_segs_per_layer + seg_idx] = seg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float gx0 = (pts_x[0] - bounds_min_x) / pitch_mm;
|
||||||
|
float gy0 = (pts_y[0] - bounds_min_y) / pitch_mm;
|
||||||
|
float gx1 = (pts_x[1] - bounds_min_x) / pitch_mm;
|
||||||
|
float gy1 = (pts_y[1] - bounds_min_y) / pitch_mm;
|
||||||
|
|
||||||
|
if (gy0 == gy1) continue;
|
||||||
|
|
||||||
|
if (gy0 > gy1) {
|
||||||
|
float tx = gx0; gx0 = gx1; gx1 = tx;
|
||||||
|
float ty = gy0; gy0 = gy1; gy1 = ty;
|
||||||
|
}
|
||||||
|
|
||||||
|
int y_start = (int)ceilf(gy0);
|
||||||
|
int y_end = (int)ceilf(gy1) - 1;
|
||||||
|
|
||||||
|
if (y_start < 0) y_start = 0;
|
||||||
|
if (y_end >= rows) y_end = rows - 1;
|
||||||
|
|
||||||
|
float dy = gy1 - gy0;
|
||||||
|
if (dy < 1e-7f) continue;
|
||||||
|
|
||||||
|
size_t layer_word_offset = (size_t)l * rows * row_stride_words;
|
||||||
|
|
||||||
|
for (int y = y_start; y <= y_end; ++y) {
|
||||||
|
float t = ((float)y - gy0) / dy;
|
||||||
|
float gx = gx0 + t * (gx1 - gx0);
|
||||||
|
int x_int = (int)ceilf(gx);
|
||||||
|
if (x_int < 0) x_int = 0;
|
||||||
|
if (x_int >= cols) x_int = cols - 1;
|
||||||
|
|
||||||
|
int w = x_int / 32;
|
||||||
|
int b = x_int % 32;
|
||||||
|
size_t w_idx = layer_word_offset + (size_t)y * row_stride_words + w;
|
||||||
|
atomicXor(&parity_words[w_idx], (1u << b));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
__global__ void gpu_parity_scan_to_mask_sdf_kernel(
|
||||||
|
const unsigned int* __restrict__ parity_words,
|
||||||
|
uint8_t* __restrict__ masks,
|
||||||
|
int cols,
|
||||||
|
int rows,
|
||||||
|
int num_layers,
|
||||||
|
int row_stride_words
|
||||||
|
) {
|
||||||
|
int y = blockIdx.x;
|
||||||
|
int l = blockIdx.y;
|
||||||
|
if (y >= rows || l >= num_layers) return;
|
||||||
|
|
||||||
|
size_t layer_word_offset = (size_t)l * rows * row_stride_words;
|
||||||
|
size_t layer_pixel_offset = (size_t)l * rows * cols;
|
||||||
|
size_t row_pixel_offset = layer_pixel_offset + (size_t)y * cols;
|
||||||
|
|
||||||
|
uint8_t inside = 0;
|
||||||
|
int x_out = 0;
|
||||||
|
|
||||||
|
for (int w = 0; w < row_stride_words; ++w) {
|
||||||
|
unsigned int word = parity_words[layer_word_offset + (size_t)y * row_stride_words + w];
|
||||||
|
for (int b = 0; b < 32 && x_out < cols; ++b, ++x_out) {
|
||||||
|
inside ^= (uint8_t)((word >> b) & 1u);
|
||||||
|
masks[row_pixel_offset + x_out] = inside;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
__global__ void gpu_compute_exact_mesh_sdf_kernel(
|
||||||
|
const uint8_t* __restrict__ masks,
|
||||||
|
const GpuSegment2D* __restrict__ layer_segments,
|
||||||
|
const int* __restrict__ layer_seg_counts,
|
||||||
|
int max_segs_per_layer,
|
||||||
|
float* __restrict__ out_sdf,
|
||||||
|
int cols,
|
||||||
|
int rows,
|
||||||
|
int num_layers,
|
||||||
|
float bounds_min_x,
|
||||||
|
float bounds_min_y,
|
||||||
|
float pitch_mm
|
||||||
|
) {
|
||||||
|
int x = blockIdx.x * blockDim.x + threadIdx.x;
|
||||||
|
int y = blockIdx.y * blockDim.y + threadIdx.y;
|
||||||
|
int l = blockIdx.z;
|
||||||
|
|
||||||
|
if (x >= cols || y >= rows || l >= num_layers) return;
|
||||||
|
|
||||||
|
size_t layer_pixel_offset = (size_t)l * rows * cols;
|
||||||
|
size_t idx = layer_pixel_offset + (size_t)y * cols + x;
|
||||||
|
|
||||||
|
int seg_count = layer_seg_counts[l];
|
||||||
|
if (seg_count > max_segs_per_layer) seg_count = max_segs_per_layer;
|
||||||
|
|
||||||
|
if (seg_count == 0) {
|
||||||
|
out_sdf[idx] = -1e5f;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float px = bounds_min_x + (float)x * pitch_mm;
|
||||||
|
float py = bounds_min_y + (float)y * pitch_mm;
|
||||||
|
|
||||||
|
float min_d2 = 1e10f;
|
||||||
|
const GpuSegment2D* segs = layer_segments + (size_t)l * max_segs_per_layer;
|
||||||
|
|
||||||
|
for (int s = 0; s < seg_count; ++s) {
|
||||||
|
GpuSegment2D seg = segs[s];
|
||||||
|
float vx = seg.x1 - seg.x0;
|
||||||
|
float vy = seg.y1 - seg.y0;
|
||||||
|
float wx = px - seg.x0;
|
||||||
|
float wy = py - seg.y0;
|
||||||
|
float len_sq = vx * vx + vy * vy;
|
||||||
|
float t = (len_sq > 1e-10f) ? (wx * vx + wy * vy) / len_sq : 0.0f;
|
||||||
|
if (t < 0.0f) t = 0.0f;
|
||||||
|
else if (t > 1.0f) t = 1.0f;
|
||||||
|
float qx = seg.x0 + t * vx;
|
||||||
|
float qy = seg.y0 + t * vy;
|
||||||
|
float dx = px - qx;
|
||||||
|
float dy = py - qy;
|
||||||
|
float d2 = dx * dx + dy * dy;
|
||||||
|
if (d2 < min_d2) min_d2 = d2;
|
||||||
|
}
|
||||||
|
|
||||||
|
float d = sqrtf(min_d2);
|
||||||
|
uint8_t m = masks[idx];
|
||||||
|
out_sdf[idx] = (m > 0) ? d : -d;
|
||||||
|
}
|
||||||
|
|
||||||
GPU_SDF_API GpuSdfStackHandle gpu_sdf_from_mesh(
|
GPU_SDF_API GpuSdfStackHandle gpu_sdf_from_mesh(
|
||||||
const GpuTriangle3D* triangles,
|
const GpuTriangle3D* triangles,
|
||||||
|
|
@ -656,107 +796,67 @@ GPU_SDF_API GpuSdfStackHandle gpu_sdf_from_mesh(
|
||||||
GpuSdfStackHandle stack = gpu_sdf_create(&meta, z_heights_um.data());
|
GpuSdfStackHandle stack = gpu_sdf_create(&meta, z_heights_um.data());
|
||||||
if (!stack) return nullptr;
|
if (!stack) return nullptr;
|
||||||
|
|
||||||
|
int row_stride_words = (cols + 31) / 32;
|
||||||
|
size_t total_words = (size_t)num_layers * rows * row_stride_words;
|
||||||
size_t total_volume = (size_t)num_layers * rows * cols;
|
size_t total_volume = (size_t)num_layers * rows * cols;
|
||||||
size_t tri_bytes = num_triangles * sizeof(GpuTriangle3D);
|
size_t tri_bytes = num_triangles * sizeof(GpuTriangle3D);
|
||||||
size_t seed_bytes = total_volume * sizeof(int);
|
|
||||||
|
int max_segs_per_layer = std::min(8192, (int)num_triangles);
|
||||||
|
if (max_segs_per_layer < 256) max_segs_per_layer = 256;
|
||||||
|
size_t total_segs = (size_t)num_layers * max_segs_per_layer;
|
||||||
|
|
||||||
GpuTriangle3D* d_triangles = nullptr;
|
GpuTriangle3D* d_triangles = nullptr;
|
||||||
|
unsigned int* d_parity = nullptr;
|
||||||
uint8_t* d_masks = nullptr;
|
uint8_t* d_masks = nullptr;
|
||||||
int* d_seeds_ext_a = nullptr;
|
GpuSegment2D* d_layer_segments = nullptr;
|
||||||
int* d_seeds_ext_b = nullptr;
|
int* d_layer_seg_counts = nullptr;
|
||||||
int* d_seeds_int_a = nullptr;
|
|
||||||
int* d_seeds_int_b = nullptr;
|
|
||||||
|
|
||||||
if (cudaMalloc(&d_triangles, tri_bytes) != cudaSuccess ||
|
if (cudaMalloc(&d_triangles, tri_bytes) != cudaSuccess ||
|
||||||
|
cudaMalloc(&d_parity, total_words * sizeof(unsigned int)) != cudaSuccess ||
|
||||||
cudaMalloc(&d_masks, total_volume * sizeof(uint8_t)) != cudaSuccess ||
|
cudaMalloc(&d_masks, total_volume * sizeof(uint8_t)) != cudaSuccess ||
|
||||||
cudaMalloc(&d_seeds_ext_a, seed_bytes) != cudaSuccess ||
|
cudaMalloc(&d_layer_segments, total_segs * sizeof(GpuSegment2D)) != cudaSuccess ||
|
||||||
cudaMalloc(&d_seeds_ext_b, seed_bytes) != cudaSuccess ||
|
cudaMalloc(&d_layer_seg_counts, num_layers * sizeof(int)) != cudaSuccess) {
|
||||||
cudaMalloc(&d_seeds_int_a, seed_bytes) != cudaSuccess ||
|
|
||||||
cudaMalloc(&d_seeds_int_b, seed_bytes) != cudaSuccess) {
|
|
||||||
if (d_triangles) cudaFree(d_triangles);
|
if (d_triangles) cudaFree(d_triangles);
|
||||||
|
if (d_parity) cudaFree(d_parity);
|
||||||
if (d_masks) cudaFree(d_masks);
|
if (d_masks) cudaFree(d_masks);
|
||||||
if (d_seeds_ext_a) cudaFree(d_seeds_ext_a);
|
if (d_layer_segments) cudaFree(d_layer_segments);
|
||||||
if (d_seeds_ext_b) cudaFree(d_seeds_ext_b);
|
if (d_layer_seg_counts) cudaFree(d_layer_seg_counts);
|
||||||
if (d_seeds_int_a) cudaFree(d_seeds_int_a);
|
|
||||||
if (d_seeds_int_b) cudaFree(d_seeds_int_b);
|
|
||||||
gpu_sdf_destroy(stack);
|
gpu_sdf_destroy(stack);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cudaMemsetAsync(d_parity, 0, total_words * sizeof(unsigned int), stack->stream);
|
||||||
|
cudaMemsetAsync(d_layer_seg_counts, 0, num_layers * sizeof(int), stack->stream);
|
||||||
cudaMemcpyAsync(d_triangles, triangles, tri_bytes, cudaMemcpyHostToDevice, stack->stream);
|
cudaMemcpyAsync(d_triangles, triangles, tri_bytes, cudaMemcpyHostToDevice, stack->stream);
|
||||||
|
|
||||||
int slice_ret = gpu_slice_mesh_to_device_mask(
|
int block_sz = 256;
|
||||||
|
int grid_sz = (num_triangles + block_sz - 1) / block_sz;
|
||||||
|
gpu_mesh_slice_raster_and_segments_kernel<<<grid_sz, block_sz, 0, stack->stream>>>(
|
||||||
d_triangles, num_triangles, min_z, h_first, layer_height_mm, num_layers,
|
d_triangles, num_triangles, min_z, h_first, layer_height_mm, num_layers,
|
||||||
bounds_min_x, bounds_min_y, pitch, cols, rows, d_masks, stack->stream
|
bounds_min_x, bounds_min_y, pitch, cols, rows, row_stride_words,
|
||||||
);
|
d_parity, d_layer_segments, d_layer_seg_counts, max_segs_per_layer
|
||||||
if (slice_ret != GPU_OFFSET_SUCCESS) {
|
|
||||||
cudaFree(d_triangles);
|
|
||||||
cudaFree(d_masks);
|
|
||||||
cudaFree(d_seeds_ext_a);
|
|
||||||
cudaFree(d_seeds_ext_b);
|
|
||||||
cudaFree(d_seeds_int_a);
|
|
||||||
cudaFree(d_seeds_int_b);
|
|
||||||
gpu_sdf_destroy(stack);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run Batched 3D JFA Dual Direction Signed Distance
|
|
||||||
dim3 block(16, 16, 1);
|
|
||||||
dim3 grid((cols + 15) / 16, (rows + 15) / 16, num_layers);
|
|
||||||
|
|
||||||
jfa_init_interior_exterior_kernel<<<grid, block, 0, stack->stream>>>(
|
|
||||||
d_masks, d_seeds_ext_a, d_seeds_int_a, cols, rows, num_layers
|
|
||||||
);
|
);
|
||||||
|
|
||||||
int max_dim = std::max(cols, rows);
|
dim3 scan_grid(rows, num_layers);
|
||||||
int step = 1;
|
gpu_parity_scan_to_mask_sdf_kernel<<<scan_grid, 1, 0, stack->stream>>>(
|
||||||
while (step * 2 < max_dim) step *= 2;
|
d_parity, d_masks, cols, rows, num_layers, row_stride_words
|
||||||
|
|
||||||
int* in_seeds_ext = d_seeds_ext_a;
|
|
||||||
int* out_seeds_ext = d_seeds_ext_b;
|
|
||||||
int* in_seeds_int = d_seeds_int_a;
|
|
||||||
int* out_seeds_int = d_seeds_int_b;
|
|
||||||
|
|
||||||
while (step >= 1) {
|
|
||||||
jfa_step_dual_kernel<<<grid, block, 0, stack->stream>>>(
|
|
||||||
in_seeds_ext, out_seeds_ext,
|
|
||||||
in_seeds_int, out_seeds_int,
|
|
||||||
cols, rows, num_layers, step
|
|
||||||
);
|
);
|
||||||
std::swap(in_seeds_ext, out_seeds_ext);
|
|
||||||
std::swap(in_seeds_int, out_seeds_int);
|
|
||||||
step /= 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
jfa_step_dual_kernel<<<grid, block, 0, stack->stream>>>(
|
dim3 exact_block(16, 16, 1);
|
||||||
in_seeds_ext, out_seeds_ext,
|
dim3 exact_grid((cols + 15) / 16, (rows + 15) / 16, num_layers);
|
||||||
in_seeds_int, out_seeds_int,
|
gpu_compute_exact_mesh_sdf_kernel<<<exact_grid, exact_block, 0, stack->stream>>>(
|
||||||
cols, rows, num_layers, 2
|
d_masks, d_layer_segments, d_layer_seg_counts, max_segs_per_layer,
|
||||||
);
|
stack->d_sdf, cols, rows, num_layers,
|
||||||
std::swap(in_seeds_ext, out_seeds_ext);
|
bounds_min_x, bounds_min_y, pitch
|
||||||
std::swap(in_seeds_int, out_seeds_int);
|
|
||||||
|
|
||||||
jfa_step_dual_kernel<<<grid, block, 0, stack->stream>>>(
|
|
||||||
in_seeds_ext, out_seeds_ext,
|
|
||||||
in_seeds_int, out_seeds_int,
|
|
||||||
cols, rows, num_layers, 1
|
|
||||||
);
|
|
||||||
std::swap(in_seeds_ext, out_seeds_ext);
|
|
||||||
std::swap(in_seeds_int, out_seeds_int);
|
|
||||||
|
|
||||||
fuse_signed_sdf_kernel<<<grid, block, 0, stack->stream>>>(
|
|
||||||
d_masks, in_seeds_ext, in_seeds_int, stack->d_sdf,
|
|
||||||
cols, rows, num_layers, pitch
|
|
||||||
);
|
);
|
||||||
|
|
||||||
cudaStreamSynchronize(stack->stream);
|
cudaStreamSynchronize(stack->stream);
|
||||||
|
|
||||||
cudaFree(d_triangles);
|
cudaFree(d_triangles);
|
||||||
|
cudaFree(d_parity);
|
||||||
cudaFree(d_masks);
|
cudaFree(d_masks);
|
||||||
cudaFree(d_seeds_ext_a);
|
cudaFree(d_layer_segments);
|
||||||
cudaFree(d_seeds_ext_b);
|
cudaFree(d_layer_seg_counts);
|
||||||
cudaFree(d_seeds_int_a);
|
|
||||||
cudaFree(d_seeds_int_b);
|
|
||||||
|
|
||||||
return stack;
|
return stack;
|
||||||
}
|
}
|
||||||
|
|
@ -1002,35 +1102,6 @@ GPU_SDF_API GpuSdfStackHandle gpu_sdf_shell(
|
||||||
return out_stack;
|
return out_stack;
|
||||||
}
|
}
|
||||||
|
|
||||||
GPU_SDF_API GpuSdfStackHandle gpu_sdf_generate_infill(
|
|
||||||
GpuSdfStackHandle stack,
|
|
||||||
GpuSdfInfillPattern pattern,
|
|
||||||
float cell_size_mm,
|
|
||||||
float density_ratio,
|
|
||||||
float wall_offset_mm
|
|
||||||
) {
|
|
||||||
if (!stack) return nullptr;
|
|
||||||
|
|
||||||
std::vector<int32_t> z_um(stack->meta.num_layers);
|
|
||||||
gpu_sdf_get_layer_z_um(stack, z_um.data());
|
|
||||||
|
|
||||||
GpuSdfStackHandle out_stack = gpu_sdf_create(&stack->meta, z_um.data());
|
|
||||||
if (!out_stack) return nullptr;
|
|
||||||
|
|
||||||
dim3 block(16, 16, 1);
|
|
||||||
dim3 grid((stack->meta.width + 15) / 16, (stack->meta.height + 15) / 16, (unsigned int)stack->meta.num_layers);
|
|
||||||
|
|
||||||
gpu_sdf_infill_kernel<<<grid, block, 0, out_stack->stream>>>(
|
|
||||||
stack->d_sdf, out_stack->d_sdf,
|
|
||||||
stack->meta.width, stack->meta.height, (int)stack->meta.num_layers,
|
|
||||||
stack->meta.min_x_um, stack->meta.min_y_um, stack->meta.pitch_um,
|
|
||||||
stack->d_z_um, (int)pattern, cell_size_mm, density_ratio, wall_offset_mm
|
|
||||||
);
|
|
||||||
cudaStreamSynchronize(out_stack->stream);
|
|
||||||
|
|
||||||
return out_stack;
|
|
||||||
}
|
|
||||||
|
|
||||||
GPU_SDF_API int gpu_sdf_vectorize(
|
GPU_SDF_API int gpu_sdf_vectorize(
|
||||||
GpuSdfStackHandle stack,
|
GpuSdfStackHandle stack,
|
||||||
const GpuOffsetConfig* config,
|
const GpuOffsetConfig* config,
|
||||||
|
|
@ -1068,9 +1139,7 @@ GPU_SDF_API int gpu_sdf_vectorize(
|
||||||
size_t num_offsets = resolved_offsets.size();
|
size_t num_offsets = resolved_offsets.size();
|
||||||
|
|
||||||
float simplify_tol = config->simplify_tolerance_mm;
|
float simplify_tol = config->simplify_tolerance_mm;
|
||||||
if (simplify_tol <= 0.0f && config->subpixel_lerp != 0) {
|
int subpixel_lerp = config->subpixel_lerp;
|
||||||
simplify_tol = 0.02f; // default 0.02 mm (20 µm) tolerance
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t total_voxels = (size_t)num_layers * rows * cols;
|
size_t total_voxels = (size_t)num_layers * rows * cols;
|
||||||
size_t sdf_bytes = total_voxels * sizeof(float);
|
size_t sdf_bytes = total_voxels * sizeof(float);
|
||||||
|
|
@ -1113,7 +1182,7 @@ GPU_SDF_API int gpu_sdf_vectorize(
|
||||||
|
|
||||||
std::vector<GpuContour> contours;
|
std::vector<GpuContour> contours;
|
||||||
vectorize_sdf_layer(layer_sdf_ptr, cols, rows, bounds_min_x, bounds_min_y, pitch_mm,
|
vectorize_sdf_layer(layer_sdf_ptr, cols, rows, bounds_min_x, bounds_min_y, pitch_mm,
|
||||||
iso_thresh, simplify_tol,
|
iso_thresh, simplify_tol, subpixel_lerp,
|
||||||
next_edge_buf, active_edges_buf, visited_buf, contours);
|
next_edge_buf, active_edges_buf, visited_buf, contours);
|
||||||
|
|
||||||
res_array[l].perimeters[o].offset_distance_mm = iso_thresh;
|
res_array[l].perimeters[o].offset_distance_mm = iso_thresh;
|
||||||
|
|
@ -1126,11 +1195,11 @@ GPU_SDF_API int gpu_sdf_vectorize(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Infill
|
// Infill Contours (Innermost offset boundary)
|
||||||
float infill_iso_thresh = resolved_offsets[P];
|
float infill_iso_thresh = resolved_offsets[P];
|
||||||
std::vector<GpuContour> infill_contours;
|
std::vector<GpuContour> infill_contours;
|
||||||
vectorize_sdf_layer(layer_sdf_ptr, cols, rows, bounds_min_x, bounds_min_y, pitch_mm,
|
vectorize_sdf_layer(layer_sdf_ptr, cols, rows, bounds_min_x, bounds_min_y, pitch_mm,
|
||||||
infill_iso_thresh, simplify_tol,
|
infill_iso_thresh, simplify_tol, subpixel_lerp,
|
||||||
next_edge_buf, active_edges_buf, visited_buf, infill_contours);
|
next_edge_buf, active_edges_buf, visited_buf, infill_contours);
|
||||||
|
|
||||||
res_array[l].infill.offset_distance_mm = infill_iso_thresh;
|
res_array[l].infill.offset_distance_mm = infill_iso_thresh;
|
||||||
|
|
|
||||||
|
|
@ -143,12 +143,16 @@ static inline std::vector<GpuPoint2D> simplify_contour_rdp(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<bool> keep(n, false);
|
std::vector<GpuPoint2D> closed_pts = loop_pts;
|
||||||
|
closed_pts.push_back(loop_pts[0]);
|
||||||
|
|
||||||
|
std::vector<bool> keep(n + 1, false);
|
||||||
keep[0] = true;
|
keep[0] = true;
|
||||||
keep[split_idx] = true;
|
keep[split_idx] = true;
|
||||||
|
keep[n] = true;
|
||||||
|
|
||||||
rdp_simplify_recursive(loop_pts, 0, split_idx, eps_sq, keep);
|
rdp_simplify_recursive(closed_pts, 0, split_idx, eps_sq, keep);
|
||||||
rdp_simplify_recursive(loop_pts, split_idx, n - 1, eps_sq, keep);
|
rdp_simplify_recursive(closed_pts, split_idx, n, eps_sq, keep);
|
||||||
|
|
||||||
std::vector<GpuPoint2D> simplified;
|
std::vector<GpuPoint2D> simplified;
|
||||||
simplified.reserve(n);
|
simplified.reserve(n);
|
||||||
|
|
@ -164,6 +168,7 @@ static inline void vectorize_sdf_layer(
|
||||||
float min_x, float min_y, float pitch_mm,
|
float min_x, float min_y, float pitch_mm,
|
||||||
float iso_val,
|
float iso_val,
|
||||||
float simplify_tolerance_mm,
|
float simplify_tolerance_mm,
|
||||||
|
int subpixel_lerp,
|
||||||
std::vector<int>& next_edge_buf,
|
std::vector<int>& next_edge_buf,
|
||||||
std::vector<int>& active_edges_buf,
|
std::vector<int>& active_edges_buf,
|
||||||
std::vector<uint8_t>& visited_buf,
|
std::vector<uint8_t>& visited_buf,
|
||||||
|
|
@ -253,7 +258,12 @@ static inline void vectorize_sdf_layer(
|
||||||
|
|
||||||
while (cur != -1 && !visited_buf[cur]) {
|
while (cur != -1 && !visited_buf[cur]) {
|
||||||
visited_buf[cur] = 1;
|
visited_buf[cur] = 1;
|
||||||
GpuPoint2D pt = get_sdf_edge_point(cur, sdf, width, height, min_x, min_y, pitch_mm, iso_val);
|
GpuPoint2D pt;
|
||||||
|
if (subpixel_lerp) {
|
||||||
|
pt = get_sdf_edge_point(cur, sdf, width, height, min_x, min_y, pitch_mm, iso_val);
|
||||||
|
} else {
|
||||||
|
pt = get_edge_point(cur, stride_w, min_x, min_y, pitch_mm);
|
||||||
|
}
|
||||||
|
|
||||||
size_t n = loop_pts.size();
|
size_t n = loop_pts.size();
|
||||||
if (n >= 2) {
|
if (n >= 2) {
|
||||||
|
|
@ -274,11 +284,13 @@ static inline void vectorize_sdf_layer(
|
||||||
|
|
||||||
cur = next_edge_buf[cur];
|
cur = next_edge_buf[cur];
|
||||||
if (cur == start_e) break;
|
if (cur == start_e) break;
|
||||||
if (loop_pts.size() > 50000) break;
|
if (loop_pts.size() > 100000) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loop_pts.size() >= 3) {
|
if (loop_pts.size() >= 3) {
|
||||||
std::vector<GpuPoint2D> simplified = simplify_contour_rdp(loop_pts, simplify_tolerance_mm);
|
std::vector<GpuPoint2D> simplified = (simplify_tolerance_mm > 1e-6f)
|
||||||
|
? simplify_contour_rdp(loop_pts, simplify_tolerance_mm)
|
||||||
|
: loop_pts;
|
||||||
if (simplified.size() >= 3) {
|
if (simplified.size() >= 3) {
|
||||||
GpuContour c;
|
GpuContour c;
|
||||||
c.num_points = simplified.size();
|
c.num_points = simplified.size();
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue