conquer3d 0.7.4__tar.gz → 0.7.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. {conquer3d-0.7.4/conquer3d.egg-info → conquer3d-0.7.5}/PKG-INFO +1 -1
  2. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/_C.so +0 -0
  3. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/binds/ops/dc.cpp +21 -2
  4. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/binds/ops/dmc.cpp +16 -2
  5. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/maths/qef.h +11 -4
  6. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/ops/dc.cu +32 -4
  7. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/ops/dc.h +3 -1
  8. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/ops/dmc.cu +36 -1
  9. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/ops/dmc.h +3 -1
  10. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/ops/__init__.py +4 -0
  11. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/ops/dual_contouring.py +28 -2
  12. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/ops/dual_marching_cubes.py +29 -2
  13. conquer3d-0.7.5/conquer3d/ops/hermite.py +263 -0
  14. {conquer3d-0.7.4 → conquer3d-0.7.5/conquer3d.egg-info}/PKG-INFO +1 -1
  15. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d.egg-info/SOURCES.txt +1 -0
  16. {conquer3d-0.7.4 → conquer3d-0.7.5}/pyproject.toml +1 -1
  17. {conquer3d-0.7.4 → conquer3d-0.7.5}/LICENSE +0 -0
  18. {conquer3d-0.7.4 → conquer3d-0.7.5}/MANIFEST.in +0 -0
  19. {conquer3d-0.7.4 → conquer3d-0.7.5}/README.md +0 -0
  20. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/_C.pyi +0 -0
  21. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/__init__.py +0 -0
  22. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/conversion/__init__.py +0 -0
  23. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/conversion/grid.py +0 -0
  24. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/conversion/mesh.py +0 -0
  25. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/conversion/tmesh.py +0 -0
  26. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/creation/__init__.py +0 -0
  27. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/creation/triangle_creation.py +0 -0
  28. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/binds/creation/triangle_creation.cpp +0 -0
  29. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/binds/data_structure/bvh.cpp +0 -0
  30. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/binds/data_structure/grid.cpp +0 -0
  31. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/binds/data_structure/gs_bvh.cpp +0 -0
  32. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/binds/data_structure/kdtree.cpp +0 -0
  33. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/binds/data_structure/mesh_bvh.cpp +0 -0
  34. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/binds/data_structure/pgs_bvh.cpp +0 -0
  35. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/binds/data_structure/triangle_mesh.cpp +0 -0
  36. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/binds/data_structure/zcurve.cpp +0 -0
  37. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/binds/ops/chamfer.cpp +0 -0
  38. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/binds/ops/flood_fill.cpp +0 -0
  39. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/binds/ops/flood_fill_cf.cpp +0 -0
  40. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/binds/ops/mc.cpp +0 -0
  41. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/binds/ops/mca.cpp +0 -0
  42. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/binds/ops/mt.cpp +0 -0
  43. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/binds/ops/mtg.cpp +0 -0
  44. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/binds/ops/volint.cpp +0 -0
  45. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/binds/primitive/gs.cpp +0 -0
  46. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/binds/primitive/pgs.cpp +0 -0
  47. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/binds/primitive/ray.cpp +0 -0
  48. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/binds/primitive/triangle.cpp +0 -0
  49. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/check.h +0 -0
  50. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/constants.h +0 -0
  51. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/creation/triangle_creation.h +0 -0
  52. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/data_structure/bvh.cu +0 -0
  53. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/data_structure/bvh.h +0 -0
  54. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/data_structure/grid.cu +0 -0
  55. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/data_structure/grid.h +0 -0
  56. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/data_structure/gs_bvh.h +0 -0
  57. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/data_structure/kdtree.cu +0 -0
  58. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/data_structure/kdtree.h +0 -0
  59. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/data_structure/mesh_bvh.cu +0 -0
  60. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/data_structure/mesh_bvh.h +0 -0
  61. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/data_structure/pgs_bvh.h +0 -0
  62. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/data_structure/triangle_mesh.cu +0 -0
  63. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/data_structure/triangle_mesh.h +0 -0
  64. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/data_structure/zcurve.cu +0 -0
  65. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/data_structure/zcurve.h +0 -0
  66. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/maths/f2x2.h +0 -0
  67. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/maths/f3x1.h +0 -0
  68. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/maths/f3x3.h +0 -0
  69. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/maths/f3x4.h +0 -0
  70. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/maths/f4x1.h +0 -0
  71. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/maths/f4x4.h +0 -0
  72. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/maths/maths.h +0 -0
  73. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/maths/ops.h +0 -0
  74. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/ops/chamfer.cu +0 -0
  75. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/ops/chamfer.h +0 -0
  76. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/ops/dc_data.h +0 -0
  77. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/ops/dmc_data.h +0 -0
  78. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/ops/flood_fill.cu +0 -0
  79. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/ops/flood_fill.h +0 -0
  80. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/ops/flood_fill_cf.cu +0 -0
  81. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/ops/flood_fill_cf.h +0 -0
  82. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/ops/mc.cu +0 -0
  83. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/ops/mc.h +0 -0
  84. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/ops/mc_data.h +0 -0
  85. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/ops/mca.cu +0 -0
  86. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/ops/mca.h +0 -0
  87. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/ops/mca_data.h +0 -0
  88. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/ops/mt.cu +0 -0
  89. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/ops/mt.h +0 -0
  90. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/ops/mt_data.h +0 -0
  91. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/ops/mtg.cu +0 -0
  92. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/ops/mtg.h +0 -0
  93. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/ops/mtg_data.h +0 -0
  94. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/ops/volint.cu +0 -0
  95. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/ops/volint.h +0 -0
  96. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/primitive/aabb.h +0 -0
  97. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/primitive/edge.h +0 -0
  98. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/primitive/gs.cu +0 -0
  99. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/primitive/gs.h +0 -0
  100. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/primitive/gs_aabb.cu +0 -0
  101. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/primitive/gs_math.cuh +0 -0
  102. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/primitive/pgs.cu +0 -0
  103. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/primitive/pgs.h +0 -0
  104. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/primitive/pgs_aabb.cu +0 -0
  105. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/primitive/pgs_math.cuh +0 -0
  106. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/primitive/ray.h +0 -0
  107. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/primitive/triangle.h +0 -0
  108. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/csrc/pybind.cpp +0 -0
  109. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/data/__init__.py +0 -0
  110. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/data/assets/__init__.py +0 -0
  111. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/data/assets/common.py +0 -0
  112. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/data/assets/iphigenia.py +0 -0
  113. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/data/collate/__init__.py +0 -0
  114. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/data/collate/mesh.py +0 -0
  115. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/data/collate/sparse_tensor.py +0 -0
  116. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/data/dataset/__init__.py +0 -0
  117. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/data/dataset/base_mesh.py +0 -0
  118. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/data/dataset/digit3d.py +0 -0
  119. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/data/dataset/digit3dmv.py +0 -0
  120. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/data/dataset/mesh.py +0 -0
  121. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/data/dataset/redwood.py +0 -0
  122. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/data/transform/__init__.py +0 -0
  123. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/data/transform/base.py +0 -0
  124. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/data/transform/ops.py +0 -0
  125. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/data/transform/vertex.py +0 -0
  126. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/data_structure/__init__.py +0 -0
  127. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/data_structure/bmesh.py +0 -0
  128. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/data_structure/grid.py +0 -0
  129. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/data_structure/sort.py +0 -0
  130. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/io/__init__.py +0 -0
  131. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/io/obj.py +0 -0
  132. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/io/off.py +0 -0
  133. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/io/ply.py +0 -0
  134. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/ops/delaunay_triangulation.py +0 -0
  135. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/ops/diff_marching_cubes.py +0 -0
  136. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/ops/diff_marching_tetrahedra_grid.py +0 -0
  137. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/ops/distance.py +0 -0
  138. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/ops/dpsr.py +0 -0
  139. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/ops/marching_cubes.py +0 -0
  140. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/ops/marching_cubes_asymptotic.py +0 -0
  141. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/ops/marching_tetrahedra.py +0 -0
  142. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/ops/marching_tetrahedra_grid.py +0 -0
  143. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/ops/volint.py +0 -0
  144. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/primitive/__init__.py +0 -0
  145. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/primitive/gs.py +0 -0
  146. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d/primitive/pgs.py +0 -0
  147. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d.egg-info/dependency_links.txt +0 -0
  148. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d.egg-info/not-zip-safe +0 -0
  149. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d.egg-info/requires.txt +0 -0
  150. {conquer3d-0.7.4 → conquer3d-0.7.5}/conquer3d.egg-info/top_level.txt +0 -0
  151. {conquer3d-0.7.4 → conquer3d-0.7.5}/setup.cfg +0 -0
  152. {conquer3d-0.7.4 → conquer3d-0.7.5}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: conquer3d
