DeepSDFStruct 1.7.2__py3-none-any.whl
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.
- DeepSDFStruct/SDF.py +1734 -0
- DeepSDFStruct/__init__.py +66 -0
- DeepSDFStruct/deep_sdf/__init__.py +69 -0
- DeepSDFStruct/deep_sdf/create_screenshots_from_plyfiles.py +65 -0
- DeepSDFStruct/deep_sdf/data.py +258 -0
- DeepSDFStruct/deep_sdf/metrics/__init__.py +14 -0
- DeepSDFStruct/deep_sdf/metrics/mesh_to_analytical.py +24 -0
- DeepSDFStruct/deep_sdf/models.py +197 -0
- DeepSDFStruct/deep_sdf/networks/__init__.py +8 -0
- DeepSDFStruct/deep_sdf/networks/analytic_round_cross.py +105 -0
- DeepSDFStruct/deep_sdf/networks/deep_sdf_decoder.py +155 -0
- DeepSDFStruct/deep_sdf/networks/hierarchical_deep_sdf_decoder.py +139 -0
- DeepSDFStruct/deep_sdf/networks/hierarchical_positional_sdf_decoder.py +190 -0
- DeepSDFStruct/deep_sdf/networks/resnet_positional_sdf_decoder.py +108 -0
- DeepSDFStruct/deep_sdf/nn_utils.py +70 -0
- DeepSDFStruct/deep_sdf/plotting.py +143 -0
- DeepSDFStruct/deep_sdf/reconstruction.py +165 -0
- DeepSDFStruct/deep_sdf/training.py +840 -0
- DeepSDFStruct/deep_sdf/workspace.py +363 -0
- DeepSDFStruct/design_of_experiments.py +235 -0
- DeepSDFStruct/flexicubes/__init__.py +15 -0
- DeepSDFStruct/flexicubes/flexicubes.py +1005 -0
- DeepSDFStruct/flexicubes/tables.py +2338 -0
- DeepSDFStruct/flexisquares/__init__.py +15 -0
- DeepSDFStruct/flexisquares/flexisquares.py +967 -0
- DeepSDFStruct/flexisquares/tables.py +63 -0
- DeepSDFStruct/lattice_structure.py +254 -0
- DeepSDFStruct/local_shapes.py +226 -0
- DeepSDFStruct/mesh.py +1184 -0
- DeepSDFStruct/optimization.py +357 -0
- DeepSDFStruct/parametrization.py +215 -0
- DeepSDFStruct/pretrained_models.py +128 -0
- DeepSDFStruct/sampling.py +670 -0
- DeepSDFStruct/sdf_operations.py +990 -0
- DeepSDFStruct/sdf_primitives.py +1416 -0
- DeepSDFStruct/splinepy_unitcells/__init__.py +35 -0
- DeepSDFStruct/splinepy_unitcells/chi_3D.py +1617 -0
- DeepSDFStruct/splinepy_unitcells/cross_lattice.py +165 -0
- DeepSDFStruct/splinepy_unitcells/double_lattice_extruded.py +298 -0
- DeepSDFStruct/splinepy_unitcells/snappy_3d.py +544 -0
- DeepSDFStruct/torch_spline.py +446 -0
- DeepSDFStruct/trained_models/analytic_round_cross/LatentCodes/latest.pth +0 -0
- DeepSDFStruct/trained_models/analytic_round_cross/ModelParameters/latest.pth +0 -0
- DeepSDFStruct/trained_models/analytic_round_cross/OptimizerParameters/latest.pth +0 -0
- DeepSDFStruct/trained_models/analytic_round_cross/specs.json +45 -0
- DeepSDFStruct/trained_models/chi_and_cross/LatentCodes/latest.pth +0 -0
- DeepSDFStruct/trained_models/chi_and_cross/Logs.pth +0 -0
- DeepSDFStruct/trained_models/chi_and_cross/ModelParameters/latest.pth +0 -0
- DeepSDFStruct/trained_models/chi_and_cross/OptimizerParameters/latest.pth +0 -0
- DeepSDFStruct/trained_models/chi_and_cross/specs.json +76 -0
- DeepSDFStruct/trained_models/primitives/LatentCodes/latest.pth +0 -0
- DeepSDFStruct/trained_models/primitives/ModelParameters/latest.pth +0 -0
- DeepSDFStruct/trained_models/primitives/OptimizerParameters/latest.pth +0 -0
- DeepSDFStruct/trained_models/primitives/specs.json +46 -0
- DeepSDFStruct/trained_models/primitives_2d/LatentCodes/latest.pth +0 -0
- DeepSDFStruct/trained_models/primitives_2d/ModelParameters/latest.pth +0 -0
- DeepSDFStruct/trained_models/primitives_2d/OptimizerParameters/latest.pth +0 -0
- DeepSDFStruct/trained_models/primitives_2d/specs.json +46 -0
- DeepSDFStruct/trained_models/round_cross/LatentCodes/latest.pth +0 -0
- DeepSDFStruct/trained_models/round_cross/Logs.pth +0 -0
- DeepSDFStruct/trained_models/round_cross/ModelParameters/latest.pth +0 -0
- DeepSDFStruct/trained_models/round_cross/OptimizerParameters/latest.pth +0 -0
- DeepSDFStruct/trained_models/round_cross/specs.json +45 -0
- DeepSDFStruct/trained_models/test_experiment/LatentCodes/latent_code_data_map.json +106 -0
- DeepSDFStruct/trained_models/test_experiment/specs.json +45 -0
- DeepSDFStruct/trained_models/test_experiment/training_summary.json +10 -0
- DeepSDFStruct/trained_models/test_experiment_hierarchical/LatentCodes/latent_code_data_map.json +106 -0
- DeepSDFStruct/trained_models/test_experiment_hierarchical/specs.json +45 -0
- DeepSDFStruct/trained_models/test_experiment_hierarchical/training_summary.json +10 -0
- DeepSDFStruct/trained_models/test_experiment_homogenization/LatentCodes/latent_code_data_map.json +5006 -0
- DeepSDFStruct/trained_models/test_experiment_homogenization/LatentCodes/latest.pth +0 -0
- DeepSDFStruct/trained_models/test_experiment_homogenization/Logs.png +0 -0
- DeepSDFStruct/trained_models/test_experiment_homogenization/ModelParameters/latest.pth +0 -0
- DeepSDFStruct/trained_models/test_experiment_homogenization/OptimizerParameters/latest.pth +0 -0
- DeepSDFStruct/trained_models/test_experiment_homogenization/specs.json +83 -0
- DeepSDFStruct/trained_models/test_experiment_homogenization/training_summary.json +10 -0
- DeepSDFStruct/utils.py +96 -0
- DeepSDFStruct/visualization/latent_viewer.py +211 -0
- DeepSDFStruct/visualization/latent_viewer_cli.py +8 -0
- benchmarks/generate_sdf_showcase.py +622 -0
- benchmarks/sdf_from_mesh_benchmark.py +52 -0
- benchmarks/sdf_from_mesh_comparison.md +33 -0
- benchmarks/sdf_showcase/operations/boolean/difference_sphere_box.png +0 -0
- benchmarks/sdf_showcase/operations/boolean/union_sphere_box.png +0 -0
- benchmarks/sdf_showcase/operations/transformations/circular_array_sphere.png +0 -0
- benchmarks/sdf_showcase/operations/transformations/dilate_sphere.png +0 -0
- benchmarks/sdf_showcase/operations/transformations/mirror_sphere.png +0 -0
- benchmarks/sdf_showcase/operations/transformations/repeat_sphere.png +0 -0
- benchmarks/sdf_showcase/operations/transformations/revolve_circle.png +0 -0
- benchmarks/sdf_showcase/operations/transformations/shell_sphere.png +0 -0
- benchmarks/sdf_showcase/operations/transformations/twist_torus.png +0 -0
- benchmarks/sdf_showcase/primitives/2D/circle.png +0 -0
- benchmarks/sdf_showcase/primitives/2D/equilateral_triangle.png +0 -0
- benchmarks/sdf_showcase/primitives/2D/hexagon.png +0 -0
- benchmarks/sdf_showcase/primitives/2D/polygon_pentagon.png +0 -0
- benchmarks/sdf_showcase/primitives/2D/rectangle.png +0 -0
- benchmarks/sdf_showcase/primitives/2D/rounded_rectangle.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/box.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/capped_cone.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/capped_cylinder.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/capsule.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/cone.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/corner_spheres.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/cross_ms.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/cylinder.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/dodecahedron.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/ellipsoid.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/icosahedron.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/octahedron.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/pyramid.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/rounded_box.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/rounded_cone.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/rounded_cylinder.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/sphere.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/tetrahedron.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/torus.png +0 -0
- benchmarks/sdf_showcase/primitives/3D/wireframe_box.png +0 -0
- deepsdfstruct-1.7.2.dist-info/METADATA +44 -0
- deepsdfstruct-1.7.2.dist-info/RECORD +165 -0
- deepsdfstruct-1.7.2.dist-info/WHEEL +5 -0
- deepsdfstruct-1.7.2.dist-info/licenses/LICENSE +208 -0
- deepsdfstruct-1.7.2.dist-info/licenses/NOTICE +67 -0
- deepsdfstruct-1.7.2.dist-info/scm_file_list.json +178 -0
- deepsdfstruct-1.7.2.dist-info/scm_version.json +8 -0
- deepsdfstruct-1.7.2.dist-info/top_level.txt +4 -0
- docs/Makefile +20 -0
- docs/api_reference.rst +6 -0
- docs/conf.py +60 -0
- docs/index.rst +136 -0
- docs/make.bat +35 -0
- docs/qr_code_github.png +0 -0
- docs/qr_code_paper.png +0 -0
- docs/readme_images/example_output_01.png +0 -0
- docs/readme_images/example_output_02.png +0 -0
- docs/readme_images/example_output_03.png +0 -0
- docs/readme_images/example_output_04.png +0 -0
- tests/data/chairs/1005.obj +282 -0
- tests/data/chairs/1024.obj +222 -0
- tests/data/chairs/README.md +1 -0
- tests/data/circular_balls_test_case.stl +0 -0
- tests/data/cone.stl +1794 -0
- tests/data/example_disconnectd_mesh.inp +1784 -0
- tests/data/example_line_mesh.vtk +0 -0
- tests/data/stanford_bunny.stl +0 -0
- tests/data/sweep_test_case.stl +0 -0
- tests/test_DOE.py +49 -0
- tests/test_elongate_sdf.py +195 -0
- tests/test_flexisquares.py +187 -0
- tests/test_generate_dataset.py +76 -0
- tests/test_lattice_evaluation.py +137 -0
- tests/test_mesh_export.py +137 -0
- tests/test_mesh_functions.py +49 -0
- tests/test_networks.py +71 -0
- tests/test_pretrained_models.py +40 -0
- tests/test_reconstruction.py +101 -0
- tests/test_sdf_from_mesh.py +52 -0
- tests/test_sdf_functions.py +117 -0
- tests/test_sdf_primitives.py +668 -0
- tests/test_sdf_trimesh_comparison.py +482 -0
- tests/test_splinepy_unitcells.py +64 -0
- tests/test_structural_optimization.py +178 -0
- tests/test_torch_spline.py +216 -0
- tests/test_train_model.py +110 -0
- tests/test_training_data_ids.py +41 -0
- tests/tmp_outputs/temp_file.txt +0 -0
|
@@ -0,0 +1,1416 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Primitive SDF Shapes
|
|
3
|
+
====================
|
|
4
|
+
|
|
5
|
+
This module provides basic geometric primitive SDFs that can be used as building
|
|
6
|
+
blocks for more complex geometries. All primitives inherit from SDFBase and can
|
|
7
|
+
be combined using boolean operations.
|
|
8
|
+
|
|
9
|
+
Available Primitives
|
|
10
|
+
--------------------
|
|
11
|
+
- SphereSDF: Sphere with specified center and radius
|
|
12
|
+
- CylinderSDF: Infinite cylinder along a coordinate axis
|
|
13
|
+
- TorusSDF: Torus with major and minor radii
|
|
14
|
+
- PlaneSDF: Half-space defined by a point and normal vector
|
|
15
|
+
- CornerSpheresSDF: Cube with spherical cutouts at corners
|
|
16
|
+
- CrossMsSDF: Cross-shaped structure (intersection of three cylinders)
|
|
17
|
+
- RoundedBoxSDF: 3D axis-aligned box with rounded corners
|
|
18
|
+
- WireframeBoxSDF: 3D wireframe box SDF
|
|
19
|
+
- CapsuleSDF: SDF for a capsule (a line segment with a radius)
|
|
20
|
+
- EllipsoidSDF: SDF for an ellipsoid
|
|
21
|
+
- PyramidSDF: SDF for a pyramid
|
|
22
|
+
|
|
23
|
+
All primitives support PyTorch's automatic differentiation and can be used
|
|
24
|
+
in optimization workflows.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from DeepSDFStruct.SDF import SDFBase
|
|
28
|
+
import DeepSDFStruct
|
|
29
|
+
import logging
|
|
30
|
+
import torch
|
|
31
|
+
import numpy as np
|
|
32
|
+
|
|
33
|
+
logger = logging.getLogger(DeepSDFStruct.__name__)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class SphereSDF(SDFBase):
|
|
37
|
+
"""Signed distance function for a sphere.
|
|
38
|
+
|
|
39
|
+
Computes the signed distance from query points to a sphere surface.
|
|
40
|
+
The distance is negative inside the sphere, zero on the surface,
|
|
41
|
+
and positive outside.
|
|
42
|
+
|
|
43
|
+
Parameters
|
|
44
|
+
----------
|
|
45
|
+
center : array-like of shape (3,)
|
|
46
|
+
Center point of the sphere in 3D space.
|
|
47
|
+
radius : float
|
|
48
|
+
Radius of the sphere.
|
|
49
|
+
|
|
50
|
+
Examples
|
|
51
|
+
--------
|
|
52
|
+
>>> # Sphere centered at origin with radius 0.5
|
|
53
|
+
>>> SphereSDF(center=[0, 0, 0], radius=0.5)
|
|
54
|
+
>>>
|
|
55
|
+
>>> # Sphere at different location with larger radius
|
|
56
|
+
>>> SphereSDF(center=[1, 0, 0], radius=1.0)
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def __init__(self, center, radius):
|
|
60
|
+
super().__init__()
|
|
61
|
+
# make center and radius trainable parameters and ensure correct dtype
|
|
62
|
+
c = torch.as_tensor(center, dtype=torch.float32)
|
|
63
|
+
r = torch.as_tensor(radius, dtype=torch.float32)
|
|
64
|
+
self.center = torch.nn.Parameter(c)
|
|
65
|
+
self.r = torch.nn.Parameter(r.reshape(())) # scalar parameter
|
|
66
|
+
|
|
67
|
+
def _compute(self, queries: torch.Tensor) -> torch.Tensor:
|
|
68
|
+
logger.debug(f"{type(self).__name__}._compute - {queries.shape[0]} points")
|
|
69
|
+
# ensure computations use same dtype/device as queries
|
|
70
|
+
center = self.center.to(device=queries.device, dtype=queries.dtype)
|
|
71
|
+
r = self.r.to(device=queries.device, dtype=queries.dtype)
|
|
72
|
+
return (torch.linalg.norm(queries - center, dim=1) - r).reshape(-1, 1)
|
|
73
|
+
|
|
74
|
+
def _get_domain_bounds(self) -> torch.Tensor:
|
|
75
|
+
# Use same dtype/device as parameters
|
|
76
|
+
center = self.center
|
|
77
|
+
r = self.r
|
|
78
|
+
|
|
79
|
+
# Compute axis-aligned bounding box of the sphere
|
|
80
|
+
lower = center - r
|
|
81
|
+
upper = center + r
|
|
82
|
+
|
|
83
|
+
return torch.stack([lower, upper], dim=0)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class BoxSDF(SDFBase):
|
|
87
|
+
"""Signed distance function for a 3D axis-aligned box.
|
|
88
|
+
|
|
89
|
+
The box is defined by its center point and extents (full widths) along each axis.
|
|
90
|
+
Both center and extents are trainable torch parameters.
|
|
91
|
+
|
|
92
|
+
Parameters
|
|
93
|
+
----------
|
|
94
|
+
center : array-like of shape (3,)
|
|
95
|
+
Center point of the box in 3D space.
|
|
96
|
+
extents : array-like of shape (3,)
|
|
97
|
+
Full widths of the box along x, y, z axes.
|
|
98
|
+
|
|
99
|
+
Examples
|
|
100
|
+
--------
|
|
101
|
+
>>> # Box centered at origin with dimensions 2x1x1
|
|
102
|
+
>>> BoxSDF(center=[0, 0, 0], extents=[2.0, 1.0, 1.0])
|
|
103
|
+
>>>
|
|
104
|
+
>>> # Elongated box in x-direction
|
|
105
|
+
>>> BoxSDF(center=[1, 0, 0], extents=[3.0, 0.5, 0.5])
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
def __init__(self, center, extents):
|
|
109
|
+
"""Initialize BoxSDF with center and extents.
|
|
110
|
+
|
|
111
|
+
Parameters
|
|
112
|
+
----------
|
|
113
|
+
center : array-like of shape (3,)
|
|
114
|
+
Center point of the box in 3D space.
|
|
115
|
+
extents : array-like of shape (3,)
|
|
116
|
+
Full widths of the box along x, y, z axes.
|
|
117
|
+
"""
|
|
118
|
+
super().__init__(geometric_dim=3)
|
|
119
|
+
|
|
120
|
+
c = torch.as_tensor(center, dtype=torch.float32)
|
|
121
|
+
e = torch.as_tensor(extents, dtype=torch.float32)
|
|
122
|
+
|
|
123
|
+
if c.numel() != 3 or e.numel() != 3:
|
|
124
|
+
raise ValueError("center and extents must be length-3 for BoxSDF")
|
|
125
|
+
|
|
126
|
+
self.center = torch.nn.Parameter(c.reshape(3))
|
|
127
|
+
self.extents = torch.nn.Parameter(e.reshape(3))
|
|
128
|
+
|
|
129
|
+
def _compute(self, queries: torch.Tensor) -> torch.Tensor:
|
|
130
|
+
logger.debug(f"{type(self).__name__}._compute - {queries.shape[0]} points")
|
|
131
|
+
center = self.center.to(device=queries.device, dtype=queries.dtype)
|
|
132
|
+
half = self.extents.to(device=queries.device, dtype=queries.dtype) / 2.0
|
|
133
|
+
|
|
134
|
+
q = queries - center # (N,3)
|
|
135
|
+
d = torch.abs(q) - half # (N,3)
|
|
136
|
+
|
|
137
|
+
zero = torch.tensor(0.0, device=queries.device, dtype=queries.dtype)
|
|
138
|
+
|
|
139
|
+
# outside distance
|
|
140
|
+
d_clamped = torch.maximum(d, zero)
|
|
141
|
+
outside_dist = torch.linalg.norm(d_clamped, dim=1)
|
|
142
|
+
|
|
143
|
+
# inside distance (negative inside)
|
|
144
|
+
inside_dist = torch.minimum(
|
|
145
|
+
torch.maximum(torch.maximum(d[:, 0], d[:, 1]), d[:, 2]), zero
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
sdf = (outside_dist + inside_dist).reshape(-1, 1)
|
|
149
|
+
return sdf
|
|
150
|
+
|
|
151
|
+
def _get_domain_bounds(self) -> torch.Tensor:
|
|
152
|
+
center = self.center.detach()
|
|
153
|
+
half = self.extents.detach() / 2.0
|
|
154
|
+
|
|
155
|
+
lower = center - half
|
|
156
|
+
upper = center + half
|
|
157
|
+
|
|
158
|
+
return torch.stack([lower, upper], dim=0)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class CylinderSDF(SDFBase):
|
|
162
|
+
"""Signed distance function for a finite cylinder with exact end caps.
|
|
163
|
+
|
|
164
|
+
Creates a cylinder defined by two endpoints and a radius.
|
|
165
|
+
|
|
166
|
+
Parameters
|
|
167
|
+
----------
|
|
168
|
+
point_a : array-like of shape (3,)
|
|
169
|
+
First endpoint of the cylinder's axis.
|
|
170
|
+
point_b : array-like of shape (3,)
|
|
171
|
+
Second endpoint of the cylinder's axis.
|
|
172
|
+
radius : float
|
|
173
|
+
Radius of the cylinder.
|
|
174
|
+
|
|
175
|
+
Examples
|
|
176
|
+
--------
|
|
177
|
+
>>> # Cylinder along z-axis from -1 to 1 with radius 0.5
|
|
178
|
+
>>> CylinderSDF(point_a=[0, 0, -1], point_b=[0, 0, 1], radius=0.5)
|
|
179
|
+
>>>
|
|
180
|
+
>>> # Cylinder along x-axis
|
|
181
|
+
>>> CylinderSDF(point_a=[-1, 0, 0], point_b=[1, 0, 0], radius=0.3)
|
|
182
|
+
>>>
|
|
183
|
+
>>> # Diagonal cylinder
|
|
184
|
+
>>> CylinderSDF(point_a=[0, 0, 0], point_b=[1, 1, 1], radius=0.2)
|
|
185
|
+
"""
|
|
186
|
+
|
|
187
|
+
def __init__(self, point_a, point_b, radius):
|
|
188
|
+
super().__init__()
|
|
189
|
+
a = torch.as_tensor(point_a, dtype=torch.float32)
|
|
190
|
+
b = torch.as_tensor(point_b, dtype=torch.float32)
|
|
191
|
+
r = torch.as_tensor(radius, dtype=torch.float32)
|
|
192
|
+
self.point_a = torch.nn.Parameter(a)
|
|
193
|
+
self.point_b = torch.nn.Parameter(b)
|
|
194
|
+
self.radius = torch.nn.Parameter(r.reshape(()))
|
|
195
|
+
|
|
196
|
+
def _compute(self, queries: torch.Tensor) -> torch.Tensor:
|
|
197
|
+
logger.debug(f"{type(self).__name__}._compute - {queries.shape[0]} points")
|
|
198
|
+
a = self.point_a.to(device=queries.device, dtype=queries.dtype)
|
|
199
|
+
b = self.point_b.to(device=queries.device, dtype=queries.dtype)
|
|
200
|
+
r = self.radius.to(device=queries.device, dtype=queries.dtype)
|
|
201
|
+
|
|
202
|
+
ba = b - a
|
|
203
|
+
pa = queries - a
|
|
204
|
+
|
|
205
|
+
# Squared length of ba
|
|
206
|
+
baba = torch.sum(ba**2)
|
|
207
|
+
|
|
208
|
+
# Projection of pa onto ba
|
|
209
|
+
paba = torch.sum(pa * ba, dim=1)
|
|
210
|
+
|
|
211
|
+
# Distance to cylinder side
|
|
212
|
+
# x = length(pa * baba - ba * paba) - radius * baba
|
|
213
|
+
pa_scaled = pa * baba
|
|
214
|
+
ba_paba = ba * paba.unsqueeze(1)
|
|
215
|
+
x = torch.sqrt(torch.sum((pa_scaled - ba_paba) ** 2, dim=1)) - r * baba
|
|
216
|
+
|
|
217
|
+
# Distance to end caps
|
|
218
|
+
# y = |paba - baba * 0.5| - baba * 0.5
|
|
219
|
+
y = torch.abs(paba - baba * 0.5) - baba * 0.5
|
|
220
|
+
|
|
221
|
+
# Combine distances
|
|
222
|
+
x2 = x**2
|
|
223
|
+
y2 = y**2 * baba
|
|
224
|
+
|
|
225
|
+
d = torch.where(
|
|
226
|
+
torch.maximum(x, y) < 0,
|
|
227
|
+
-torch.minimum(x2, y2),
|
|
228
|
+
torch.where(x > 0, x2, torch.tensor(0.0, device=x.device))
|
|
229
|
+
+ torch.where(y > 0, y2, torch.tensor(0.0, device=y.device)),
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
return (torch.sign(d) * torch.sqrt(torch.abs(d)) / baba).reshape(-1, 1)
|
|
233
|
+
|
|
234
|
+
def _get_domain_bounds(self) -> torch.Tensor:
|
|
235
|
+
a = self.point_a.detach()
|
|
236
|
+
b = self.point_b.detach()
|
|
237
|
+
r = self.radius.detach()
|
|
238
|
+
lower = torch.minimum(a, b) - r
|
|
239
|
+
upper = torch.maximum(a, b) + r
|
|
240
|
+
return torch.stack([lower, upper])
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
class ConeSDF(SDFBase):
|
|
244
|
+
"""Signed distance function for a finite cone.
|
|
245
|
+
|
|
246
|
+
The cone is defined by:
|
|
247
|
+
- apex point
|
|
248
|
+
- axis direction
|
|
249
|
+
- height
|
|
250
|
+
- base radius
|
|
251
|
+
|
|
252
|
+
Parameters
|
|
253
|
+
----------
|
|
254
|
+
apexpoint : array-like of shape (3,)
|
|
255
|
+
Apex position of the cone.
|
|
256
|
+
axis : array-like of shape (3,)
|
|
257
|
+
Direction vector of the cone axis.
|
|
258
|
+
radius : float
|
|
259
|
+
Radius at the base of the cone.
|
|
260
|
+
height : float
|
|
261
|
+
Height of the cone from apex to base.
|
|
262
|
+
|
|
263
|
+
Example
|
|
264
|
+
-------
|
|
265
|
+
>>> cone = ConeSDF(apexpoint=[0,0,0], axis=[0,1,0], radius=1.0, height=2.0)
|
|
266
|
+
"""
|
|
267
|
+
|
|
268
|
+
def __init__(self, apexpoint, axis, radius, height):
|
|
269
|
+
super().__init__()
|
|
270
|
+
apexpoint = torch.as_tensor(apexpoint, dtype=torch.float32)
|
|
271
|
+
axis = torch.as_tensor(axis, dtype=torch.float32)
|
|
272
|
+
radius = torch.as_tensor([radius], dtype=torch.float32)
|
|
273
|
+
height = torch.as_tensor([height], dtype=torch.float32)
|
|
274
|
+
self.point = torch.nn.Parameter(apexpoint)
|
|
275
|
+
self.axis = torch.nn.Parameter(axis)
|
|
276
|
+
self.radius = torch.nn.Parameter(radius)
|
|
277
|
+
self.height = torch.nn.Parameter(height)
|
|
278
|
+
self.geometric_dim = 3
|
|
279
|
+
|
|
280
|
+
def _compute(self, queries: torch.Tensor) -> torch.Tensor:
|
|
281
|
+
logger.debug(f"{type(self).__name__}._compute - {queries.shape[0]} points")
|
|
282
|
+
point = self.point.to(device=queries.device, dtype=queries.dtype)
|
|
283
|
+
axis = self.axis.to(device=queries.device, dtype=queries.dtype)
|
|
284
|
+
radius = self.radius.to(device=queries.device, dtype=queries.dtype)
|
|
285
|
+
height = self.height.to(device=queries.device, dtype=queries.dtype)
|
|
286
|
+
|
|
287
|
+
# Normalize axis
|
|
288
|
+
axis_norm = torch.linalg.norm(axis)
|
|
289
|
+
if axis_norm == 0:
|
|
290
|
+
raise ValueError("Cone axis vector must be non-zero")
|
|
291
|
+
axis_unit = axis / axis_norm
|
|
292
|
+
|
|
293
|
+
# Vector from apex to query points
|
|
294
|
+
diff = queries - point # (N,3)
|
|
295
|
+
|
|
296
|
+
# Axial projection
|
|
297
|
+
y = torch.sum(diff * axis_unit, dim=1, keepdim=True) # (N,1)
|
|
298
|
+
|
|
299
|
+
# Radial component
|
|
300
|
+
radial_vec = diff - y * axis_unit
|
|
301
|
+
x = torch.linalg.norm(radial_vec, dim=1, keepdim=True)
|
|
302
|
+
|
|
303
|
+
# Linear radius scaling along height
|
|
304
|
+
# radius(y) = (y/h) * r
|
|
305
|
+
k = radius / height
|
|
306
|
+
r_at_y = k * y
|
|
307
|
+
|
|
308
|
+
# Distance to infinite cone surface
|
|
309
|
+
d_side = x - r_at_y
|
|
310
|
+
|
|
311
|
+
# Distance to base plane
|
|
312
|
+
d_base = y - height
|
|
313
|
+
|
|
314
|
+
# Distance to apex cap (prevent below apex)
|
|
315
|
+
d_apex = -y
|
|
316
|
+
|
|
317
|
+
# Outside distance (Euclidean corner handling)
|
|
318
|
+
external = torch.linalg.norm(
|
|
319
|
+
torch.clamp(torch.cat([d_side, d_base], dim=1), min=0), dim=1, keepdim=True
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
# Inside distance
|
|
323
|
+
internal = torch.clamp(torch.max(d_side, torch.max(d_base, d_apex)), max=0)
|
|
324
|
+
|
|
325
|
+
return external + internal
|
|
326
|
+
|
|
327
|
+
def _get_domain_bounds(self) -> torch.Tensor:
|
|
328
|
+
"""
|
|
329
|
+
Conservative bounding box.
|
|
330
|
+
"""
|
|
331
|
+
extent = max(self.radius, self.height)
|
|
332
|
+
min_bound = self.point - extent
|
|
333
|
+
max_bound = self.point + extent
|
|
334
|
+
return torch.stack([min_bound, max_bound])
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
class TorusSDF(SDFBase):
|
|
338
|
+
"""Signed distance function for a torus (doughnut shape).
|
|
339
|
+
|
|
340
|
+
Creates a torus defined by its center, orientation axis, and two radii.
|
|
341
|
+
The torus is rotationally symmetric around the axis direction.
|
|
342
|
+
|
|
343
|
+
Parameters
|
|
344
|
+
----------
|
|
345
|
+
center : array-like of shape (3,)
|
|
346
|
+
Center point of the torus in 3D space.
|
|
347
|
+
axis : array-like of shape (3,)
|
|
348
|
+
Axis vector normal to the torus ring plane. The torus is
|
|
349
|
+
rotationally symmetric around this axis. Will be normalized.
|
|
350
|
+
major_radius : float
|
|
351
|
+
Distance from the center to the tube center (R). This is the
|
|
352
|
+
radius of the ring itself. Must be positive.
|
|
353
|
+
minor_radius : float
|
|
354
|
+
Radius of the tube cross-section (r). This is the thickness
|
|
355
|
+
of the ring. Must be positive and typically less than major_radius.
|
|
356
|
+
|
|
357
|
+
Examples
|
|
358
|
+
--------
|
|
359
|
+
>>> # Torus centered at origin, lying in XY plane (axis along Z)
|
|
360
|
+
>>> TorusSDF(center=[0, 0, 0], axis=[0, 0, 1], major_radius=1.0, minor_radius=0.2)
|
|
361
|
+
>>>
|
|
362
|
+
>>> # Tilted torus with axis at an angle
|
|
363
|
+
>>> TorusSDF(center=[0, 0, 0], axis=[1, 0, 1], major_radius=1.0, minor_radius=0.1)
|
|
364
|
+
|
|
365
|
+
Notes
|
|
366
|
+
-----
|
|
367
|
+
- The torus lies in a plane perpendicular to the axis vector
|
|
368
|
+
- major_radius > minor_radius creates a ring torus (standard doughnut)
|
|
369
|
+
- major_radius = minor_radius creates a horn torus (hole closes)
|
|
370
|
+
- major_radius < minor_radius creates a self-intersecting spindle torus
|
|
371
|
+
- All parameters are trainable for optimization
|
|
372
|
+
"""
|
|
373
|
+
|
|
374
|
+
def __init__(self, center, axis, major_radius, minor_radius):
|
|
375
|
+
"""Initialize TorusSDF.
|
|
376
|
+
|
|
377
|
+
Parameters
|
|
378
|
+
----------
|
|
379
|
+
center : array-like of shape (3,)
|
|
380
|
+
Center of the torus.
|
|
381
|
+
axis : array-like of shape (3,)
|
|
382
|
+
Axis normal to the torus ring plane.
|
|
383
|
+
major_radius : float
|
|
384
|
+
Distance from center to tube center (R).
|
|
385
|
+
minor_radius : float
|
|
386
|
+
Tube radius (r).
|
|
387
|
+
"""
|
|
388
|
+
super().__init__()
|
|
389
|
+
# store as trainable parameters
|
|
390
|
+
c = torch.as_tensor(center, dtype=torch.float32)
|
|
391
|
+
a = torch.as_tensor(axis, dtype=torch.float32)
|
|
392
|
+
R = torch.as_tensor(major_radius, dtype=torch.float32)
|
|
393
|
+
r = torch.as_tensor(minor_radius, dtype=torch.float32)
|
|
394
|
+
|
|
395
|
+
self.center = torch.nn.Parameter(c)
|
|
396
|
+
self.axis = torch.nn.Parameter(a)
|
|
397
|
+
self.R = torch.nn.Parameter(R.reshape(())) # scalar
|
|
398
|
+
self.r = torch.nn.Parameter(r.reshape(())) # scalar
|
|
399
|
+
|
|
400
|
+
self.geometric_dim = 3
|
|
401
|
+
|
|
402
|
+
def _compute(self, queries: torch.Tensor) -> torch.Tensor:
|
|
403
|
+
logger.debug(f"{type(self).__name__}._compute - {queries.shape[0]} points")
|
|
404
|
+
# align dtype/device
|
|
405
|
+
center = self.center.to(device=queries.device, dtype=queries.dtype)
|
|
406
|
+
axis = self.axis.to(device=queries.device, dtype=queries.dtype)
|
|
407
|
+
R = self.R.to(device=queries.device, dtype=queries.dtype)
|
|
408
|
+
r = self.r.to(device=queries.device, dtype=queries.dtype)
|
|
409
|
+
|
|
410
|
+
# normalize axis
|
|
411
|
+
axis_norm = torch.linalg.norm(axis)
|
|
412
|
+
if axis_norm.item() == 0:
|
|
413
|
+
raise ValueError("Torus axis must be non-zero")
|
|
414
|
+
axis_unit = axis / axis_norm
|
|
415
|
+
|
|
416
|
+
diff = queries - center # (N,3)
|
|
417
|
+
|
|
418
|
+
# axial projection (height from ring plane)
|
|
419
|
+
y = torch.sum(diff * axis_unit, dim=1, keepdim=True) # (N,1)
|
|
420
|
+
|
|
421
|
+
# radial length in ring plane
|
|
422
|
+
radial_vec = diff - y * axis_unit
|
|
423
|
+
radial_len = torch.linalg.norm(radial_vec, dim=1, keepdim=True) # (N,1)
|
|
424
|
+
|
|
425
|
+
q = torch.cat([radial_len - R, y], dim=1) # (N,2)
|
|
426
|
+
out = torch.linalg.norm(q, dim=1, keepdim=True) - r # (N,1)
|
|
427
|
+
return out.reshape(-1, 1)
|
|
428
|
+
|
|
429
|
+
def _get_domain_bounds(self) -> torch.Tensor:
|
|
430
|
+
# tight AABB accounting for torus orientation
|
|
431
|
+
center = self.center
|
|
432
|
+
R = self.R
|
|
433
|
+
r = self.r
|
|
434
|
+
|
|
435
|
+
# normalize axis
|
|
436
|
+
axis = self.axis
|
|
437
|
+
axis_unit = axis / torch.linalg.norm(axis)
|
|
438
|
+
|
|
439
|
+
# extent along axis direction is only the tube radius
|
|
440
|
+
extent_along_axis = r
|
|
441
|
+
|
|
442
|
+
# extent perpendicular to axis is major + minor radius
|
|
443
|
+
extent_perp = R + r
|
|
444
|
+
|
|
445
|
+
# For each coordinate axis i, the extent is:
|
|
446
|
+
# sqrt(extent_perp^2 * (1 - axis[i]^2) + extent_along^2 * axis[i]^2)
|
|
447
|
+
# This accounts for how much the torus extends in each coordinate direction
|
|
448
|
+
axis_sq = axis_unit**2
|
|
449
|
+
extent = torch.sqrt(
|
|
450
|
+
extent_perp**2 * (1 - axis_sq) + extent_along_axis**2 * axis_sq
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
lower = center - extent
|
|
454
|
+
upper = center + extent
|
|
455
|
+
return torch.stack([lower, upper], dim=0)
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
class PlaneSDF(SDFBase):
|
|
459
|
+
"""Signed distance function for a half-space defined by a point and normal vector.
|
|
460
|
+
|
|
461
|
+
Computes the signed distance from query points to a plane. The distance is
|
|
462
|
+
negative on the side the normal points toward, zero on the plane, and
|
|
463
|
+
positive on the opposite side.
|
|
464
|
+
|
|
465
|
+
Parameters
|
|
466
|
+
----------
|
|
467
|
+
point : array-like of shape (3,)
|
|
468
|
+
A point on the plane.
|
|
469
|
+
normal : array-like of shape (3,)
|
|
470
|
+
Normal vector defining the plane orientation (will be normalized).
|
|
471
|
+
|
|
472
|
+
Examples
|
|
473
|
+
--------
|
|
474
|
+
>>> from DeepSDFStruct.sdf_primitives import PlaneSDF
|
|
475
|
+
>>> import torch
|
|
476
|
+
>>>
|
|
477
|
+
>>> # Plane at z=0 with normal pointing up
|
|
478
|
+
>>> plane = PlaneSDF(point=[0, 0, 0], normal=[0, 0, 1])
|
|
479
|
+
>>> points = torch.tensor([[0.0, 0.0, 0.0], [0.0, 0.0, 1.0], [0.0, 0.0, -1.0]])
|
|
480
|
+
>>> distances = plane(points)
|
|
481
|
+
>>> print(distances) # [0.0, 1.0, -1.0]
|
|
482
|
+
"""
|
|
483
|
+
|
|
484
|
+
def __init__(self, point, normal):
|
|
485
|
+
"""Initialize PlaneSDF with a point and normal vector.
|
|
486
|
+
|
|
487
|
+
Parameters
|
|
488
|
+
----------
|
|
489
|
+
point : array-like of shape (3,)
|
|
490
|
+
A point on the plane.
|
|
491
|
+
normal : array-like of shape (3,)
|
|
492
|
+
Normal vector defining the plane orientation.
|
|
493
|
+
"""
|
|
494
|
+
super().__init__()
|
|
495
|
+
self.point = torch.tensor(point, dtype=torch.float32)
|
|
496
|
+
self.normal = torch.tensor(normal, dtype=torch.float32)
|
|
497
|
+
self.normal = self.normal / torch.linalg.norm(self.normal)
|
|
498
|
+
|
|
499
|
+
def _compute(self, queries: torch.Tensor) -> torch.Tensor:
|
|
500
|
+
logger.debug(f"{type(self).__name__}._compute - {queries.shape[0]} points")
|
|
501
|
+
return torch.matmul(queries - self.point, self.normal).reshape(-1, 1)
|
|
502
|
+
|
|
503
|
+
def _get_domain_bounds(self) -> torch.Tensor:
|
|
504
|
+
return torch.tensor([[-1.0, -1.0, -1.0], [1.0, 1.0, 1.0]])
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
class CornerSpheresSDF(SDFBase):
|
|
508
|
+
"""Signed distance function for a cube with spherical cutouts at the corners.
|
|
509
|
+
|
|
510
|
+
Creates a cube with rounded corners by subtracting spheres from each of
|
|
511
|
+
the 8 corners. The result is a cube-like shape with smooth corner transitions.
|
|
512
|
+
|
|
513
|
+
Parameters
|
|
514
|
+
----------
|
|
515
|
+
radius : float
|
|
516
|
+
Radius of the spherical cutouts at each corner.
|
|
517
|
+
limit : float, default 1.0
|
|
518
|
+
Half-size of the cube (distance from center to each face).
|
|
519
|
+
|
|
520
|
+
Examples
|
|
521
|
+
--------
|
|
522
|
+
>>> from DeepSDFStruct.sdf_primitives import CornerSpheresSDF
|
|
523
|
+
>>> import torch
|
|
524
|
+
>>>
|
|
525
|
+
>>> corner_spheres = CornerSpheresSDF(radius=0.3, limit=1.0)
|
|
526
|
+
>>> points = torch.tensor([[0.0, 0.0, 0.0], [1.0, 1.0, 1.0]])
|
|
527
|
+
>>> distances = corner_spheres(points)
|
|
528
|
+
"""
|
|
529
|
+
|
|
530
|
+
def __init__(self, radius, limit=1.0):
|
|
531
|
+
"""Initialize CornerSpheresSDF with corner sphere radius and cube size.
|
|
532
|
+
|
|
533
|
+
Parameters
|
|
534
|
+
----------
|
|
535
|
+
radius : float
|
|
536
|
+
Radius of the spherical cutouts at each corner.
|
|
537
|
+
limit : float, default 1.0
|
|
538
|
+
Half-size of the cube.
|
|
539
|
+
"""
|
|
540
|
+
super().__init__()
|
|
541
|
+
self.r = radius
|
|
542
|
+
self.limit = limit
|
|
543
|
+
|
|
544
|
+
# define the 8 corners of the cube
|
|
545
|
+
self.corners = torch.tensor(
|
|
546
|
+
[[x, y, z] for x in [-1, 1] for y in [-1, 1] for z in [-1, 1]],
|
|
547
|
+
dtype=torch.float32,
|
|
548
|
+
)
|
|
549
|
+
|
|
550
|
+
def _compute(self, queries: torch.Tensor) -> torch.Tensor:
|
|
551
|
+
logger.debug(f"{type(self).__name__}._compute - {queries.shape[0]} points")
|
|
552
|
+
|
|
553
|
+
# start with the cube SDF
|
|
554
|
+
output = torch.linalg.norm(queries, dim=1, ord=float("inf")) - self.limit
|
|
555
|
+
|
|
556
|
+
# subtract spheres at corners
|
|
557
|
+
for corner in self.corners:
|
|
558
|
+
sphere_like = torch.linalg.norm(queries - corner, dim=1, ord=3) - self.r
|
|
559
|
+
output = torch.maximum(output, -sphere_like)
|
|
560
|
+
|
|
561
|
+
return output.reshape(-1, 1)
|
|
562
|
+
|
|
563
|
+
def _get_domain_bounds(self) -> torch.Tensor:
|
|
564
|
+
return torch.tensor([[-1.0, -1.0, -1.0], [1.0, 1.0, 1.0]])
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
class CrossMsSDF(SDFBase):
|
|
568
|
+
"""Signed distance function for a cross-shaped structure.
|
|
569
|
+
|
|
570
|
+
Creates a 3D cross by computing the intersection of three cylinders
|
|
571
|
+
aligned along the x, y, and z axes. The result is a symmetric
|
|
572
|
+
cross-like structure centered at the origin.
|
|
573
|
+
|
|
574
|
+
Parameters
|
|
575
|
+
----------
|
|
576
|
+
radius : float
|
|
577
|
+
Radius of each cylinder forming the cross.
|
|
578
|
+
|
|
579
|
+
Examples
|
|
580
|
+
--------
|
|
581
|
+
>>> from DeepSDFStruct.sdf_primitives import CrossMsSDF
|
|
582
|
+
>>> import torch
|
|
583
|
+
>>>
|
|
584
|
+
>>> cross = CrossMsSDF(radius=0.2)
|
|
585
|
+
>>> points = torch.tensor([[0.0, 0.0, 0.0], [1.0, 0.0, 0.0]])
|
|
586
|
+
>>> distances = cross(points)
|
|
587
|
+
"""
|
|
588
|
+
|
|
589
|
+
def __init__(self, radius):
|
|
590
|
+
"""Initialize CrossMsSDF with cylinder radius.
|
|
591
|
+
|
|
592
|
+
Parameters
|
|
593
|
+
----------
|
|
594
|
+
radius : float
|
|
595
|
+
Radius of each cylinder.
|
|
596
|
+
"""
|
|
597
|
+
super().__init__()
|
|
598
|
+
self.r = radius
|
|
599
|
+
|
|
600
|
+
def _compute(self, queries: torch.Tensor) -> torch.Tensor:
|
|
601
|
+
logger.debug(f"{type(self).__name__}._compute - {queries.shape[0]} points")
|
|
602
|
+
# start with the L∞ norm
|
|
603
|
+
output = torch.linalg.norm(queries, dim=1, ord=float("inf"))
|
|
604
|
+
|
|
605
|
+
# x-axis cylinder
|
|
606
|
+
cylinder_x = torch.sqrt(queries[:, 1] ** 2 + queries[:, 2] ** 2) - self.r
|
|
607
|
+
output = torch.minimum(output, cylinder_x)
|
|
608
|
+
|
|
609
|
+
# y-axis cylinder
|
|
610
|
+
cylinder_y = torch.sqrt(queries[:, 0] ** 2 + queries[:, 2] ** 2) - self.r
|
|
611
|
+
output = torch.minimum(output, cylinder_y)
|
|
612
|
+
|
|
613
|
+
# z-axis cylinder
|
|
614
|
+
cylinder_z = torch.sqrt(queries[:, 0] ** 2 + queries[:, 1] ** 2) - self.r
|
|
615
|
+
output = torch.minimum(output, cylinder_z)
|
|
616
|
+
|
|
617
|
+
return output.reshape(-1, 1)
|
|
618
|
+
|
|
619
|
+
def _get_domain_bounds(self) -> torch.Tensor:
|
|
620
|
+
return torch.tensor([[-1.0, -1.0, -1.0], [1.0, 1.0, 1.0]])
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
class RoundedBoxSDF(SDFBase):
|
|
624
|
+
"""3D axis-aligned box with rounded corners."""
|
|
625
|
+
|
|
626
|
+
def __init__(self, center, extents, radius):
|
|
627
|
+
super().__init__(geometric_dim=3)
|
|
628
|
+
c = torch.as_tensor(center, dtype=torch.float32)
|
|
629
|
+
e = torch.as_tensor(extents, dtype=torch.float32)
|
|
630
|
+
r = torch.as_tensor(radius, dtype=torch.float32)
|
|
631
|
+
self.center = torch.nn.Parameter(c.reshape(3))
|
|
632
|
+
self.extents = torch.nn.Parameter(e.reshape(3))
|
|
633
|
+
self.radius = torch.nn.Parameter(r.reshape(()))
|
|
634
|
+
|
|
635
|
+
def _compute(self, queries: torch.Tensor) -> torch.Tensor:
|
|
636
|
+
logger.debug(f"{type(self).__name__}._compute - {queries.shape[0]} points")
|
|
637
|
+
center = self.center.to(device=queries.device, dtype=queries.dtype)
|
|
638
|
+
extents = self.extents.to(device=queries.device, dtype=queries.dtype)
|
|
639
|
+
radius = self.radius.to(device=queries.device, dtype=queries.dtype)
|
|
640
|
+
|
|
641
|
+
q = torch.abs(queries - center) - extents / 2.0 + radius
|
|
642
|
+
|
|
643
|
+
outside_dist = torch.linalg.norm(torch.clamp(q, min=0.0), dim=1)
|
|
644
|
+
inside_dist = torch.minimum(torch.max(q, dim=1).values, torch.tensor(0.0))
|
|
645
|
+
|
|
646
|
+
return (outside_dist + inside_dist - radius).reshape(-1, 1)
|
|
647
|
+
|
|
648
|
+
def _get_domain_bounds(self) -> torch.Tensor:
|
|
649
|
+
center = self.center.detach()
|
|
650
|
+
half = self.extents.detach() / 2.0
|
|
651
|
+
lower = center - half
|
|
652
|
+
upper = center + half
|
|
653
|
+
return torch.stack([lower, upper], dim=0)
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
class WireframeBoxSDF(SDFBase):
|
|
657
|
+
"""3D wireframe box SDF."""
|
|
658
|
+
|
|
659
|
+
def __init__(self, center, extents, thickness):
|
|
660
|
+
super().__init__(geometric_dim=3)
|
|
661
|
+
c = torch.as_tensor(center, dtype=torch.float32)
|
|
662
|
+
e = torch.as_tensor(extents, dtype=torch.float32)
|
|
663
|
+
t = torch.as_tensor(thickness, dtype=torch.float32)
|
|
664
|
+
self.center = torch.nn.Parameter(c.reshape(3))
|
|
665
|
+
self.extents = torch.nn.Parameter(e.reshape(3))
|
|
666
|
+
self.thickness = torch.nn.Parameter(t.reshape(()))
|
|
667
|
+
|
|
668
|
+
def _compute(self, queries: torch.Tensor) -> torch.Tensor:
|
|
669
|
+
logger.debug(f"{type(self).__name__}._compute - {queries.shape[0]} points")
|
|
670
|
+
center = self.center.to(device=queries.device, dtype=queries.dtype)
|
|
671
|
+
extents = self.extents.to(device=queries.device, dtype=queries.dtype)
|
|
672
|
+
thickness = self.thickness.to(device=queries.device, dtype=queries.dtype)
|
|
673
|
+
|
|
674
|
+
p = torch.abs(queries - center) - extents / 2.0 - thickness / 2.0
|
|
675
|
+
q = torch.abs(p + thickness / 2.0) - thickness / 2.0
|
|
676
|
+
|
|
677
|
+
px, py, pz = p[:, 0], p[:, 1], p[:, 2]
|
|
678
|
+
qx, qy, qz = q[:, 0], q[:, 1], q[:, 2]
|
|
679
|
+
|
|
680
|
+
def g(a, b, c):
|
|
681
|
+
return torch.linalg.norm(
|
|
682
|
+
torch.clamp(torch.stack([a, b, c], dim=1), min=0.0), dim=1
|
|
683
|
+
) + torch.minimum(torch.max(torch.max(a, b), c), torch.tensor(0.0))
|
|
684
|
+
|
|
685
|
+
return torch.minimum(
|
|
686
|
+
torch.minimum(g(px, qy, qz), g(qx, py, qz)), g(qx, qy, pz)
|
|
687
|
+
).reshape(-1, 1)
|
|
688
|
+
|
|
689
|
+
def _get_domain_bounds(self) -> torch.Tensor:
|
|
690
|
+
center = self.center.detach()
|
|
691
|
+
half = self.extents.detach() / 2.0
|
|
692
|
+
lower = center - half
|
|
693
|
+
upper = center + half
|
|
694
|
+
return torch.stack([lower, upper], dim=0)
|
|
695
|
+
|
|
696
|
+
|
|
697
|
+
class CapsuleSDF(SDFBase):
|
|
698
|
+
"""SDF for a capsule (a line segment with a radius)."""
|
|
699
|
+
|
|
700
|
+
def __init__(self, point_a, point_b, radius):
|
|
701
|
+
super().__init__(geometric_dim=3)
|
|
702
|
+
a = torch.as_tensor(point_a, dtype=torch.float32)
|
|
703
|
+
b = torch.as_tensor(point_b, dtype=torch.float32)
|
|
704
|
+
r = torch.as_tensor(radius, dtype=torch.float32)
|
|
705
|
+
self.point_a = torch.nn.Parameter(a.reshape(3))
|
|
706
|
+
self.point_b = torch.nn.Parameter(b.reshape(3))
|
|
707
|
+
self.radius = torch.nn.Parameter(r.reshape(()))
|
|
708
|
+
|
|
709
|
+
def _compute(self, queries: torch.Tensor) -> torch.Tensor:
|
|
710
|
+
logger.debug(f"{type(self).__name__}._compute - {queries.shape[0]} points")
|
|
711
|
+
point_a = self.point_a.to(device=queries.device, dtype=queries.dtype)
|
|
712
|
+
point_b = self.point_b.to(device=queries.device, dtype=queries.dtype)
|
|
713
|
+
radius = self.radius.to(device=queries.device, dtype=queries.dtype)
|
|
714
|
+
|
|
715
|
+
pa = queries - point_a
|
|
716
|
+
ba = point_b - point_a
|
|
717
|
+
|
|
718
|
+
h = torch.clamp(
|
|
719
|
+
torch.sum(pa * ba, dim=1) / torch.sum(ba * ba), 0.0, 1.0
|
|
720
|
+
).reshape(-1, 1)
|
|
721
|
+
|
|
722
|
+
return (torch.linalg.norm(pa - h * ba, dim=1) - radius).reshape(-1, 1)
|
|
723
|
+
|
|
724
|
+
def _get_domain_bounds(self) -> torch.Tensor:
|
|
725
|
+
a = self.point_a.detach()
|
|
726
|
+
b = self.point_b.detach()
|
|
727
|
+
r = self.radius.detach()
|
|
728
|
+
lower = torch.min(a, b) - r
|
|
729
|
+
upper = torch.max(a, b) + r
|
|
730
|
+
return torch.stack([lower, upper], dim=0)
|
|
731
|
+
|
|
732
|
+
|
|
733
|
+
class EllipsoidSDF(SDFBase):
|
|
734
|
+
"""SDF for an ellipsoid."""
|
|
735
|
+
|
|
736
|
+
def __init__(self, center, extents):
|
|
737
|
+
super().__init__(geometric_dim=3)
|
|
738
|
+
c = torch.as_tensor(center, dtype=torch.float32)
|
|
739
|
+
e = torch.as_tensor(extents, dtype=torch.float32)
|
|
740
|
+
self.center = torch.nn.Parameter(c.reshape(3))
|
|
741
|
+
self.extents = torch.nn.Parameter(e.reshape(3))
|
|
742
|
+
|
|
743
|
+
def _compute(self, queries: torch.Tensor) -> torch.Tensor:
|
|
744
|
+
logger.debug(f"{type(self).__name__}._compute - {queries.shape[0]} points")
|
|
745
|
+
center = self.center.to(device=queries.device, dtype=queries.dtype)
|
|
746
|
+
extents = self.extents.to(device=queries.device, dtype=queries.dtype)
|
|
747
|
+
|
|
748
|
+
p = queries - center
|
|
749
|
+
k0 = torch.linalg.norm(p / extents, dim=1)
|
|
750
|
+
k1 = torch.linalg.norm(p / (extents * extents), dim=1)
|
|
751
|
+
|
|
752
|
+
return (k0 * (k0 - 1.0) / k1).reshape(-1, 1)
|
|
753
|
+
|
|
754
|
+
def _get_domain_bounds(self) -> torch.Tensor:
|
|
755
|
+
center = self.center.detach()
|
|
756
|
+
extents = self.extents.detach()
|
|
757
|
+
lower = center - extents
|
|
758
|
+
upper = center + extents
|
|
759
|
+
return torch.stack([lower, upper], dim=0)
|
|
760
|
+
|
|
761
|
+
|
|
762
|
+
class PyramidSDF(SDFBase):
|
|
763
|
+
"""SDF for a pyramid."""
|
|
764
|
+
|
|
765
|
+
def __init__(self, height):
|
|
766
|
+
super().__init__(geometric_dim=3)
|
|
767
|
+
h = torch.as_tensor(height, dtype=torch.float32)
|
|
768
|
+
self.height = torch.nn.Parameter(h.reshape(()))
|
|
769
|
+
|
|
770
|
+
def _compute(self, queries: torch.Tensor) -> torch.Tensor:
|
|
771
|
+
logger.debug(f"{type(self).__name__}._compute - {queries.shape[0]} points")
|
|
772
|
+
h = self.height.to(device=queries.device, dtype=queries.dtype)
|
|
773
|
+
|
|
774
|
+
a = torch.abs(queries[:, :2]) - 0.5
|
|
775
|
+
|
|
776
|
+
# This part is tricky to translate directly without a direct equivalent of the numpy advanced indexing in torch
|
|
777
|
+
# For now, let's stick to a simplified version or assume a square base.
|
|
778
|
+
# The original implementation handles non-square bases by swapping coordinates.
|
|
779
|
+
|
|
780
|
+
px = a[:, 0]
|
|
781
|
+
pz = a[:, 1]
|
|
782
|
+
py = queries[:, 2]
|
|
783
|
+
|
|
784
|
+
m2 = h * h + 0.25
|
|
785
|
+
|
|
786
|
+
qx = pz
|
|
787
|
+
qy = h * py - 0.5 * px
|
|
788
|
+
qz = h * px + 0.5 * py
|
|
789
|
+
|
|
790
|
+
s = torch.clamp(-qx, min=0.0)
|
|
791
|
+
t = torch.clamp((qy - 0.5 * pz) / (m2 + 0.25), 0.0, 1.0)
|
|
792
|
+
|
|
793
|
+
a_dist = m2 * (qx + s) ** 2 + qy**2
|
|
794
|
+
b_dist = m2 * (qx + 0.5 * t) ** 2 + (qy - m2 * t) ** 2
|
|
795
|
+
|
|
796
|
+
d2 = torch.where(
|
|
797
|
+
(qy < 0) & (-qx * m2 - qy * 0.5 < 0),
|
|
798
|
+
torch.min(a_dist, b_dist),
|
|
799
|
+
torch.tensor(0.0, device=queries.device),
|
|
800
|
+
)
|
|
801
|
+
|
|
802
|
+
return (
|
|
803
|
+
torch.sqrt((d2 + qz**2) / m2) * torch.sign(torch.max(qz, -py))
|
|
804
|
+
).reshape(-1, 1)
|
|
805
|
+
|
|
806
|
+
def _get_domain_bounds(self) -> torch.Tensor:
|
|
807
|
+
h = self.height.detach()
|
|
808
|
+
return torch.tensor([[-0.5, -0.5, 0], [0.5, 0.5, h]])
|
|
809
|
+
|
|
810
|
+
|
|
811
|
+
class CircleSDF(SDFBase):
|
|
812
|
+
"""SDF for a 2D circle."""
|
|
813
|
+
|
|
814
|
+
def __init__(self, center, radius):
|
|
815
|
+
super().__init__(geometric_dim=2)
|
|
816
|
+
self.center = torch.nn.Parameter(torch.as_tensor(center, dtype=torch.float32))
|
|
817
|
+
self.radius = torch.nn.Parameter(
|
|
818
|
+
torch.as_tensor(radius, dtype=torch.float32).reshape(())
|
|
819
|
+
)
|
|
820
|
+
|
|
821
|
+
def _compute(self, queries: torch.Tensor) -> torch.Tensor:
|
|
822
|
+
logger.debug(f"{type(self).__name__}._compute - {queries.shape[0]} points")
|
|
823
|
+
center = self.center.to(device=queries.device, dtype=queries.dtype)
|
|
824
|
+
radius = self.radius.to(device=queries.device, dtype=queries.dtype)
|
|
825
|
+
return (torch.linalg.norm(queries - center, dim=1) - radius).reshape(-1, 1)
|
|
826
|
+
|
|
827
|
+
def _get_domain_bounds(self) -> torch.Tensor:
|
|
828
|
+
r = self.radius
|
|
829
|
+
c = self.center
|
|
830
|
+
return torch.stack([c - r, c + r])
|
|
831
|
+
|
|
832
|
+
|
|
833
|
+
class RectangleSDF(SDFBase):
|
|
834
|
+
"""SDF for a 2D rectangle with center and extents."""
|
|
835
|
+
|
|
836
|
+
def __init__(self, center, extents):
|
|
837
|
+
super().__init__(geometric_dim=2)
|
|
838
|
+
self.center = torch.nn.Parameter(torch.as_tensor(center, dtype=torch.float32))
|
|
839
|
+
self.extents = torch.nn.Parameter(torch.as_tensor(extents, dtype=torch.float32))
|
|
840
|
+
|
|
841
|
+
def _compute(self, queries: torch.Tensor) -> torch.Tensor:
|
|
842
|
+
logger.debug(f"{type(self).__name__}._compute - {queries.shape[0]} points")
|
|
843
|
+
center = self.center.to(device=queries.device, dtype=queries.dtype)
|
|
844
|
+
half = self.extents.to(device=queries.device, dtype=queries.dtype) / 2.0
|
|
845
|
+
q = torch.abs(queries - center) - half
|
|
846
|
+
outside = torch.linalg.norm(torch.clamp(q, min=0.0), dim=1)
|
|
847
|
+
inside = torch.minimum(torch.maximum(q[:, 0], q[:, 1]), torch.tensor(0.0))
|
|
848
|
+
return (outside + inside).reshape(-1, 1)
|
|
849
|
+
|
|
850
|
+
def _get_domain_bounds(self) -> torch.Tensor:
|
|
851
|
+
c = self.center
|
|
852
|
+
e = self.extents
|
|
853
|
+
return torch.stack([c - e / 2, c + e / 2])
|
|
854
|
+
|
|
855
|
+
|
|
856
|
+
class LineSDF(SDFBase):
|
|
857
|
+
"""SDF for a 2D line (infinite line defined by normal vector and point)."""
|
|
858
|
+
|
|
859
|
+
def __init__(self, normal, point):
|
|
860
|
+
super().__init__(geometric_dim=2)
|
|
861
|
+
self.normal = torch.nn.Parameter(torch.as_tensor(normal, dtype=torch.float32))
|
|
862
|
+
self.point = torch.nn.Parameter(torch.as_tensor(point, dtype=torch.float32))
|
|
863
|
+
|
|
864
|
+
def _compute(self, queries: torch.Tensor) -> torch.Tensor:
|
|
865
|
+
logger.debug(f"{type(self).__name__}._compute - {queries.shape[0]} points")
|
|
866
|
+
n = self.normal.to(device=queries.device, dtype=queries.dtype)
|
|
867
|
+
p = self.point.to(device=queries.device, dtype=queries.dtype)
|
|
868
|
+
n_normalized = n / torch.linalg.norm(n)
|
|
869
|
+
return torch.matmul(queries - p, n_normalized).reshape(-1, 1)
|
|
870
|
+
|
|
871
|
+
def _get_domain_bounds(self) -> torch.Tensor:
|
|
872
|
+
return torch.tensor([[-1.0, -1.0], [1.0, 1.0]])
|
|
873
|
+
|
|
874
|
+
|
|
875
|
+
class RoundedRectangleSDF(SDFBase):
|
|
876
|
+
"""SDF for a 2D rectangle with rounded corners."""
|
|
877
|
+
|
|
878
|
+
def __init__(self, center, extents, radius):
|
|
879
|
+
super().__init__(geometric_dim=2)
|
|
880
|
+
self.center = torch.nn.Parameter(torch.as_tensor(center, dtype=torch.float32))
|
|
881
|
+
self.extents = torch.nn.Parameter(torch.as_tensor(extents, dtype=torch.float32))
|
|
882
|
+
|
|
883
|
+
# Support single radius or tuple of 4 radii (one per quadrant)
|
|
884
|
+
if isinstance(radius, (tuple, list)):
|
|
885
|
+
if len(radius) == 1:
|
|
886
|
+
self.radii = torch.nn.Parameter(
|
|
887
|
+
torch.as_tensor([radius[0]] * 4, dtype=torch.float32)
|
|
888
|
+
)
|
|
889
|
+
elif len(radius) == 4:
|
|
890
|
+
self.radii = torch.nn.Parameter(
|
|
891
|
+
torch.as_tensor(radius, dtype=torch.float32)
|
|
892
|
+
)
|
|
893
|
+
else:
|
|
894
|
+
raise ValueError("radius must be a single value or a tuple of 4 values")
|
|
895
|
+
else:
|
|
896
|
+
self.radii = torch.nn.Parameter(
|
|
897
|
+
torch.as_tensor([radius] * 4, dtype=torch.float32)
|
|
898
|
+
)
|
|
899
|
+
|
|
900
|
+
def _compute(self, queries: torch.Tensor) -> torch.Tensor:
|
|
901
|
+
logger.debug(f"{type(self).__name__}._compute - {queries.shape[0]} points")
|
|
902
|
+
center = self.center.to(device=queries.device, dtype=queries.dtype)
|
|
903
|
+
extents = self.extents.to(device=queries.device, dtype=queries.dtype)
|
|
904
|
+
radii = self.radii.to(device=queries.device, dtype=queries.dtype)
|
|
905
|
+
|
|
906
|
+
q = torch.abs(queries - center) - extents / 2.0
|
|
907
|
+
|
|
908
|
+
# Select appropriate radius based on quadrant
|
|
909
|
+
x, y = q[:, 0], q[:, 1]
|
|
910
|
+
zeros = torch.zeros_like(x)
|
|
911
|
+
r0 = torch.where((x > 0) & (y > 0), radii[0], zeros)
|
|
912
|
+
r1 = torch.where((x > 0) & (y <= 0), radii[1], zeros)
|
|
913
|
+
r2 = torch.where((x <= 0) & (y <= 0), radii[2], zeros)
|
|
914
|
+
r3 = torch.where((x <= 0) & (y > 0), radii[3], zeros)
|
|
915
|
+
r = r0 + r1 + r2 + r3
|
|
916
|
+
|
|
917
|
+
q = q + r.unsqueeze(1)
|
|
918
|
+
outside = torch.linalg.norm(torch.clamp(q, min=0.0), dim=1)
|
|
919
|
+
inside = torch.minimum(torch.maximum(q[:, 0], q[:, 1]), torch.tensor(0.0))
|
|
920
|
+
return (outside + inside - r).reshape(-1, 1)
|
|
921
|
+
|
|
922
|
+
def _get_domain_bounds(self) -> torch.Tensor:
|
|
923
|
+
c = self.center
|
|
924
|
+
e = self.extents
|
|
925
|
+
return torch.stack([c - e / 2, c + e / 2])
|
|
926
|
+
|
|
927
|
+
|
|
928
|
+
class EquilateralTriangleSDF(SDFBase):
|
|
929
|
+
"""SDF for a regular equilateral triangle."""
|
|
930
|
+
|
|
931
|
+
def __init__(self, size=1.0):
|
|
932
|
+
super().__init__(geometric_dim=2)
|
|
933
|
+
self.size = torch.nn.Parameter(
|
|
934
|
+
torch.as_tensor(size, dtype=torch.float32).reshape(())
|
|
935
|
+
)
|
|
936
|
+
|
|
937
|
+
def _compute(self, queries: torch.Tensor) -> torch.Tensor:
|
|
938
|
+
logger.debug(f"{type(self).__name__}._compute - {queries.shape[0]} points")
|
|
939
|
+
size = self.size.to(device=queries.device, dtype=queries.dtype)
|
|
940
|
+
r = size
|
|
941
|
+
k = torch.tensor(np.sqrt(3.0), device=queries.device, dtype=queries.dtype)
|
|
942
|
+
p = queries.clone()
|
|
943
|
+
|
|
944
|
+
# Apply abs and offset
|
|
945
|
+
p[:, 0] = torch.abs(p[:, 0]) - r
|
|
946
|
+
p[:, 1] = p[:, 1] + r / k
|
|
947
|
+
|
|
948
|
+
# Conditional transformation
|
|
949
|
+
w = (p[:, 0] + k * p[:, 1]) > 0
|
|
950
|
+
q = torch.stack([p[:, 0] - k * p[:, 1], -k * p[:, 0] - p[:, 1]], dim=1) / 2.0
|
|
951
|
+
p = torch.where(w.unsqueeze(1), q, p)
|
|
952
|
+
|
|
953
|
+
# Clamp x
|
|
954
|
+
p[:, 0] = p[:, 0] - torch.clamp(p[:, 0], min=-2.0 * r, max=torch.zeros_like(r))
|
|
955
|
+
|
|
956
|
+
# Compute signed distance
|
|
957
|
+
return (-torch.linalg.norm(p, dim=1) * torch.sign(p[:, 1])).reshape(-1, 1)
|
|
958
|
+
|
|
959
|
+
def _get_domain_bounds(self) -> torch.Tensor:
|
|
960
|
+
s = self.size
|
|
961
|
+
return torch.tensor([[-s, -s], [s, s]])
|
|
962
|
+
|
|
963
|
+
|
|
964
|
+
class HexagonSDF(SDFBase):
|
|
965
|
+
"""SDF for a regular hexagon."""
|
|
966
|
+
|
|
967
|
+
def __init__(self, size=1.0):
|
|
968
|
+
super().__init__(geometric_dim=2)
|
|
969
|
+
self.size = torch.nn.Parameter(
|
|
970
|
+
torch.as_tensor(size, dtype=torch.float32).reshape(())
|
|
971
|
+
)
|
|
972
|
+
|
|
973
|
+
def _compute(self, queries: torch.Tensor) -> torch.Tensor:
|
|
974
|
+
logger.debug(f"{type(self).__name__}._compute - {queries.shape[0]} points")
|
|
975
|
+
r = self.size.to(device=queries.device, dtype=queries.dtype)
|
|
976
|
+
k = torch.tensor(
|
|
977
|
+
[-np.sqrt(3.0) / 2.0, 0.5, np.tan(np.pi / 6.0)],
|
|
978
|
+
device=queries.device,
|
|
979
|
+
dtype=queries.dtype,
|
|
980
|
+
)
|
|
981
|
+
p = torch.abs(queries)
|
|
982
|
+
p = p - 2.0 * torch.clamp(torch.sum(p * k[:2], dim=1, keepdim=True), max=0) * k[
|
|
983
|
+
:2
|
|
984
|
+
].unsqueeze(0)
|
|
985
|
+
p = p - torch.stack(
|
|
986
|
+
[
|
|
987
|
+
torch.clamp(p[:, 0], min=-k[2] * r, max=k[2] * r),
|
|
988
|
+
torch.ones_like(p[:, 0]) * r,
|
|
989
|
+
],
|
|
990
|
+
dim=1,
|
|
991
|
+
)
|
|
992
|
+
return (torch.linalg.norm(p, dim=1) * torch.sign(p[:, 1])).reshape(-1, 1)
|
|
993
|
+
|
|
994
|
+
def _get_domain_bounds(self) -> torch.Tensor:
|
|
995
|
+
s = self.size
|
|
996
|
+
return torch.tensor([[-s, -s], [s, s]])
|
|
997
|
+
|
|
998
|
+
|
|
999
|
+
class PolygonSDF(SDFBase):
|
|
1000
|
+
"""SDF for a general convex polygon defined by vertices."""
|
|
1001
|
+
|
|
1002
|
+
def __init__(self, vertices):
|
|
1003
|
+
super().__init__(geometric_dim=2)
|
|
1004
|
+
if len(vertices) < 3:
|
|
1005
|
+
raise ValueError("Polygon needs at least 3 vertices")
|
|
1006
|
+
self.vertices = torch.nn.Parameter(
|
|
1007
|
+
torch.as_tensor(vertices, dtype=torch.float32)
|
|
1008
|
+
)
|
|
1009
|
+
|
|
1010
|
+
def _compute(self, queries: torch.Tensor) -> torch.Tensor:
|
|
1011
|
+
"""
|
|
1012
|
+
Signed distance for convex polygon using winding number method.
|
|
1013
|
+
Returns negative values for points inside the polygon.
|
|
1014
|
+
"""
|
|
1015
|
+
logger.debug(f"{type(self).__name__}._compute - {queries.shape[0]} points")
|
|
1016
|
+
points = self.vertices.to(device=queries.device, dtype=queries.dtype)
|
|
1017
|
+
p = queries
|
|
1018
|
+
n = len(points)
|
|
1019
|
+
|
|
1020
|
+
# Initialize distance squared to distance from first vertex
|
|
1021
|
+
d = torch.sum((p - points[0]) ** 2, dim=1)
|
|
1022
|
+
|
|
1023
|
+
# Initialize sign (positive = outside)
|
|
1024
|
+
s = torch.ones(len(p), device=p.device, dtype=p.dtype)
|
|
1025
|
+
|
|
1026
|
+
for i in range(n):
|
|
1027
|
+
j = (i + n - 1) % n
|
|
1028
|
+
vi = points[i]
|
|
1029
|
+
vj = points[j]
|
|
1030
|
+
e = vj - vi
|
|
1031
|
+
w = p - vi
|
|
1032
|
+
|
|
1033
|
+
# Compute distance to edge segment
|
|
1034
|
+
# Project w onto e, clamp to [0, 1], then compute perpendicular distance
|
|
1035
|
+
e_len_sq = torch.sum(e**2)
|
|
1036
|
+
if e_len_sq > 1e-12:
|
|
1037
|
+
t = torch.clamp(torch.sum(w * e, dim=1) / e_len_sq, 0, 1)
|
|
1038
|
+
b = w - t.unsqueeze(1) * e
|
|
1039
|
+
d = torch.minimum(d, torch.sum(b**2, dim=1))
|
|
1040
|
+
|
|
1041
|
+
# Winding number computation using cross products
|
|
1042
|
+
# Check conditions: (p.y >= vi.y), (p.y < vj.y), (e.x * w.y > e.y * w.x)
|
|
1043
|
+
c1 = p[:, 1] >= vi[1]
|
|
1044
|
+
c2 = p[:, 1] < vj[1]
|
|
1045
|
+
c3 = e[0] * w[:, 1] > e[1] * w[:, 0]
|
|
1046
|
+
|
|
1047
|
+
# Flip sign if point crosses edge (all conditions true or all false)
|
|
1048
|
+
all_c = c1 & c2 & c3
|
|
1049
|
+
all_not_c = (~c1) & (~c2) & (~c3)
|
|
1050
|
+
condition = all_c | all_not_c
|
|
1051
|
+
s = torch.where(condition, -s, s)
|
|
1052
|
+
|
|
1053
|
+
return (s * torch.sqrt(d)).reshape(-1, 1)
|
|
1054
|
+
|
|
1055
|
+
def _get_domain_bounds(self) -> torch.Tensor:
|
|
1056
|
+
v = self.vertices.detach()
|
|
1057
|
+
return torch.stack([v.min(dim=0).values, v.max(dim=0).values])
|
|
1058
|
+
|
|
1059
|
+
|
|
1060
|
+
class SlabSDF(SDFBase):
|
|
1061
|
+
"""SDF for a slab (clipping box defined by axis-aligned planes)."""
|
|
1062
|
+
|
|
1063
|
+
def __init__(self, x0=None, y0=None, z0=None, x1=None, y1=None, z1=None):
|
|
1064
|
+
super().__init__()
|
|
1065
|
+
# Bounds for each axis
|
|
1066
|
+
self.bounds = {}
|
|
1067
|
+
if x0 is not None:
|
|
1068
|
+
self.bounds["x0"] = (
|
|
1069
|
+
float(x0) if not isinstance(x0, torch.nn.Parameter) else x0
|
|
1070
|
+
)
|
|
1071
|
+
if x1 is not None:
|
|
1072
|
+
self.bounds["x1"] = (
|
|
1073
|
+
float(x1) if not isinstance(x1, torch.nn.Parameter) else x1
|
|
1074
|
+
)
|
|
1075
|
+
if y0 is not None:
|
|
1076
|
+
self.bounds["y0"] = (
|
|
1077
|
+
float(y0) if not isinstance(y0, torch.nn.Parameter) else y0
|
|
1078
|
+
)
|
|
1079
|
+
if y1 is not None:
|
|
1080
|
+
self.bounds["y1"] = (
|
|
1081
|
+
float(y1) if not isinstance(y1, torch.nn.Parameter) else y1
|
|
1082
|
+
)
|
|
1083
|
+
if z0 is not None:
|
|
1084
|
+
self.bounds["z0"] = (
|
|
1085
|
+
float(z0) if not isinstance(z0, torch.nn.Parameter) else z0
|
|
1086
|
+
)
|
|
1087
|
+
if z1 is not None:
|
|
1088
|
+
self.bounds["z1"] = (
|
|
1089
|
+
float(z1) if not isinstance(z1, torch.nn.Parameter) else z1
|
|
1090
|
+
)
|
|
1091
|
+
|
|
1092
|
+
def _compute(self, queries: torch.Tensor) -> torch.Tensor:
|
|
1093
|
+
logger.debug(f"{type(self).__name__}._compute - {queries.shape[0]} points")
|
|
1094
|
+
dist = torch.full(
|
|
1095
|
+
(len(queries),), -float("inf"), device=queries.device, dtype=queries.dtype
|
|
1096
|
+
)
|
|
1097
|
+
|
|
1098
|
+
# For each plane constraint
|
|
1099
|
+
for axis, side, bound_key, direction in [
|
|
1100
|
+
(torch.tensor([1, 0, 0], dtype=torch.float32), 0, "x0", 1),
|
|
1101
|
+
(torch.tensor([1, 0, 0], dtype=torch.float32), 1, "x1", -1),
|
|
1102
|
+
(torch.tensor([0, 1, 0], dtype=torch.float32), 0, "y0", 1),
|
|
1103
|
+
(torch.tensor([0, 1, 0], dtype=torch.float32), 1, "y1", -1),
|
|
1104
|
+
(torch.tensor([0, 0, 1], dtype=torch.float32), 0, "z0", 1),
|
|
1105
|
+
(torch.tensor([0, 0, 1], dtype=torch.float32), 1, "z1", -1),
|
|
1106
|
+
]:
|
|
1107
|
+
bound = self.bounds.get(bound_key)
|
|
1108
|
+
if bound is None:
|
|
1109
|
+
continue
|
|
1110
|
+
|
|
1111
|
+
idx = 0
|
|
1112
|
+
if torch.all(axis == torch.tensor([1, 0, 0])):
|
|
1113
|
+
idx = 0
|
|
1114
|
+
elif torch.all(axis == torch.tensor([0, 1, 0])):
|
|
1115
|
+
idx = 1
|
|
1116
|
+
elif torch.all(axis == torch.tensor([0, 0, 1])):
|
|
1117
|
+
idx = 2
|
|
1118
|
+
if side == 0:
|
|
1119
|
+
plane_dist = bound - queries[:, idx]
|
|
1120
|
+
dist = torch.maximum(dist, plane_dist)
|
|
1121
|
+
else:
|
|
1122
|
+
plane_dist = queries[:, idx] - bound
|
|
1123
|
+
dist = torch.maximum(dist, plane_dist)
|
|
1124
|
+
|
|
1125
|
+
return dist.reshape(-1, 1)
|
|
1126
|
+
|
|
1127
|
+
def _get_domain_bounds(self) -> torch.Tensor:
|
|
1128
|
+
return torch.tensor([[-1e9, -1e9, -1e9], [1e9, 1e9, 1e9]])
|
|
1129
|
+
|
|
1130
|
+
|
|
1131
|
+
class RoundedCylinderSDF(SDFBase):
|
|
1132
|
+
"""SDF for a cylinder with smooth rounded transitions."""
|
|
1133
|
+
|
|
1134
|
+
def __init__(self, ra, rb, h):
|
|
1135
|
+
super().__init__()
|
|
1136
|
+
self.ra = torch.nn.Parameter(torch.as_tensor(ra, dtype=torch.float32))
|
|
1137
|
+
self.rb = torch.nn.Parameter(torch.as_tensor(rb, dtype=torch.float32))
|
|
1138
|
+
self.h = torch.nn.Parameter(torch.as_tensor(h, dtype=torch.float32))
|
|
1139
|
+
|
|
1140
|
+
def _compute(self, queries: torch.Tensor) -> torch.Tensor:
|
|
1141
|
+
logger.debug(f"{type(self).__name__}._compute - {queries.shape[0]} points")
|
|
1142
|
+
ra = self.ra.to(device=queries.device, dtype=queries.dtype)
|
|
1143
|
+
rb = self.rb.to(device=queries.device, dtype=queries.dtype)
|
|
1144
|
+
h = self.h.to(device=queries.device, dtype=queries.dtype)
|
|
1145
|
+
|
|
1146
|
+
d = torch.stack(
|
|
1147
|
+
[
|
|
1148
|
+
torch.sqrt(queries[:, 0] ** 2 + queries[:, 1] ** 2) - ra + rb,
|
|
1149
|
+
torch.abs(queries[:, 2]) - h / 2 + rb,
|
|
1150
|
+
],
|
|
1151
|
+
dim=1,
|
|
1152
|
+
)
|
|
1153
|
+
|
|
1154
|
+
outside = torch.linalg.norm(torch.clamp(d, min=0.0), dim=1)
|
|
1155
|
+
inside = torch.minimum(torch.maximum(d[:, 0], d[:, 1]), torch.tensor(0.0))
|
|
1156
|
+
return (outside + inside - rb).reshape(-1, 1)
|
|
1157
|
+
|
|
1158
|
+
def _get_domain_bounds(self) -> torch.Tensor:
|
|
1159
|
+
r = max(self.ra.detach().item(), self.rb.detach().item())
|
|
1160
|
+
h = self.h.detach().item()
|
|
1161
|
+
return torch.tensor([[-r, -r, -h / 2], [r, r, h / 2]])
|
|
1162
|
+
|
|
1163
|
+
|
|
1164
|
+
class CappedConeSDF(SDFBase):
|
|
1165
|
+
"""Signed distance function for a finite cone with exact end caps.
|
|
1166
|
+
|
|
1167
|
+
Creates a cone frustum (or full cone if rb=0) defined by two endpoints
|
|
1168
|
+
and radii at each end. The cone has flat end caps at both ends.
|
|
1169
|
+
|
|
1170
|
+
Parameters
|
|
1171
|
+
----------
|
|
1172
|
+
point_a : array-like of shape (3,)
|
|
1173
|
+
First endpoint of the cone axis (base center).
|
|
1174
|
+
point_b : array-like of shape (3,)
|
|
1175
|
+
Second endpoint of the cone axis (top center).
|
|
1176
|
+
ra : float
|
|
1177
|
+
Radius at point_a (base radius). Must be non-negative.
|
|
1178
|
+
rb : float
|
|
1179
|
+
Radius at point_b (top radius). Use 0 for a sharp cone tip.
|
|
1180
|
+
|
|
1181
|
+
Examples
|
|
1182
|
+
--------
|
|
1183
|
+
>>> # Cone from (0,0,0) to (0,0,2) with base radius 0.5 and sharp tip
|
|
1184
|
+
>>> CappedConeSDF(point_a=[0, 0, 0], point_b=[0, 0, 2], ra=0.5, rb=0.0)
|
|
1185
|
+
>>>
|
|
1186
|
+
>>> # Cone frustum with radii at both ends
|
|
1187
|
+
>>> CappedConeSDF(point_a=[0, 0, 0], point_b=[0, 0, 1], ra=0.3, rb=0.1)
|
|
1188
|
+
|
|
1189
|
+
Notes
|
|
1190
|
+
-----
|
|
1191
|
+
- The cone axis is the line segment from point_a to point_b
|
|
1192
|
+
- ra and rb control the radii at each end independently
|
|
1193
|
+
- Set rb=0 for a cone with a sharp tip
|
|
1194
|
+
- Set ra=rb for a cylinder (use CylinderSDF instead for better accuracy)
|
|
1195
|
+
"""
|
|
1196
|
+
|
|
1197
|
+
def __init__(self, point_a, point_b, ra, rb):
|
|
1198
|
+
"""Initialize CappedConeSDF.
|
|
1199
|
+
|
|
1200
|
+
Parameters
|
|
1201
|
+
----------
|
|
1202
|
+
point_a : array-like of shape (3,)
|
|
1203
|
+
First endpoint of the cone axis.
|
|
1204
|
+
point_b : array-like of shape (3,)
|
|
1205
|
+
Second endpoint of the cone axis.
|
|
1206
|
+
ra : float
|
|
1207
|
+
Radius at point_a.
|
|
1208
|
+
rb : float
|
|
1209
|
+
Radius at point_b.
|
|
1210
|
+
"""
|
|
1211
|
+
super().__init__()
|
|
1212
|
+
self.point_a = torch.nn.Parameter(torch.as_tensor(point_a, dtype=torch.float32))
|
|
1213
|
+
self.point_b = torch.nn.Parameter(torch.as_tensor(point_b, dtype=torch.float32))
|
|
1214
|
+
self.ra = torch.nn.Parameter(torch.as_tensor(ra, dtype=torch.float32))
|
|
1215
|
+
self.rb = torch.nn.Parameter(torch.as_tensor(rb, dtype=torch.float32))
|
|
1216
|
+
|
|
1217
|
+
def _compute(self, queries: torch.Tensor) -> torch.Tensor:
|
|
1218
|
+
logger.debug(f"{type(self).__name__}._compute - {queries.shape[0]} points")
|
|
1219
|
+
a = self.point_a.to(device=queries.device, dtype=queries.dtype)
|
|
1220
|
+
b = self.point_b.to(device=queries.device, dtype=queries.dtype)
|
|
1221
|
+
ra = self.ra.to(device=queries.device, dtype=queries.dtype)
|
|
1222
|
+
rb = self.rb.to(device=queries.device, dtype=queries.dtype)
|
|
1223
|
+
|
|
1224
|
+
rba = rb - ra
|
|
1225
|
+
baba = torch.sum((b - a) ** 2)
|
|
1226
|
+
p = queries
|
|
1227
|
+
papa = torch.sum((p - a) ** 2, dim=1)
|
|
1228
|
+
paba = torch.sum((p - a) * (b - a), dim=1) / baba
|
|
1229
|
+
|
|
1230
|
+
# Distance to cone side
|
|
1231
|
+
q = torch.sqrt(torch.clamp(papa - paba * paba * baba, min=0.0))
|
|
1232
|
+
ca = torch.maximum(torch.tensor(0.0), q - torch.where(paba < 0.5, ra, rb))
|
|
1233
|
+
cay = torch.abs(paba - 0.5) - 0.5
|
|
1234
|
+
|
|
1235
|
+
cax = ca
|
|
1236
|
+
cbx = (
|
|
1237
|
+
q
|
|
1238
|
+
- ra
|
|
1239
|
+
- (torch.clamp((rba * (q - ra) + paba * baba) / (rba * rba + baba), 0, 1))
|
|
1240
|
+
* rba
|
|
1241
|
+
)
|
|
1242
|
+
cby = paba - torch.clamp(
|
|
1243
|
+
(rba * (q - ra) + paba * baba) / (rba * rba + baba), 0, 1
|
|
1244
|
+
)
|
|
1245
|
+
|
|
1246
|
+
s = torch.where((cbx < 0) & (cay < 0), -1, 1)
|
|
1247
|
+
|
|
1248
|
+
a_dist = cax**2 + cay**2 * baba
|
|
1249
|
+
b_dist = cbx**2 + cby**2 * baba
|
|
1250
|
+
|
|
1251
|
+
# Determine which distance to use
|
|
1252
|
+
mask = (ca < 0) & (cay < 0)
|
|
1253
|
+
d2 = torch.where(mask, torch.tensor(0.0), torch.minimum(a_dist, b_dist))
|
|
1254
|
+
|
|
1255
|
+
d = torch.sign(d2) * torch.sqrt(torch.abs(d2)) * s
|
|
1256
|
+
return d.reshape(-1, 1)
|
|
1257
|
+
|
|
1258
|
+
def _get_domain_bounds(self) -> torch.Tensor:
|
|
1259
|
+
r = max(self.ra.detach().item(), self.rb.detach().item())
|
|
1260
|
+
h = torch.linalg.norm(self.point_b.detach() - self.point_a.detach())
|
|
1261
|
+
return torch.tensor([[-r, -r, 0], [r, r, h]])
|
|
1262
|
+
|
|
1263
|
+
|
|
1264
|
+
class RoundedConeSDF(SDFBase):
|
|
1265
|
+
"""SDF for a cone with smooth rounded transitions."""
|
|
1266
|
+
|
|
1267
|
+
def __init__(self, r1, r2, h):
|
|
1268
|
+
super().__init__()
|
|
1269
|
+
self.r1 = torch.nn.Parameter(torch.as_tensor(r1, dtype=torch.float32))
|
|
1270
|
+
self.r2 = torch.nn.Parameter(torch.as_tensor(r2, dtype=torch.float32))
|
|
1271
|
+
self.h = torch.nn.Parameter(torch.as_tensor(h, dtype=torch.float32))
|
|
1272
|
+
|
|
1273
|
+
def _compute(self, queries: torch.Tensor) -> torch.Tensor:
|
|
1274
|
+
logger.debug(f"{type(self).__name__}._compute - {queries.shape[0]} points")
|
|
1275
|
+
r1 = self.r1.to(device=queries.device, dtype=queries.dtype)
|
|
1276
|
+
r2 = self.r2.to(device=queries.device, dtype=queries.dtype)
|
|
1277
|
+
h = self.h.to(device=queries.device, dtype=queries.dtype)
|
|
1278
|
+
|
|
1279
|
+
q = torch.stack(
|
|
1280
|
+
[torch.sqrt(queries[:, 0] ** 2 + queries[:, 1] ** 2), queries[:, 2]], dim=1
|
|
1281
|
+
)
|
|
1282
|
+
b = (r1 - r2) / h
|
|
1283
|
+
a = torch.sqrt(1 - b * b)
|
|
1284
|
+
k = torch.sum(q * torch.stack([-b, a]), dim=1)
|
|
1285
|
+
|
|
1286
|
+
c1 = torch.linalg.norm(q, dim=1) - r1
|
|
1287
|
+
c2 = torch.linalg.norm(q - torch.stack([torch.tensor(0.0), h]), dim=1) - r2
|
|
1288
|
+
c3 = torch.sum(q * torch.stack([a, b]), dim=1) - r1
|
|
1289
|
+
|
|
1290
|
+
return torch.where(k < 0, c1, torch.where(k > a * h, c2, c3)).reshape(-1, 1)
|
|
1291
|
+
|
|
1292
|
+
def _get_domain_bounds(self) -> torch.Tensor:
|
|
1293
|
+
r = max(self.r1.detach().item(), self.r2.detach().item())
|
|
1294
|
+
h = self.h.detach().item()
|
|
1295
|
+
return torch.tensor([[-r, -r, 0], [r, r, h]])
|
|
1296
|
+
|
|
1297
|
+
|
|
1298
|
+
class TetrahedronSDF(SDFBase):
|
|
1299
|
+
"""SDF for a regular tetrahedron."""
|
|
1300
|
+
|
|
1301
|
+
def __init__(self, r=1.0):
|
|
1302
|
+
super().__init__()
|
|
1303
|
+
self.r = torch.nn.Parameter(torch.as_tensor(r, dtype=torch.float32))
|
|
1304
|
+
|
|
1305
|
+
def _compute(self, queries: torch.Tensor) -> torch.Tensor:
|
|
1306
|
+
logger.debug(f"{type(self).__name__}._compute - {queries.shape[0]} points")
|
|
1307
|
+
r = self.r.to(device=queries.device, dtype=queries.dtype)
|
|
1308
|
+
result = (
|
|
1309
|
+
torch.maximum(
|
|
1310
|
+
torch.abs(queries[:, 0] + queries[:, 1]) - queries[:, 2],
|
|
1311
|
+
torch.abs(queries[:, 0] - queries[:, 1]) + queries[:, 2],
|
|
1312
|
+
)
|
|
1313
|
+
- r
|
|
1314
|
+
)
|
|
1315
|
+
return result.reshape(-1, 1) / torch.sqrt(torch.tensor(3.0))
|
|
1316
|
+
|
|
1317
|
+
def _get_domain_bounds(self) -> torch.Tensor:
|
|
1318
|
+
r = self.r.item()
|
|
1319
|
+
return torch.tensor([[-r, -r, -r], [r, r, r]])
|
|
1320
|
+
|
|
1321
|
+
|
|
1322
|
+
class OctahedronSDF(SDFBase):
|
|
1323
|
+
"""SDF for a regular octahedron."""
|
|
1324
|
+
|
|
1325
|
+
def __init__(self, r=1.0):
|
|
1326
|
+
super().__init__()
|
|
1327
|
+
self.r = torch.nn.Parameter(torch.as_tensor(r, dtype=torch.float32))
|
|
1328
|
+
|
|
1329
|
+
def _compute(self, queries: torch.Tensor) -> torch.Tensor:
|
|
1330
|
+
logger.debug(f"{type(self).__name__}._compute - {queries.shape[0]} points")
|
|
1331
|
+
r = self.r.to(device=queries.device, dtype=queries.dtype)
|
|
1332
|
+
return (
|
|
1333
|
+
(torch.sum(torch.abs(queries), dim=1) - r)
|
|
1334
|
+
* torch.tan(torch.tensor(np.pi / 6))
|
|
1335
|
+
).reshape(-1, 1)
|
|
1336
|
+
|
|
1337
|
+
def _get_domain_bounds(self) -> torch.Tensor:
|
|
1338
|
+
r = self.r.item()
|
|
1339
|
+
return torch.tensor([[-r, -r, -r], [r, r, r]])
|
|
1340
|
+
|
|
1341
|
+
|
|
1342
|
+
class DodecahedronSDF(SDFBase):
|
|
1343
|
+
"""SDF for a regular dodecahedron."""
|
|
1344
|
+
|
|
1345
|
+
def __init__(self, r=1.0):
|
|
1346
|
+
super().__init__()
|
|
1347
|
+
self.r = torch.nn.Parameter(torch.as_tensor(r, dtype=torch.float32))
|
|
1348
|
+
|
|
1349
|
+
def _compute(self, queries: torch.Tensor) -> torch.Tensor:
|
|
1350
|
+
logger.debug(f"{type(self).__name__}._compute - {queries.shape[0]} points")
|
|
1351
|
+
r = self.r.to(device=queries.device, dtype=queries.dtype)
|
|
1352
|
+
|
|
1353
|
+
# Golden ratio
|
|
1354
|
+
golden = (1 + torch.sqrt(torch.tensor(5.0))) / 2
|
|
1355
|
+
# Normalized vertices of dodecahedron face normals
|
|
1356
|
+
vn = torch.tensor([golden, 1, 0], dtype=torch.float32)
|
|
1357
|
+
vn = vn / torch.linalg.norm(vn)
|
|
1358
|
+
|
|
1359
|
+
p = torch.abs(queries / r)
|
|
1360
|
+
a = torch.sum(p * vn, dim=1)
|
|
1361
|
+
b = torch.sum(
|
|
1362
|
+
p * torch.tensor([vn[1], vn[2], vn[0]], dtype=torch.float32), dim=1
|
|
1363
|
+
)
|
|
1364
|
+
c = torch.sum(
|
|
1365
|
+
p * torch.tensor([vn[2], vn[0], vn[1]], dtype=torch.float32), dim=1
|
|
1366
|
+
)
|
|
1367
|
+
|
|
1368
|
+
q = (torch.maximum(torch.maximum(a, b), c) - vn[0]) * r
|
|
1369
|
+
return q.reshape(-1, 1)
|
|
1370
|
+
|
|
1371
|
+
def _get_domain_bounds(self) -> torch.Tensor:
|
|
1372
|
+
r = self.r.item()
|
|
1373
|
+
return torch.tensor([[-r, -r, -r], [r, r, r]])
|
|
1374
|
+
|
|
1375
|
+
|
|
1376
|
+
class IcosahedronSDF(SDFBase):
|
|
1377
|
+
"""SDF for a regular icosahedron."""
|
|
1378
|
+
|
|
1379
|
+
def __init__(self, r=1.0):
|
|
1380
|
+
super().__init__()
|
|
1381
|
+
self.r = torch.nn.Parameter(torch.as_tensor(r, dtype=torch.float32))
|
|
1382
|
+
|
|
1383
|
+
def _compute(self, queries: torch.Tensor) -> torch.Tensor:
|
|
1384
|
+
logger.debug(f"{type(self).__name__}._compute - {queries.shape[0]} points")
|
|
1385
|
+
r = self.r.to(device=queries.device, dtype=queries.dtype)
|
|
1386
|
+
r_scaled = r * torch.tensor(0.8506507174597755)
|
|
1387
|
+
|
|
1388
|
+
# Normalized icosahedron vertices
|
|
1389
|
+
vn_raw = torch.tensor(
|
|
1390
|
+
[(torch.sqrt(torch.tensor(5.0)) + 3) / 2, 1, 0], dtype=torch.float32
|
|
1391
|
+
)
|
|
1392
|
+
vn = vn_raw / torch.linalg.norm(vn_raw)
|
|
1393
|
+
|
|
1394
|
+
w = torch.sqrt(torch.tensor(3.0)) / 3
|
|
1395
|
+
|
|
1396
|
+
p = torch.abs(queries / r_scaled)
|
|
1397
|
+
a = torch.sum(p * vn, dim=1)
|
|
1398
|
+
|
|
1399
|
+
vn2 = torch.tensor([vn[1], vn[2], vn[0]], dtype=torch.float32)
|
|
1400
|
+
b = torch.sum(p * vn2, dim=1)
|
|
1401
|
+
|
|
1402
|
+
vn3 = torch.tensor([vn[2], vn[0], vn[1]], dtype=torch.float32)
|
|
1403
|
+
c = torch.sum(p * vn3, dim=1)
|
|
1404
|
+
|
|
1405
|
+
d = torch.sum(p * torch.tensor([w, w, w]), dim=1) - vn[0]
|
|
1406
|
+
|
|
1407
|
+
return (
|
|
1408
|
+
torch.maximum(torch.maximum(torch.maximum(a, b), c) - vn[0], d).reshape(
|
|
1409
|
+
-1, 1
|
|
1410
|
+
)
|
|
1411
|
+
* r_scaled
|
|
1412
|
+
)
|
|
1413
|
+
|
|
1414
|
+
def _get_domain_bounds(self) -> torch.Tensor:
|
|
1415
|
+
r = self.r.item() * 0.8506507174597755
|
|
1416
|
+
return torch.tensor([[-r, -r, -r], [r, r, r]])
|