3
- Version: 0.7.4
3
+ Version: 0.7.5
4
4
  Summary: Geometric Cuda Tool Box
5
5
  Author-email: Do Hoang Khoi <khoido8899@gmail.com>
6
6
  License-Expression: MIT
@@ -21,7 +21,9 @@ std::tuple<torch::Tensor, torch::Tensor, std::optional<torch::Tensor>> dual_cont
21
21
  std::optional<torch::Tensor> colors,
22
22
  std::optional<torch::Tensor> voxel_vertices,
23
23
  float iso,
24
- bool quad_split
24
+ bool quad_split,
25
+ std::optional<torch::Tensor> edge_points,
26
+ std::optional<torch::Tensor> edge_normals
25
27
  ) {
26
28
  CHECK_INPUT(grid_vertices);
27
29
  CHECK_INPUT(voxels);
@@ -36,6 +38,20 @@ std::tuple<torch::Tensor, torch::Tensor, std::optional<torch::Tensor>> dual_cont
36
38
  if (voxel_vertices.has_value() && voxel_vertices.value().defined()) {
37
39
  CHECK_INPUT(voxel_vertices.value());
38
40
  }
41
+ if (edge_points.has_value() && edge_points.value().defined()) {
42
+ CHECK_INPUT(edge_points.value());
43
+ TORCH_CHECK(edge_points.value().dim() == 3 && edge_points.value().size(1) == 12 && edge_points.value().size(2) == 3,
44
+ "edge_points must have shape (M, 12, 3)");
45
+ TORCH_CHECK(edge_points.value().size(0) == voxels.size(0),
46
+ "edge_points must have one row per voxel");
47
+ }
48
+ if (edge_normals.has_value() && edge_normals.value().defined()) {
49
+ CHECK_INPUT(edge_normals.value());
50
+ TORCH_CHECK(edge_normals.value().dim() == 3 && edge_normals.value().size(1) == 12 && edge_normals.value().size(2) == 3,
51
+ "edge_normals must have shape (M, 12, 3)");
52
+ TORCH_CHECK(edge_normals.value().size(0) == voxels.size(0),
53
+ "edge_normals must have one row per voxel");
54
+ }
39
55
 
40
56
  return conquer3d::ops::dual_contouring(
41
57
  grid_vertices,
@@ -45,7 +61,9 @@ std::tuple<torch::Tensor, torch::Tensor, std::optional<torch::Tensor>> dual_cont
45
61
  colors,
46
62
  voxel_vertices,
47
63
  iso,
48
- quad_split
64
+ quad_split,
65
+ edge_points,
66
+ edge_normals
49
67
  );
50
68
  }
51
69
 
@@ -106,6 +124,7 @@ void bind_ops_dc(py::module &m) {
106
124
  py::arg("grid_normals") = py::none(), py::arg("colors") = py::none(),
107
125
  py::arg("voxel_vertices") = py::none(),
108
126
  py::arg("iso") = 0.0f, py::arg("quad_split") = true,
127
+ py::arg("edge_points") = py::none(), py::arg("edge_normals") = py::none(),
109
128
  R"pbdoc(
110
129
  Extracts a sharp-feature preserving surface mesh using Dual Contouring with GPU QEF solver (Ju et al. 2002) or precomputed voxel vertices.
111
130
 
@@ -21,7 +21,9 @@ std::tuple<torch::Tensor, torch::Tensor, std::optional<torch::Tensor>> dual_marc
21
21
  std::optional<torch::Tensor> voxel_vertices,
22
22
  float iso,
23
23
  bool quad_split,
24
- int project_iters
24
+ int project_iters,
25
+ std::optional<torch::Tensor> edge_points,
26
+ std::optional<torch::Tensor> edge_normals
25
27
  ) {
26
28
  CHECK_INPUT(grid_vertices);
27
29
  CHECK_INPUT(voxels);
@@ -33,6 +35,15 @@ std::tuple<torch::Tensor, torch::Tensor, std::optional<torch::Tensor>> dual_marc
33
35
  if (voxel_vertices.has_value() && voxel_vertices.value().defined()) {
34
36
  CHECK_INPUT(voxel_vertices.value());
35
37
  }
38
+ for (auto *t : {&edge_points, &edge_normals}) {
39
+ if (t->has_value() && t->value().defined()) {
40
+ CHECK_INPUT(t->value());
41
+ TORCH_CHECK(t->value().dim() == 3 && t->value().size(1) == 12 && t->value().size(2) == 3,
42
+ "edge_points/edge_normals must have shape (M, 12, 3)");
43
+ TORCH_CHECK(t->value().size(0) == voxels.size(0),
44
+ "edge_points/edge_normals must have one row per voxel");
45
+ }
46
+ }
36
47
 
37
48
  return conquer3d::ops::dual_marching_cubes(
38
49
  grid_vertices,
@@ -42,7 +53,9 @@ std::tuple<torch::Tensor, torch::Tensor, std::optional<torch::Tensor>> dual_marc
42
53
  voxel_vertices,
43
54
  iso,
44
55
  quad_split,
45
- project_iters
56
+ project_iters,
57
+ edge_points,
58
+ edge_normals
46
59
  );
47
60
  }
48
61
 
@@ -99,6 +112,7 @@ void bind_ops_dmc(py::module &m) {
99
112
  py::arg("grid_vertices"), py::arg("voxels"), py::arg("sdf"),
100
113
  py::arg("colors") = py::none(), py::arg("voxel_vertices") = py::none(),
101
114
  py::arg("iso") = 0.0f, py::arg("quad_split") = true, py::arg("project_iters") = 5,
115
+ py::arg("edge_points") = py::none(), py::arg("edge_normals") = py::none(),
102
116
  R"pbdoc(
103
117
  Extracts a watertight 2-manifold surface mesh using Differentiable Dual Marching Cubes (Schaefer & Warren 2004) or precomputed voxel vertices.
104
118
 
@@ -144,6 +144,7 @@ __host__ __device__ __forceinline__ void symmetric_eigen_3x3(
144
144
  * @param cell_min Minimum AABB coordinate of the voxel cell.
145
145
  * @param cell_max Maximum AABB coordinate of the voxel cell.
146
146
  * @param svd_tolerance Relative eigenvalue threshold for pseudoinverse (default: 0.01).
147
+ * @param cell_expand Scale applied to the cell half-extents before clamping (default: 2.0).
147
148
  * @return Optimal inner vertex position x*.
148
149
  */
149
150
  __host__ __device__ __forceinline__ float3 solve_qef(
@@ -152,12 +153,18 @@ __host__ __device__ __forceinline__ float3 solve_qef(
152
153
  int count,
153
154
  const float3 &cell_min,
154
155
  const float3 &cell_max,
155
- float svd_tolerance = 0.01f
156
+ float svd_tolerance = 0.01f,
157
+ float cell_expand = 2.0f
156
158
  ) {
157
159
  if (count <= 0) {
158
160
  return (cell_min + cell_max) * 0.5f;
159
161
  }
160
162
 
163
+ const float3 cell_centre = (cell_min + cell_max) * 0.5f;
164
+ const float3 cell_half = (cell_max - cell_min) * (0.5f * cell_expand);
165
+ const float3 clamp_min = cell_centre - cell_half;
166
+ const float3 clamp_max = cell_centre + cell_half;
167
+
161
168
  // 1. Compute Mass Point (Centroid)
162
169
  float3 mass_point = make_float3(0.0f, 0.0f, 0.0f);
163
170
  for (int i = 0; i < count; ++i) {
@@ -166,7 +173,7 @@ __host__ __device__ __forceinline__ float3 solve_qef(
166
173
  mass_point = mass_point * (1.0f / (float)count);
167
174
 
168
175
  if (count == 1) {
169
- return maths::clamp(pts[0], cell_min, cell_max);
176
+ return maths::clamp(pts[0], clamp_min, clamp_max);
170
177
  }
171
178
 
172
179
  // 2. Build Shifted Normal Equation System: (A^T A) y = b_tilde
@@ -224,9 +231,9 @@ __host__ __device__ __forceinline__ float3 solve_qef(
224
231
  y = y + v2 * proj;
225
232
  }
226
233
 
227
- // 5. Unshift and Clamp to Voxel AABB
234
+ // 5. Unshift and Clamp to the (optionally widened) Voxel AABB
228
235
  float3 x = mass_point + y;
229
- x = maths::clamp(x, cell_min, cell_max);
236
+ x = maths::clamp(x, clamp_min, clamp_max);
230
237
 
231
238
  return x;
232
239
  }
@@ -138,6 +138,8 @@ __global__ void compute_dual_vertices_kernel(
138
138
  const int *__restrict__ voxels,
139
139
  const float *__restrict__ sdf,
140
140
  const float3 *__restrict__ grid_normals,
141
+ const float3 *__restrict__ edge_points,
142
+ const float3 *__restrict__ edge_normals,
141
143
  float iso,
142
144
  int num_voxels,
143
145
  float3 *__restrict__ dual_vertices,
@@ -184,11 +186,27 @@ __global__ void compute_dual_vertices_kernel(
184
186
  float t = (iso - s0) / (s1 - s0);
185
187
  t = fmaxf(0.0f, fminf(1.0f, t));
186
188
 
187
- float3 pt = p[v0] + (p[v1] - p[v0]) * t;
189
+ float3 pt;
190
+ float3 n;
191
+ bool have_hermite = (edge_points != nullptr && edge_normals != nullptr);
192
+ if (have_hermite) {
193
+ pt = edge_points[m * 12 + e];
194
+ float3 hn = edge_normals[m * 12 + e];
195
+ float hlen = maths::norm(hn);
196
+ if (hlen > 1e-8f) {
197
+ n = hn * (1.0f / hlen);
198
+ } else {
199
+ have_hermite = false;
200
+ }
201
+ }
202
+ if (!have_hermite) {
203
+ pt = p[v0] + (p[v1] - p[v0]) * t;
204
+ }
188
205
  pts[count] = pt;
189
206
 
190
- float3 n;
191
- if (grid_normals != nullptr) {
207
+ if (have_hermite) {
208
+ // normal already set from the supplied Hermite data
209
+ } else if (grid_normals != nullptr) {
192
210
  float3 n0 = grid_normals[c_idx[v0]];
193
211
  float3 n1 = grid_normals[c_idx[v1]];
194
212
  float3 n_interp = n0 + (n1 - n0) * t;
@@ -665,7 +683,9 @@ std::tuple<at::Tensor, at::Tensor, c10::optional<at::Tensor>> dual_contouring(
665
683
  const c10::optional<at::Tensor> &colors,
666
684
  const c10::optional<at::Tensor> &voxel_vertices,
667
685
  float iso,
668
- bool quad_split
686
+ bool quad_split,
687
+ const c10::optional<at::Tensor> &edge_points,
688
+ const c10::optional<at::Tensor> &edge_normals
669
689
  ) {
670
690
  at::cuda::CUDAGuard device_guard(grid_vertices.device());
671
691
  auto allocator = at::cuda::ThrustAllocator();
@@ -705,12 +725,18 @@ std::tuple<at::Tensor, at::Tensor, c10::optional<at::Tensor>> dual_contouring(
705
725
  dual_vertices = at::empty({num_voxels, 3}, grid_vertices.options());
706
726
  source_dual_vertices_ptr = reinterpret_cast<const float3*>(dual_vertices.data_ptr<float>());
707
727
  const float3 *normals_ptr = grid_normals.has_value() ? reinterpret_cast<const float3*>(grid_normals.value().data_ptr<float>()) : nullptr;
728
+ const float3 *ep_ptr = (edge_points.has_value() && edge_points.value().defined() && edge_points.value().numel() > 0)
729
+ ? reinterpret_cast<const float3*>(edge_points.value().data_ptr<float>()) : nullptr;
730
+ const float3 *en_ptr = (edge_normals.has_value() && edge_normals.value().defined() && edge_normals.value().numel() > 0)
731
+ ? reinterpret_cast<const float3*>(edge_normals.value().data_ptr<float>()) : nullptr;
708
732
 
709
733
  compute_dual_vertices_kernel<<<blocks, threads, 0, at::cuda::getCurrentCUDAStream()>>>(
710
734
  reinterpret_cast<const float3*>(grid_vertices.data_ptr<float>()),
711
735
  voxels.data_ptr<int>(),
712
736
  sdf.data_ptr<float>(),
713
737
  normals_ptr,
738
+ ep_ptr,
739
+ en_ptr,
714
740
  iso,
715
741
  num_voxels,
716
742
  reinterpret_cast<float3*>(dual_vertices.data_ptr<float>()),
@@ -910,6 +936,8 @@ std::tuple<at::Tensor, c10::optional<at::Tensor>> dual_contouring_backward(
910
936
  voxels.data_ptr<int>(),
911
937
  sdf.data_ptr<float>(),
912
938
  normals_ptr,
939
+ nullptr,
940
+ nullptr,
913
941
  iso,
914
942
  num_voxels,
915
943
  reinterpret_cast<float3*>(dual_vertices.data_ptr<float>()),
@@ -30,7 +30,9 @@ std::tuple<at::Tensor, at::Tensor, c10::optional<at::Tensor>> dual_contouring(
30
30
  const c10::optional<at::Tensor> &colors = c10::nullopt,
31
31
  const c10::optional<at::Tensor> &voxel_vertices = c10::nullopt,
32
32
  float iso = 0.0f,
33
- bool quad_split = true
33
+ bool quad_split = true,
34
+ const c10::optional<at::Tensor> &edge_points = c10::nullopt,
35
+ const c10::optional<at::Tensor> &edge_normals = c10::nullopt
34
36
  );
35
37
 
36
38
  /**
@@ -368,6 +368,8 @@ __global__ void dmc_extract_dual_vertices_and_edges_kernel(
368
368
  const float *__restrict__ sdf,
369
369
  const float *__restrict__ colors,
370
370
  const float3 *__restrict__ precomputed_vertices,
371
+ const float3 *__restrict__ edge_points,
372
+ const float3 *__restrict__ edge_normals,
371
373
  int num_channels,
372
374
  const int *__restrict__ vert_offsets,
373
375
  const int *__restrict__ edge_offsets,
@@ -436,8 +438,22 @@ __global__ void dmc_extract_dual_vertices_and_edges_kernel(
436
438
  int dual_vert_idx = v_base + c;
437
439
  float u_sum = 0.0f, v_sum = 0.0f, w_sum = 0.0f;
438
440
 
441
+ const bool use_hermite = (edge_points != nullptr && edge_normals != nullptr);
442
+ float3 h_pts[12];
443
+ float3 h_nrm[12];
444
+ int h_count = 0;
445
+
439
446
  for (int i = 0; i < sz; ++i) {
440
447
  int e = c_edges[c][i];
448
+ if (use_hermite && h_count < 12) {
449
+ float3 hn = edge_normals[m * 12 + e];
450
+ float hlen = maths::norm(hn);
451
+ if (hlen > 1e-8f) {
452
+ h_pts[h_count] = edge_points[m * 12 + e];
453
+ h_nrm[h_count] = hn * (1.0f / hlen);
454
+ h_count++;
455
+ }
456
+ }
441
457
  int v0 = dmc_edge_corners[e][0];
442
458
  int v1 = dmc_edge_corners[e][1];
443
459
  float s0 = s[v0];
@@ -476,6 +492,16 @@ __global__ void dmc_extract_dual_vertices_and_edges_kernel(
476
492
  u = fmaxf(0.0f, fminf(1.0f, (pt.x - c_min.x) / dx));
477
493
  v = fmaxf(0.0f, fminf(1.0f, (pt.y - c_min.y) / dy));
478
494
  w = fmaxf(0.0f, fminf(1.0f, (pt.z - c_min.z) / dz));
495
+ } else if (use_hermite && h_count > 0) {
496
+ // Solve the quadratic error function over THIS contour's Hermite data.
497
+ // Restricting the solve to one contour is what keeps a cell that carries
498
+ // several contours emitting several distinct vertices, so the manifold
499
+ // guarantee survives while each vertex still lands on its own feature.
500
+ float3 pt = maths::solve_qef(h_pts, h_nrm, h_count, c_min, c_max, 0.01f);
501
+ out_vertices[dual_vert_idx] = pt;
502
+ u = fmaxf(0.0f, fminf(1.0f, (pt.x - c_min.x) / dx));
503
+ v = fmaxf(0.0f, fminf(1.0f, (pt.y - c_min.y) / dy));
504
+ w = fmaxf(0.0f, fminf(1.0f, (pt.z - c_min.z) / dz));
479
505
  } else {
480
506
  // Newton-Raphson Level-Set Projection
481
507
  for (int iter = 0; iter < project_iters; ++iter) {
@@ -802,7 +828,9 @@ std::tuple<at::Tensor, at::Tensor, c10::optional<at::Tensor>> dual_marching_cube
802
828
  const c10::optional<at::Tensor> &voxel_vertices,
803
829
  float iso,
804
830
  bool quad_split,
805
- int project_iters
831
+ int project_iters,
832
+ const c10::optional<at::Tensor> &edge_points,
833
+ const c10::optional<at::Tensor> &edge_normals
806
834
  ) {
807
835
  TORCH_CHECK(grid_vertices.is_cuda(), "grid_vertices must be a CUDA tensor");
808
836
  TORCH_CHECK(voxels.is_cuda(), "voxels must be a CUDA tensor");
@@ -889,12 +917,19 @@ std::tuple<at::Tensor, at::Tensor, c10::optional<at::Tensor>> dual_marching_cube
889
917
  ? reinterpret_cast<const float3*>(voxel_vertices->data_ptr<float>())
890
918
  : nullptr;
891
919
 
920
+ const float3 *dmc_ep_ptr = (edge_points.has_value() && edge_points->defined() && edge_points->numel() > 0)
921
+ ? reinterpret_cast<const float3*>(edge_points->data_ptr<float>()) : nullptr;
922
+ const float3 *dmc_en_ptr = (edge_normals.has_value() && edge_normals->defined() && edge_normals->numel() > 0)
923
+ ? reinterpret_cast<const float3*>(edge_normals->data_ptr<float>()) : nullptr;
924
+
892
925
  dmc_extract_dual_vertices_and_edges_kernel<<<grid, block, 0, stream>>>(
893
926
  reinterpret_cast<const float3*>(grid_vertices.data_ptr<float>()),
894
927
  voxels.data_ptr<int>(),
895
928
  sdf.data_ptr<float>(),
896
929
  colors_ptr,
897
930
  precomputed_ptr,
931
+ dmc_ep_ptr,
932
+ dmc_en_ptr,
898
933
  num_channels,
899
934
  vert_offsets.data_ptr<int>(),
900
935
  edge_offsets.data_ptr<int>(),
@@ -29,7 +29,9 @@ std::tuple<at::Tensor, at::Tensor, c10::optional<at::Tensor>> dual_marching_cube
29
29
  const c10::optional<at::Tensor> &voxel_vertices = c10::nullopt,
30
30
  float iso = 0.0f,
31
31
  bool quad_split = true,
32
- int project_iters = 5
32
+ int project_iters = 5,
33
+ const c10::optional<at::Tensor> &edge_points = c10::nullopt,
34
+ const c10::optional<at::Tensor> &edge_normals = c10::nullopt
33
35
  );
34
36
 
35
37
  /**
@@ -13,6 +13,7 @@ This package provides:
13
13
  from .marching_cubes import marching_cubes
14
14
  from .marching_cubes_asymptotic import marching_cubes_asymptotic, mca
15
15
  from .dual_contouring import dual_contouring, dc
16
+ from .hermite import compute_hermite_from_mesh, compute_hermite_from_field, DC_EDGE_CORNERS
16
17
  from .dual_marching_cubes import dual_marching_cubes, dmc
17
18
  from .diff_marching_cubes import diff_marching_cubes
18
19
 
@@ -31,6 +32,9 @@ __all__ = [
31
32
  "mca",
32
33
  "dual_contouring",
33
34
  "dc",
35
+ "compute_hermite_from_mesh",
36
+ "compute_hermite_from_field",
37
+ "DC_EDGE_CORNERS",
34
38
  "dual_marching_cubes",
35
39
  "dmc",
36
40
  "diff_marching_cubes",
@@ -97,7 +97,9 @@ def dual_contouring(
97
97
  colors: Optional[torch.Tensor] = None,
98
98
  voxel_vertices: Optional[torch.Tensor] = None,
99
99
  iso: float = 0.0,
100
- quad_split: bool = True
100
+ quad_split: bool = True,
101
+ edge_points: Optional[torch.Tensor] = None,
102
+ edge_normals: Optional[torch.Tensor] = None
101
103
  ) -> Union[Tuple[torch.Tensor, torch.Tensor], Tuple[torch.Tensor, torch.Tensor, torch.Tensor]]:
102
104
  """Extracts sharp surface meshes from volumetric scalar fields using Differentiable Dual Contouring.
103
105
 
@@ -123,6 +125,16 @@ def dual_contouring(
123
125
  quad_split (bool, optional): If True, splits dual quadrilaterals into 2 triangles using
124
126
  the optimal Delaunay angle criterion; if False, returns quads of shape `(Q, 4)`.
125
127
  Defaults to True.
128
+ edge_points (torch.Tensor, optional): Float32 tensor of shape `(M, 12, 3)` giving the
129
+ exact surface intersection point of each voxel-local edge, in the
130
+ :data:`conquer3d.ops.hermite.DC_EDGE_CORNERS` order. Supplying this together with
131
+ `edge_normals` is what lets Dual Contouring reproduce sharp creases exactly: the
132
+ QEF is then built from true Hermite data instead of from normals interpolated
133
+ between grid vertices, which blend across a crease and round it off. Entries for
134
+ edges that are not sign-crossing are ignored. Defaults to None.
135
+ edge_normals (torch.Tensor, optional): Float32 tensor of shape `(M, 12, 3)` giving the
136
+ exact surface normal at each `edge_points` entry. Must accompany `edge_points`;
137
+ either both are used or neither. Defaults to None.
126
138
 
127
139
  Returns:
128
140
  Union[Tuple[torch.Tensor, torch.Tensor], Tuple[torch.Tensor, torch.Tensor, torch.Tensor]]:
@@ -165,8 +177,22 @@ def dual_contouring(
165
177
  # grid_vertices, voxels, sdf, grid_normals, colors, voxel_vertices, iso, quad_split
166
178
  # )
167
179
  # else:
180
+ if (edge_points is None) != (edge_normals is None):
181
+ raise ValueError("edge_points and edge_normals must be supplied together")
182
+ if edge_points is not None:
183
+ edge_points = edge_points.contiguous().float()
184
+ edge_normals = edge_normals.contiguous().float()
185
+ for name, t in (("edge_points", edge_points), ("edge_normals", edge_normals)):
186
+ if not t.is_cuda:
187
+ raise RuntimeError(f"{name} must be on CUDA")
188
+ if t.shape != (voxels.shape[0], 12, 3):
189
+ raise ValueError(
190
+ f"{name} must have shape ({voxels.shape[0]}, 12, 3), got {tuple(t.shape)}"
191
+ )
192
+
168
193
  verts, faces, out_colors = _C.dual_contouring(
169
- grid_vertices, voxels, sdf, grid_normals, colors, voxel_vertices, iso, quad_split
194
+ grid_vertices, voxels, sdf, grid_normals, colors, voxel_vertices, iso, quad_split,
195
+ edge_points, edge_normals
170
196
  )
171
197
 
172
198
  if colors is None:
@@ -95,7 +95,9 @@ def dual_marching_cubes(
95
95
  voxel_vertices: Optional[torch.Tensor] = None,
96
96
  iso: float = 0.0,
97
97
  quad_split: bool = True,
98
- project_iters: int = 5
98
+ project_iters: int = 5,
99
+ edge_points: Optional[torch.Tensor] = None,
100
+ edge_normals: Optional[torch.Tensor] = None
99
101
  ) -> Union[Tuple[torch.Tensor, torch.Tensor], Tuple[torch.Tensor, torch.Tensor, torch.Tensor]]:
100
102
  """Extracts a strictly 2-manifold surface mesh from a voxel grid using Differentiable Dual Marching Cubes.
101
103
 
@@ -120,6 +122,17 @@ def dual_marching_cubes(
120
122
  quad_split (bool, optional): If True, splits dual quadrilaterals into 2 triangles using
121
123
  the optimal Delaunay angle criterion; if False, returns quads of shape `(Q, 4)`.
122
124
  Defaults to True.
125
+ edge_points (torch.Tensor, optional): Float32 tensor of shape `(M, 12, 3)` giving the
126
+ exact surface intersection of each voxel-local edge, in
127
+ :data:`conquer3d.ops.hermite.DC_EDGE_CORNERS` order. Supplied together with
128
+ `edge_normals`, this replaces the centroid-and-projection placement with a
129
+ quadratic error function solved per contour, which is what lets Dual Marching
130
+ Cubes reproduce sharp creases instead of rounding them. Solving per contour
131
+ rather than per cell keeps a cell that carries several contours emitting
132
+ several distinct vertices, preserving the manifold guarantee. Defaults to None.
133
+ edge_normals (torch.Tensor, optional): Float32 tensor of shape `(M, 12, 3)` giving the
134
+ exact surface normal at each `edge_points` entry. Must accompany `edge_points`.
135
+ Defaults to None.
123
136
  project_iters (int, optional): Number of Newton-Raphson level-set projection iterations.
124
137
  Defaults to 5.
125
138
 
@@ -160,8 +173,22 @@ def dual_marching_cubes(
160
173
  # grid_vertices, voxels, sdf, colors, voxel_vertices, iso, quad_split, project_iters
161
174
  # )
162
175
  # else:
176
+ if (edge_points is None) != (edge_normals is None):
177
+ raise ValueError("edge_points and edge_normals must be supplied together")
178
+ if edge_points is not None:
179
+ edge_points = edge_points.contiguous().float()
180
+ edge_normals = edge_normals.contiguous().float()
181
+ for name, t in (("edge_points", edge_points), ("edge_normals", edge_normals)):
182
+ if not t.is_cuda:
183
+ raise RuntimeError(f"{name} must be on CUDA")
184
+ if t.shape != (voxels.shape[0], 12, 3):
185
+ raise ValueError(
186
+ f"{name} must have shape ({voxels.shape[0]}, 12, 3), got {tuple(t.shape)}"
187
+ )
188
+
163
189
  verts, faces, out_colors = _C.dual_marching_cubes(
164
- grid_vertices, voxels, sdf, colors, voxel_vertices, iso, quad_split, project_iters
190
+ grid_vertices, voxels, sdf, colors, voxel_vertices, iso, quad_split, project_iters,
191
+ edge_points, edge_normals
165
192
  )
166
193
 
167
194
  if colors is None: