NVIDIA Modulus Sym v1.0.0
Sym v1.0.0

# Source code for modulus.sym.geometry.primitives_3d

# Copyright (c) 2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
Primitives for 3D geometries
see https://www.iquilezles.org/www/articles/distfunctions/distfunctions.html
"""

from sympy import (
Symbol,
Function,
Abs,
Max,
Min,
sqrt,
pi,
sin,
cos,
atan,
atan2,
acos,
asin,
sign,
)

from sympy.vector import CoordSys3D
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from .geometry import Geometry, csg_curve_naming
from .helper import _sympy_sdf_to_sdf
from .curve import SympyCurve, Curve
from .parameterization import Parameterization, Parameter, Bounds
from ..constants import diff_str

[docs]class Plane(Geometry):
"""
3D Plane perpendicular to x-axis

Parameters
----------
point_1 : tuple with 3 ints or floats
lower bound point of plane
point_2 : tuple with 3 ints or floats
upper bound point of plane
parameterization : Parameterization
Parameterization of geometry.
"""

def __init__(self, point_1, point_2, normal=1, parameterization=Parameterization()):
assert (
point_1[0] == point_2[0]
), "Points must have same coordinate on normal dim"

# make sympy symbols to use
x, y, z = Symbol("x"), Symbol("y"), Symbol("z")
s_1, s_2 = Symbol(csg_curve_naming(0)), Symbol(csg_curve_naming(1))
center = (
point_1[0] + (point_2[0] - point_1[0]) / 2,
point_1[1] + (point_2[1] - point_1[1]) / 2,
point_1[2] + (point_2[2] - point_1[2]) / 2,
)
side_y = point_2[1] - point_1[1]
side_z = point_2[2] - point_1[2]

# surface of the plane
curve_parameterization = Parameterization({s_1: (-1, 1), s_2: (-1, 1)})
curve_parameterization = Parameterization.combine(
curve_parameterization, parameterization
)
curve_1 = SympyCurve(
functions={
"x": center[0],
"y": center[1] + 0.5 * s_1 * side_y,
"z": center[2] + 0.5 * s_2 * side_z,
"normal_x": 1e-10 + normal,  # TODO rm 1e-10
"normal_y": 0,
"normal_z": 0,
},
parameterization=curve_parameterization,
area=side_y * side_z,
)
curves = [curve_1]

# calculate SDF
sdf = normal * (center[0] - x)

# calculate bounds
bounds = Bounds(
{
Parameter("x"): (point_1[0], point_2[0]),
Parameter("y"): (point_1[1], point_2[1]),
Parameter("z"): (point_1[2], point_2[2]),
},
parameterization=parameterization,
)

# initialize Plane
super().__init__(
curves,
_sympy_sdf_to_sdf(sdf),
dims=3,
bounds=bounds,
parameterization=parameterization,
)

[docs]class Channel(Geometry):
"""
3D Channel (no bounding surfaces in x-direction)

Parameters
----------
point_1 : tuple with 3 ints or floats
lower bound point of channel
point_2 : tuple with 3 ints or floats
upper bound point of channel
parameterization : Parameterization
Parameterization of geometry.
"""

def __init__(self, point_1, point_2, parameterization=Parameterization()):
# make sympy symbols to use
x, y, z = Symbol("x"), Symbol("y"), Symbol("z")
s_1, s_2 = Symbol(csg_curve_naming(0)), Symbol(csg_curve_naming(1))
center = (
point_1[0] + (point_2[0] - point_1[0]) / 2,
point_1[1] + (point_2[1] - point_1[1]) / 2,
point_1[2] + (point_2[2] - point_1[2]) / 2,
)
side_x = point_2[0] - point_1[0]
side_y = point_2[1] - point_1[1]
side_z = point_2[2] - point_1[2]

# surface of the channel
curve_parameterization = Parameterization({s_1: (-1, 1), s_2: (-1, 1)})
curve_parameterization = Parameterization.combine(
curve_parameterization, parameterization
)
curve_1 = SympyCurve(
functions={
"x": center[0] + 0.5 * s_1 * side_x,
"y": center[1] + 0.5 * s_2 * side_y,
"z": center[2] + 0.5 * side_z,
"normal_x": 0,
"normal_y": 0,
"normal_z": 1,
},
parameterization=curve_parameterization,
area=side_x * side_y,
)
curve_2 = SympyCurve(
functions={
"x": center[0] + 0.5 * s_1 * side_x,
"y": center[1] + 0.5 * s_2 * side_y,
"z": center[2] - 0.5 * side_z,
"normal_x": 0,
"normal_y": 0,
"normal_z": -1,
},
parameterization=curve_parameterization,
area=side_x * side_y,
)
curve_3 = SympyCurve(
functions={
"x": center[0] + 0.5 * s_1 * side_x,
"y": center[1] + 0.5 * side_y,
"z": center[2] + 0.5 * s_2 * side_z,
"normal_x": 0,
"normal_y": 1,
"normal_z": 0,
},
parameterization=curve_parameterization,
area=side_x * side_z,
)
curve_4 = SympyCurve(
functions={
"x": center[0] + 0.5 * s_1 * side_x,
"y": center[1] - 0.5 * side_y,
"z": center[2] + 0.5 * s_2 * side_z,
"normal_x": 0,
"normal_y": -1,
"normal_z": 0,
},
parameterization=curve_parameterization,
area=side_x * side_z,
)
curves = [curve_1, curve_2, curve_3, curve_4]

# calculate SDF
y_dist = Abs(y - center[1]) - 0.5 * side_y
z_dist = Abs(z - center[2]) - 0.5 * side_z
outside_distance = sqrt(Max(y_dist, 0) ** 2 + Max(z_dist, 0) ** 2)
inside_distance = Min(Max(y_dist, z_dist), 0)
sdf = -(outside_distance + inside_distance)

# calculate bounds
bounds = Bounds(
{
Parameter("x"): (point_1[0], point_2[0]),
Parameter("y"): (point_1[1], point_2[1]),
Parameter("z"): (point_1[2], point_2[2]),
},
parameterization=parameterization,
)

# initialize Channel
super().__init__(
curves,
_sympy_sdf_to_sdf(sdf),
dims=3,
bounds=bounds,
parameterization=parameterization,
)

[docs]class Box(Geometry):
"""
3D Box/Cuboid

Parameters
----------
point_1 : tuple with 3 ints or floats
lower bound point of box
point_2 : tuple with 3 ints or floats
upper bound point of box
parameterization : Parameterization
Parameterization of geometry.
"""

def __init__(self, point_1, point_2, parameterization=Parameterization()):
# make sympy symbols to use
x, y, z = Symbol("x"), Symbol("y"), Symbol("z")
s_1, s_2 = Symbol(csg_curve_naming(0)), Symbol(csg_curve_naming(1))
center = (
point_1[0] + (point_2[0] - point_1[0]) / 2,
point_1[1] + (point_2[1] - point_1[1]) / 2,
point_1[2] + (point_2[2] - point_1[2]) / 2,
)
side_x = point_2[0] - point_1[0]
side_y = point_2[1] - point_1[1]
side_z = point_2[2] - point_1[2]

# surface of the box
curve_parameterization = Parameterization({s_1: (-1, 1), s_2: (-1, 1)})
curve_parameterization = Parameterization.combine(
curve_parameterization, parameterization
)
curve_1 = SympyCurve(
functions={
"x": center[0] + 0.5 * s_1 * side_x,
"y": center[1] + 0.5 * s_2 * side_y,
"z": center[2] + 0.5 * side_z,
"normal_x": 0,
"normal_y": 0,
"normal_z": 1,
},
parameterization=curve_parameterization,
area=side_x * side_y,
)
curve_2 = SympyCurve(
functions={
"x": center[0] + 0.5 * s_1 * side_x,
"y": center[1] + 0.5 * s_2 * side_y,
"z": center[2] - 0.5 * side_z,
"normal_x": 0,
"normal_y": 0,
"normal_z": -1,
},
parameterization=curve_parameterization,
area=side_x * side_y,
)
curve_3 = SympyCurve(
functions={
"x": center[0] + 0.5 * s_1 * side_x,
"y": center[1] + 0.5 * side_y,
"z": center[2] + 0.5 * s_2 * side_z,
"normal_x": 0,
"normal_y": 1,
"normal_z": 0,
},
parameterization=curve_parameterization,
area=side_x * side_z,
)
curve_4 = SympyCurve(
functions={
"x": center[0] + 0.5 * s_1 * side_x,
"y": center[1] - 0.5 * side_y,
"z": center[2] + 0.5 * s_2 * side_z,
"normal_x": 0,
"normal_y": -1,
"normal_z": 0,
},
parameterization=curve_parameterization,
area=side_x * side_z,
)
curve_5 = SympyCurve(
functions={
"x": center[0] + 0.5 * side_x,
"y": center[1] + 0.5 * s_1 * side_y,
"z": center[2] + 0.5 * s_2 * side_z,
"normal_x": 1,
"normal_y": 0,
"normal_z": 0,
},
parameterization=curve_parameterization,
area=side_y * side_z,
)
curve_6 = SympyCurve(
functions={
"x": center[0] - 0.5 * side_x,
"y": center[1] + 0.5 * s_1 * side_y,
"z": center[2] + 0.5 * s_2 * side_z,
"normal_x": -1,
"normal_y": 0,
"normal_z": 0,
},
parameterization=curve_parameterization,
area=side_y * side_z,
)
curves = [curve_1, curve_2, curve_3, curve_4, curve_5, curve_6]

# calculate SDF
x_dist = Abs(x - center[0]) - 0.5 * side_x
y_dist = Abs(y - center[1]) - 0.5 * side_y
z_dist = Abs(z - center[2]) - 0.5 * side_z
outside_distance = sqrt(
Max(x_dist, 0) ** 2 + Max(y_dist, 0) ** 2 + Max(z_dist, 0) ** 2
)
inside_distance = Min(Max(x_dist, y_dist, z_dist), 0)
sdf = -(outside_distance + inside_distance)

# calculate bounds
bounds = Bounds(
{
Parameter("x"): (point_1[0], point_2[0]),
Parameter("y"): (point_1[1], point_2[1]),
Parameter("z"): (point_1[2], point_2[2]),
},
parameterization=parameterization,
)

# initialize Box
super().__init__(
curves,
_sympy_sdf_to_sdf(sdf),
dims=3,
bounds=bounds,
parameterization=parameterization,
)

[docs]class VectorizedBoxes(Geometry):
"""
Vectorized 3D Box/Cuboid for faster surface and interior sampling.
This primitive can be used if many boxes are required and is
significantly faster then combining many boxes together with Boolean
operations.

Parameters
----------
box_bounds : np.ndarray
An array specifying the bounds of boxes. Shape of array is
[nr_boxes, 3, 2] where the last dim stores the lower and
upper bounds respectively.
dx : float
delta x used for SDF derivative calculations.
"""

def __init__(self, box_bounds, dx=0.0001):
# compute box centers and sides once for optimization
box_centers = (
box_bounds[:, :, 0] + (box_bounds[:, :, 1] - box_bounds[:, :, 0]) / 2
)
side = box_bounds[:, :, 1] - box_bounds[:, :, 0]

# create curves
def _sample(box_bounds, box_centers, side):
def sample(nr_points, parameterization, quasirandom):
# area of all faces
face_area = np.concatenate(
2
* [
side[:, 0] * side[:, 1],
side[:, 0] * side[:, 2],
side[:, 1] * side[:, 2],
]
)  # [6 * nr_boxes]

# calculate number or points per face
face_probabilities = face_area / np.linalg.norm(face_area, ord=1)
face_index = np.arange(face_area.shape[0])
points_per_face = np.random.choice(
face_index, nr_points, p=face_probabilities
)
points_per_face, _ = np.histogram(
points_per_face, np.arange(face_area.shape[0] + 1) - 0.5
)

# generate random values to use when sampling faces
s_1 = 2.0 * (np.random.rand(nr_points) - 0.5)
s_2 = 2.0 * (np.random.rand(nr_points) - 0.5)

# repeat side and center for each point
repeat_side = np.repeat(
np.concatenate(6 * [side], axis=0), points_per_face, axis=0
)
repeat_centers = np.repeat(
np.concatenate(6 * [box_centers], axis=0), points_per_face, axis=0
)
repeat_face_area = np.repeat(
face_area / points_per_face, points_per_face, axis=0
)

# sample face 1
nr_face_1 = np.sum(points_per_face[0 : box_bounds.shape[0]])
face_1_x = (
repeat_centers[:nr_face_1, 0]
+ 0.5 * s_1[:nr_face_1] * repeat_side[:nr_face_1, 0]
)
face_1_y = (
repeat_centers[:nr_face_1, 1]
+ 0.5 * s_2[:nr_face_1] * repeat_side[:nr_face_1, 1]
)
face_1_z = (
repeat_centers[:nr_face_1, 2] + 0.5 * repeat_side[:nr_face_1, 2]
)
face_1_normal_x = np.zeros_like(face_1_x)
face_1_normal_y = np.zeros_like(face_1_x)
face_1_normal_z = np.ones_like(face_1_x)
area_1 = repeat_face_area[:nr_face_1]

# sample face 2
nr_face_2 = (
np.sum(
points_per_face[box_bounds.shape[0] : 2 * box_bounds.shape[0]]
)
+ nr_face_1
)
face_2_x = (
repeat_centers[nr_face_1:nr_face_2, 0]
+ 0.5
* s_1[nr_face_1:nr_face_2]
* repeat_side[nr_face_1:nr_face_2, 0]
)
face_2_y = (
repeat_centers[nr_face_1:nr_face_2, 1]
+ 0.5 * repeat_side[nr_face_1:nr_face_2, 1]
)
face_2_z = (
repeat_centers[nr_face_1:nr_face_2, 2]
+ 0.5
* s_2[nr_face_1:nr_face_2]
* repeat_side[nr_face_1:nr_face_2, 2]
)
face_2_normal_x = np.zeros_like(face_2_x)
face_2_normal_y = np.ones_like(face_2_x)
face_2_normal_z = np.zeros_like(face_2_x)
area_2 = repeat_face_area[nr_face_1:nr_face_2]

# sample face 3
nr_face_3 = (
np.sum(
points_per_face[
2 * box_bounds.shape[0] : 3 * box_bounds.shape[0]
]
)
+ nr_face_2
)
face_3_x = (
repeat_centers[nr_face_2:nr_face_3, 0]
+ 0.5 * repeat_side[nr_face_2:nr_face_3, 0]
)
face_3_y = (
repeat_centers[nr_face_2:nr_face_3, 1]
+ 0.5
* s_1[nr_face_2:nr_face_3]
* repeat_side[nr_face_2:nr_face_3, 1]
)
face_3_z = (
repeat_centers[nr_face_2:nr_face_3, 2]
+ 0.5
* s_2[nr_face_2:nr_face_3]
* repeat_side[nr_face_2:nr_face_3, 2]
)
face_3_normal_x = np.ones_like(face_3_x)
face_3_normal_y = np.zeros_like(face_3_x)
face_3_normal_z = np.zeros_like(face_3_x)
area_3 = repeat_face_area[nr_face_2:nr_face_3]

# sample face 4
nr_face_4 = (
np.sum(
points_per_face[
3 * box_bounds.shape[0] : 4 * box_bounds.shape[0]
]
)
+ nr_face_3
)
face_4_x = (
repeat_centers[nr_face_3:nr_face_4, 0]
+ 0.5
* s_1[nr_face_3:nr_face_4]
* repeat_side[nr_face_3:nr_face_4, 0]
)
face_4_y = (
repeat_centers[nr_face_3:nr_face_4, 1]
+ 0.5
* s_2[nr_face_3:nr_face_4]
* repeat_side[nr_face_3:nr_face_4, 1]
)
face_4_z = (
repeat_centers[nr_face_3:nr_face_4, 2]
- 0.5 * repeat_side[nr_face_3:nr_face_4, 2]
)
face_4_normal_x = np.zeros_like(face_4_x)
face_4_normal_y = np.zeros_like(face_4_x)
face_4_normal_z = -np.ones_like(face_4_x)
area_4 = repeat_face_area[nr_face_3:nr_face_4]

# sample face 5
nr_face_5 = (
np.sum(
points_per_face[
4 * box_bounds.shape[0] : 5 * box_bounds.shape[0]
]
)
+ nr_face_4
)
face_5_x = (
repeat_centers[nr_face_4:nr_face_5, 0]
+ 0.5
* s_1[nr_face_4:nr_face_5]
* repeat_side[nr_face_4:nr_face_5, 0]
)
face_5_y = (
repeat_centers[nr_face_4:nr_face_5, 1]
- 0.5 * repeat_side[nr_face_4:nr_face_5, 1]
)
face_5_z = (
repeat_centers[nr_face_4:nr_face_5, 2]
+ 0.5
* s_2[nr_face_4:nr_face_5]
* repeat_side[nr_face_4:nr_face_5, 2]
)
face_5_normal_x = np.zeros_like(face_5_x)
face_5_normal_y = -np.ones_like(face_5_x)
face_5_normal_z = np.zeros_like(face_5_x)
area_5 = repeat_face_area[nr_face_4:nr_face_5]

# sample face 6
nr_face_6 = (
np.sum(points_per_face[5 * box_bounds.shape[0] :]) + nr_face_5
)
face_6_x = (
repeat_centers[nr_face_5:nr_face_6, 0]
- 0.5 * repeat_side[nr_face_5:nr_face_6, 0]
)
face_6_y = (
repeat_centers[nr_face_5:nr_face_6, 1]
+ 0.5
* s_1[nr_face_5:nr_face_6]
* repeat_side[nr_face_5:nr_face_6, 1]
)
face_6_z = (
repeat_centers[nr_face_5:nr_face_6, 2]
+ 0.5
* s_2[nr_face_5:nr_face_6]
* repeat_side[nr_face_5:nr_face_6, 2]
)
face_6_normal_x = -np.ones_like(face_6_x)
face_6_normal_y = np.zeros_like(face_6_x)
face_6_normal_z = np.zeros_like(face_6_x)
area_6 = repeat_face_area[nr_face_5:nr_face_6]

# gather for invar
invar = {
"x": np.concatenate(
[face_1_x, face_2_x, face_3_x, face_4_x, face_5_x, face_6_x],
axis=0,
)[:, None],
"y": np.concatenate(
[face_1_y, face_2_y, face_3_y, face_4_y, face_5_y, face_6_y],
axis=0,
)[:, None],
"z": np.concatenate(
[face_1_z, face_2_z, face_3_z, face_4_z, face_5_z, face_6_z],
axis=0,
)[:, None],
"normal_x": np.concatenate(
[
face_1_normal_x,
face_2_normal_x,
face_3_normal_x,
face_4_normal_x,
face_5_normal_x,
face_6_normal_x,
],
axis=0,
)[:, None],
"normal_y": np.concatenate(
[
face_1_normal_y,
face_2_normal_y,
face_3_normal_y,
face_4_normal_y,
face_5_normal_y,
face_6_normal_y,
],
axis=0,
)[:, None],
"normal_z": np.concatenate(
[
face_1_normal_z,
face_2_normal_z,
face_3_normal_z,
face_4_normal_z,
face_5_normal_z,
face_6_normal_z,
],
axis=0,
)[:, None],
"area": np.concatenate(
[area_1, area_2, area_3, area_4, area_5, area_6], axis=0
)[:, None],
}
return invar, {}

return sample

curves = [Curve(_sample(box_bounds, box_centers, side), dims=3)]

# create closure for SDF function
def _sdf(box_bounds, box_centers, side, dx):
def sdf(invar, param_ranges={}, compute_sdf_derivatives=False):
# get input and tile for each box
xyz = np.stack([invar["x"], invar["y"], invar["z"]], axis=-1)
xyz = np.tile(np.expand_dims(xyz, 1), (1, box_bounds.shape[0], 1))

# compute distance
outputs = {"sdf": VectorizedBoxes._sdf_box(xyz, box_centers, side)}

# compute distance derivatives if needed
if compute_sdf_derivatives:
for i, d in enumerate(["x", "y", "z"]):
# compute sdf plus dx/2
plus_xyz = np.copy(xyz)
plus_xyz[..., i] += dx / 2
computed_sdf_plus = VectorizedBoxes._sdf_box(
plus_xyz, box_centers, side
)

# compute sdf minus dx/2
minus_xyz = np.copy(xyz)
minus_xyz[..., i] -= dx / 2
computed_sdf_minus = VectorizedBoxes._sdf_box(
minus_xyz, box_centers, side
)

# store sdf derivative
outputs["sdf" + diff_str + d] = (
computed_sdf_plus - computed_sdf_minus
) / dx
return outputs

return sdf

# create bounds
bounds = Bounds(
{
"x": (np.min(box_bounds[:, 0, 0]), np.max(box_bounds[:, 0, 1])),
"y": (np.min(box_bounds[:, 1, 0]), np.max(box_bounds[:, 1, 1])),
"z": (np.min(box_bounds[:, 2, 0]), np.max(box_bounds[:, 2, 1])),
}
)

# initialize geometry
Geometry.__init__(
self, curves, _sdf(box_bounds, box_centers, side, dx), bounds=bounds, dims=3
)

@staticmethod
def _sdf_box(xyz, box_centers, side):
xyz_dist = np.abs(xyz - np.expand_dims(box_centers, 0)) - 0.5 * np.expand_dims(
side, 0
)
outside_distance = np.sqrt(np.sum(np.maximum(xyz_dist, 0) ** 2, axis=-1))
inside_distance = np.minimum(np.max(xyz_dist, axis=-1), 0)
return np.max(-(outside_distance + inside_distance), axis=-1)

[docs]class Sphere(Geometry):
"""
3D Sphere

Parameters
----------
center : tuple with 3 ints or floats
center of sphere
radius : int or float
parameterization : Parameterization
Parameterization of geometry.
"""

def __init__(self, center, radius, parameterization=Parameterization()):
# make sympy symbols to use
x, y, z = Symbol("x"), Symbol("y"), Symbol("z")
r_1, r_2, r_3 = (
Symbol(csg_curve_naming(0)),
Symbol(csg_curve_naming(1)),
Symbol(csg_curve_naming(2)),
)

# surface of the sphere
curve_parameterization = Parameterization(
{r_1: (-1, 1), r_2: (-1, 1), r_3: (-1, 1)}
)
curve_parameterization = Parameterization.combine(
curve_parameterization, parameterization
)
norm = sqrt(r_1**2 + r_2**2 + r_3**2)
curve_1 = SympyCurve(
functions={
"x": center[0] + radius * r_1 / norm,  # TODO GAUSSIAN DIST
"y": center[1] + radius * r_2 / norm,
"z": center[2] + radius * r_3 / norm,
"normal_x": r_1 / norm,
"normal_y": r_2 / norm,
"normal_z": r_3 / norm,
},
parameterization=curve_parameterization,
area=4 * pi * radius**2,
)
curves = [curve_1]

# calculate SDF
sdf = radius - sqrt(
(x - center[0]) ** 2 + (y - center[1]) ** 2 + (z - center[2]) ** 2
)

# calculate bounds
bounds = Bounds(
{
Parameter("x"): (center[0] - radius, center[0] + radius),
Parameter("y"): (center[1] - radius, center[1] + radius),
Parameter("z"): (center[2] - radius, center[2] + radius),
},
parameterization=parameterization,
)

# initialize Sphere
super().__init__(
curves,
_sympy_sdf_to_sdf(sdf),
dims=3,
bounds=bounds,
parameterization=parameterization,
)

[docs]class Cylinder(Geometry):
"""
3D Cylinder
Axis parallel to z-axis

Parameters
----------
center : tuple with 3 ints or floats
center of cylinder
radius : int or float
height : int or float
height of cylinder
parameterization : Parameterization
Parameterization of geometry.
"""

def __init__(self, center, radius, height, parameterization=Parameterization()):
# make sympy symbols to use
x, y, z = Symbol("x"), Symbol("y"), Symbol("z")
h, r = Symbol(csg_curve_naming(0)), Symbol(csg_curve_naming(1))
theta = Symbol(csg_curve_naming(2))

# surface of the cylinder
curve_parameterization = Parameterization(
{h: (-1, 1), r: (0, 1), theta: (0, 2 * pi)}
)
curve_parameterization = Parameterization.combine(
curve_parameterization, parameterization
)
curve_1 = SympyCurve(
functions={
"x": center[0] + radius * cos(theta),
"y": center[1] + radius * sin(theta),
"z": center[2] + 0.5 * h * height,
"normal_x": 1 * cos(theta),
"normal_y": 1 * sin(theta),
"normal_z": 0,
},
parameterization=curve_parameterization,
area=height * 2 * pi * radius,
)
curve_2 = SympyCurve(
functions={
"x": center[0] + sqrt(r) * radius * cos(theta),
"y": center[1] + sqrt(r) * radius * sin(theta),
"z": center[2] + 0.5 * height,
"normal_x": 0,
"normal_y": 0,
"normal_z": 1,
},
parameterization=curve_parameterization,
)
curve_3 = SympyCurve(
functions={
"x": center[0] + sqrt(r) * radius * cos(theta),
"y": center[1] + sqrt(r) * radius * sin(theta),
"z": center[2] - 0.5 * height,
"normal_x": 0,
"normal_y": 0,
"normal_z": -1,
},
parameterization=curve_parameterization,
)
curves = [curve_1, curve_2, curve_3]

# calculate SDF
r_dist = sqrt((x - center[0]) ** 2 + (y - center[1]) ** 2)
z_dist = Abs(z - center[2])
outside_distance = sqrt(
Min(0, radius - r_dist) ** 2 + Min(0, 0.5 * height - z_dist) ** 2
)
inside_distance = -1 * Min(
Abs(Min(0, r_dist - radius)), Abs(Min(0, z_dist - 0.5 * height))
)
sdf = -(outside_distance + inside_distance)

# calculate bounds
bounds = Bounds(
{
Parameter("x"): (center[0] - radius, center[0] + radius),
Parameter("y"): (center[1] - radius, center[1] + radius),
Parameter("z"): (center[2] - height / 2, center[2] + height / 2),
},
parameterization=parameterization,
)

# initialize Cylinder
super().__init__(
curves,
_sympy_sdf_to_sdf(sdf),
dims=3,
bounds=bounds,
parameterization=parameterization,
)

[docs]class Torus(Geometry):
"""
3D Torus

Parameters
----------
center : tuple with 3 ints or floats
center of torus
radius : int or float
distance from center to center of tube (major radius)
radius_tube : int or float
parameterization : Parameterization
Parameterization of geometry.
"""

def __init__(
):
# make sympy symbols to use
x, y, z = Symbol("x"), Symbol("y"), Symbol("z")
r_1, r_2, r_3 = (
Symbol(csg_curve_naming(0)),
Symbol(csg_curve_naming(1)),
Symbol(csg_curve_naming(2)),
)

N = CoordSys3D("N")
P = x * N.i + y * N.j + z * N.k
O = center[0] * N.i + center[1] * N.j + center[2] * N.k
OP_xy = (x - center[0]) * N.i + (y - center[1]) * N.j + (0) * N.k
OR = radius * OP_xy / sqrt(OP_xy.dot(OP_xy))
OP = P - O
RP = OP - OR
dist = sqrt(RP.dot(RP))

# surface of the torus
curve_parameterization = Parameterization(
{r_1: (0, 1), r_2: (0, 1), r_3: (0, 1)}
)
curve_parameterization = Parameterization.combine(
curve_parameterization, parameterization
)
theta = 2 * pi * r_1
phi = 2 * pi * r_2
curve_1 = SympyCurve(
functions={
"x": center[0] + (radius + radius_tube * cos(theta)) * cos(phi),
"y": center[1] + (radius + radius_tube * cos(theta)) * sin(phi),
"z": center[2] + radius_tube * sin(theta),
"normal_x": 1 * cos(theta) * cos(phi),
"normal_y": 1 * cos(theta) * sin(phi),
"normal_z": 1 * sin(theta),
},
parameterization=curve_parameterization,
area=4 * pi * pi * radius * radius_tube,
)
curves = [curve_1]

# calculate SDF
sdf = radius_tube - dist

# calculate bounds
bounds = Bounds(
{
Parameter("x"): (
),
Parameter("y"): (
),
Parameter("z"): (center[2] - radius_tube, center[2] + radius_tube),
},
parameterization=parameterization,
)

# initialize Torus
super().__init__(
curves,
_sympy_sdf_to_sdf(sdf),
dims=3,
bounds=bounds,
parameterization=parameterization,
)

[docs]class Cone(Geometry):
"""
3D Cone
Axis parallel to z-axis

Parameters
----------
center : tuple with 3 ints or floats
base center of cone
radius : int or float
base radius of cone
height : int or float
height of cone
parameterization : Parameterization
Parameterization of geometry.
"""

def __init__(self, center, radius, height, parameterization=Parameterization()):
# make sympy symbols to use
x, y, z = Symbol("x"), Symbol("y"), Symbol("z")
r, t = Symbol(csg_curve_naming(0)), Symbol(csg_curve_naming(1))
theta = Symbol(csg_curve_naming(2))

N = CoordSys3D("N")
P = x * N.i + y * N.j + z * N.k
O = center[0] * N.i + center[1] * N.j + center[2] * N.k
H = center[0] * N.i + center[1] * N.j + (center[2] + height) * N.k
R = (
(center[0] + radius * cos(atan2(y, x))) * N.i
+ (center[1] + radius * sin(atan2(y, x))) * N.j
+ (center[2]) * N.k
)
OP_xy = (x - center[0]) * N.i + (y - center[1]) * N.j + (0) * N.k
OR = radius * OP_xy / sqrt(OP_xy.dot(OP_xy))
OP = P - O
OH = H - O
RP = OP - OR
RH = OH - OR
PH = OH - OP
cone_angle = atan2(radius, height)
angle = acos(PH.dot(OH) / sqrt(PH.dot(PH)) / sqrt(OH.dot(OH)))
dist = sqrt(PH.dot(PH)) * sin(angle - cone_angle)

# surface of the cone
curve_parameterization = Parameterization(
{r: (0, 1), t: (0, 1), theta: (0, 2 * pi)}
)
curve_parameterization = Parameterization.combine(
curve_parameterization, parameterization
)
curve_1 = SympyCurve(
functions={
"x": center[0] + (sqrt(t)) * radius * cos(theta),
"y": center[1] + (sqrt(t)) * radius * sin(theta),
"z": center[2] + (1 - sqrt(t)) * height,
"normal_x": 1 * cos(cone_angle) * cos(theta),
"normal_y": 1 * cos(cone_angle) * sin(theta),
"normal_z": 1 * sin(cone_angle),
},
parameterization=curve_parameterization,
area=pi * radius * (sqrt(height**2 + radius**2)),
)
curve_2 = SympyCurve(
functions={
"x": center[0] + sqrt(r) * radius * cos(theta),
"y": center[1] + sqrt(r) * radius * sin(theta),
"z": center[2],
"normal_x": 0,
"normal_y": 0,
"normal_z": -1,
},
parameterization=curve_parameterization,
)
curves = [curve_1, curve_2]

# calculate SDF
outside_distance = 1 * sqrt(Max(0, dist) ** 2 + Max(0, center[2] - z) ** 2)
inside_distance = -1 * Min(Abs(Min(0, dist)), Abs(Min(0, center[2] - z)))
sdf = -(outside_distance + inside_distance)

# calculate bounds
bounds = Bounds(
{
Parameter("x"): (center[0] - radius, center[0] + radius),
Parameter("y"): (center[1] - radius, center[1] + radius),
Parameter("z"): (center[2], center[2] + height),
},
parameterization=parameterization,
)

# initialize Cone
super().__init__(
curves,
_sympy_sdf_to_sdf(sdf),
dims=3,
bounds=bounds,
parameterization=parameterization,
)

[docs]class TriangularPrism(Geometry):
"""
3D Uniform Triangular Prism
Axis parallel to z-axis

Parameters
----------
center : tuple with 3 ints or floats
center of prism
side : int or float
side of equilateral base
height : int or float
height of prism
parameterization : Parameterization
Parameterization of geometry.
"""

def __init__(self, center, side, height, parameterization=Parameterization()):
# make sympy symbols to use
x, y, z = Symbol("x"), Symbol("y"), Symbol("z")
s_1, s_2, s_3 = (
Symbol(csg_curve_naming(0)),
Symbol(csg_curve_naming(1)),
Symbol(csg_curve_naming(2)),
)

N = CoordSys3D("N")
P = x * N.i + y * N.j + z * N.k
O = center[0] * N.i + center[1] * N.j + center[2] * N.k
OP = P - O
OP_xy = OP - OP.dot(1 * N.k)
normal_1 = -1 * N.j
normal_2 = -sqrt(3) / 2 * N.i + 1 / 2 * N.j
normal_3 = sqrt(3) / 2 * N.i + 1 / 2 * N.j
r_ins = side / 2 / sqrt(3)
distance_side = Min(
Abs(r_ins - OP_xy.dot(normal_1)),
Abs(r_ins - OP_xy.dot(normal_2)),
Abs(r_ins - OP_xy.dot(normal_3)),
)
distance_top = Abs(z - center[2]) - 0.5 * height

v1 = O + (
-0.5 * side * N.i - 0.5 * sqrt(1 / 3) * side * N.j - height / 2 * side * N.k
)
v2 = O + (
0.5 * side * N.i - 0.5 * sqrt(1 / 3) * side * N.j - height / 2 * side * N.k
)
v3 = O + (1 * sqrt(1 / 3) * side * N.j - height / 2 * side * N.k)

# surface of the prism
curve_parameterization = Parameterization({s_1: (0, 1), s_2: (0, 1)})
curve_parameterization = Parameterization.combine(
curve_parameterization, parameterization
)
curve_1 = SympyCurve(
functions={
"x": v1.dot(1 * N.i) + (v2 - v1).dot(1 * N.i) * s_1,
"y": v1.dot(1 * N.j) + (v2 - v1).dot(1 * N.j) * s_1,
"z": v1.dot(1 * N.k) + height * s_2,
"normal_x": 0,
"normal_y": -1,
"normal_z": 0,
},
parameterization=curve_parameterization,
area=side * height,
)
curve_2 = SympyCurve(
functions={
"x": v1.dot(1 * N.i) + (v3 - v1).dot(1 * N.i) * s_1,
"y": v1.dot(1 * N.j) + (v3 - v1).dot(1 * N.j) * s_1,
"z": v1.dot(1 * N.k) + height * s_2,
"normal_x": -sqrt(3) / 2,
"normal_y": 1 / 2,
"normal_z": 0,
},
parameterization=curve_parameterization,
area=side * height,
)
curve_3 = SympyCurve(
functions={
"x": v2.dot(1 * N.i) + (v3 - v2).dot(1 * N.i) * s_1,
"y": v2.dot(1 * N.j) + (v3 - v2).dot(1 * N.j) * s_1,
"z": v2.dot(1 * N.k) + height * s_2,
"normal_x": sqrt(3) / 2,
"normal_y": 1 / 2,
"normal_z": 0,
},
parameterization=curve_parameterization,
area=side * height,
)
curve_4 = SympyCurve(
functions={
"x": (
(
(1 - sqrt(s_1)) * v1
+ (sqrt(s_1) * (1 - s_2)) * v2
+ s_2 * sqrt(s_1) * v3
).dot(1 * N.i)
),
"y": (
(
(1 - sqrt(s_1)) * v1
+ (sqrt(s_1) * (1 - s_2)) * v2
+ s_2 * sqrt(s_1) * v3
).dot(1 * N.j)
),
"z": (
(
(1 - sqrt(s_1)) * v1
+ (sqrt(s_1) * (1 - s_2)) * v2
+ s_2 * sqrt(s_1) * v3
).dot(1 * N.k)
),
"normal_x": 0,
"normal_y": 0,
"normal_z": -1,
},
parameterization=curve_parameterization,
area=sqrt(3) * side * side / 4,
)
curve_5 = SympyCurve(
functions={
"x": (
(
(1 - sqrt(s_1)) * v1
+ (sqrt(s_1) * (1 - s_2)) * v2
+ s_2 * sqrt(s_1) * v3
).dot(1 * N.i)
),
"y": (
(
(1 - sqrt(s_1)) * v1
+ (sqrt(s_1) * (1 - s_2)) * v2
+ s_2 * sqrt(s_1) * v3
).dot(1 * N.j)
),
"z": (
(
(1 - sqrt(s_1)) * v1
+ (sqrt(s_1) * (1 - s_2)) * v2
+ s_2 * sqrt(s_1) * v3
).dot(1 * N.k)
+ height
),
"normal_x": 0,
"normal_y": 0,
"normal_z": 1,
},
parameterization=curve_parameterization,
area=sqrt(3) * side * side / 4,
)
curves = [curve_1, curve_2, curve_3, curve_4, curve_5]

# calculate SDF
inside_distance = Max(
Min(
Max(OP_xy.dot(normal_1), OP_xy.dot(normal_2), OP_xy.dot(normal_3))
- r_ins,
0,
),
Min(Abs(z - center[2]) - 0.5 * height, 0),
)
outside_distance = sqrt(
Min(
r_ins
- Max(OP_xy.dot(normal_1), OP_xy.dot(normal_2), OP_xy.dot(normal_3)),
0,
)
** 2
+ Min(0.5 * height - Abs(z - center[2]), 0) ** 2
)
sdf = -(outside_distance + inside_distance)

# calculate bounds
bounds = Bounds(
{
Parameter("x"): (center[0] - side / 2, center[0] + side / 2),
Parameter("y"): (center[1] - side / 2, center[1] + side / 2),
Parameter("z"): (center[2], center[2] + height),
},
parameterization=parameterization,
)

# initialize TriangularPrism
super().__init__(
curves,
_sympy_sdf_to_sdf(sdf),
dims=3,
bounds=bounds,
parameterization=parameterization,
)

[docs]class Tetrahedron(Geometry):
"""
3D Tetrahedron
The 4 symmetrically placed points are on a unit sphere.
Centroid of the tetrahedron is at origin and lower face is parallel to
x-y plane
Reference: https://en.wikipedia.org/wiki/Tetrahedron

Parameters
----------
center : tuple with 3 ints or floats
centroid of tetrahedron
radius : int or float
radius of circumscribed sphere
parameterization : Parameterization
Parameterization of geometry.
"""

def __init__(self, center, radius, parameterization=Parameterization()):
# make sympy symbols to use
x, y, z = Symbol("x"), Symbol("y"), Symbol("z")
r_1, r_2 = Symbol(csg_curve_naming(0)), Symbol(csg_curve_naming(1))

N = CoordSys3D("N")
P = x * N.i + y * N.j + z * N.k
O = center[0] * N.i + center[1] * N.j + center[2] * N.k

side = sqrt(8 / 3) * radius

# vertices of the tetrahedron
v1 = (
center[0] + radius * sqrt(8 / 9),
center[1] + radius * 0,
center[2] + radius * (-1 / 3),
)
v2 = (
center[0] - radius * sqrt(2 / 9),
center[1] + radius * sqrt(2 / 3),
center[2] + radius * (-1 / 3),
)
v3 = (
center[0] - radius * sqrt(2 / 9),
center[1] - radius * sqrt(2 / 3),
center[2] + radius * (-1 / 3),
)
v4 = (
center[0] + radius * 0,
center[1] + radius * 0,
center[2] + radius * 1,
)  # apex vector

vv1 = v1[0] * N.i + v1[1] * N.j + v1[2] * N.k
vv2 = v2[0] * N.i + v2[1] * N.j + v2[2] * N.k
vv3 = v3[0] * N.i + v3[1] * N.j + v2[2] * N.k
vv4 = v4[0] * N.i + v4[1] * N.j + v4[2] * N.k

v4P = P - vv4

# surface of the tetrahedron
curve_parameterization = Parameterization({r_1: (-1, 1), r_2: (-1, 1)})
curve_parameterization = Parameterization.combine(
curve_parameterization, parameterization
)

# face between v1, v2, v3
normal_1 = ((vv3 - vv1).cross(vv2 - vv1)).normalize()
curve_1 = SympyCurve(
functions={
"x": (
center[0]
+ (1 - sqrt(r_1)) * v1[0]
+ (sqrt(r_1) * (1 - r_2)) * v2[0]
+ r_2 * sqrt(r_1) * v3[0]
),
"y": (
center[1]
+ (1 - sqrt(r_1)) * v1[1]
+ (sqrt(r_1) * (1 - r_2)) * v2[1]
+ r_2 * sqrt(r_1) * v3[1]
),
"z": (
center[2]
+ (1 - sqrt(r_1)) * v1[2]
+ (sqrt(r_1) * (1 - r_2)) * v2[2]
+ r_2 * sqrt(r_1) * v3[2]
),
"normal_x": normal_1.to_matrix(N)[0],
"normal_y": normal_1.to_matrix(N)[1],
"normal_z": normal_1.to_matrix(N)[2],
},
parameterization=curve_parameterization,
area=sqrt(3) * side * side / 4,
)
# face between v1, v2, v4
normal_2 = ((vv2 - vv1).cross(vv4 - vv1)).normalize()
curve_2 = SympyCurve(
functions={
"x": (
center[0]
+ (1 - sqrt(r_1)) * v1[0]
+ (sqrt(r_1) * (1 - r_2)) * v2[0]
+ r_2 * sqrt(r_1) * v4[0]
),
"y": (
center[1]
+ (1 - sqrt(r_1)) * v1[1]
+ (sqrt(r_1) * (1 - r_2)) * v2[1]
+ r_2 * sqrt(r_1) * v4[1]
),
"z": (
center[2]
+ (1 - sqrt(r_1)) * v1[2]
+ (sqrt(r_1) * (1 - r_2)) * v2[2]
+ r_2 * sqrt(r_1) * v4[2]
),
"normal_x": normal_2.to_matrix(N)[0],
"normal_y": normal_2.to_matrix(N)[1],
"normal_z": normal_2.to_matrix(N)[2],
},
parameterization=curve_parameterization,
area=sqrt(3) * side * side / 4,
)
# face between v1, v4, v3
normal_3 = ((vv4 - vv1).cross(vv3 - vv1)).normalize()
curve_3 = SympyCurve(
functions={
"x": (
center[0]
+ (1 - sqrt(r_1)) * v1[0]
+ (sqrt(r_1) * (1 - r_2)) * v4[0]
+ r_2 * sqrt(r_1) * v3[0]
),
"y": (
center[1]
+ (1 - sqrt(r_1)) * v1[1]
+ (sqrt(r_1) * (1 - r_2)) * v4[1]
+ r_2 * sqrt(r_1) * v3[1]
),
"z": (
center[2]
+ (1 - sqrt(r_1)) * v1[2]
+ (sqrt(r_1) * (1 - r_2)) * v4[2]
+ r_2 * sqrt(r_1) * v3[2]
),
"normal_x": normal_3.to_matrix(N)[0],
"normal_y": normal_3.to_matrix(N)[1],
"normal_z": normal_3.to_matrix(N)[2],
},
parameterization=curve_parameterization,
area=sqrt(3) * side * side / 4,
)
# face between v4, v2, v3
normal_4 = ((vv2 - vv4).cross(vv3 - vv4)).normalize()
curve_4 = SympyCurve(
functions={
"x": (
center[0]
+ (1 - sqrt(r_1)) * v4[0]
+ (sqrt(r_1) * (1 - r_2)) * v2[0]
+ r_2 * sqrt(r_1) * v3[0]
),
"y": (
center[1]
+ (1 - sqrt(r_1)) * v4[1]
+ (sqrt(r_1) * (1 - r_2)) * v2[1]
+ r_2 * sqrt(r_1) * v3[1]
),
"z": (
center[2]
+ (1 - sqrt(r_1)) * v4[2]
+ (sqrt(r_1) * (1 - r_2)) * v2[2]
+ r_2 * sqrt(r_1) * v3[2]
),
"normal_x": normal_4.to_matrix(N)[0],
"normal_y": normal_4.to_matrix(N)[1],
"normal_z": normal_4.to_matrix(N)[2],
},
parameterization=curve_parameterization,
area=sqrt(3) * side * side / 4,
)
curves = [curve_1, curve_2, curve_3, curve_4]

dist = Max(
v4P.dot(normal_2) / normal_2.magnitude(),
v4P.dot(normal_3) / normal_3.magnitude(),
v4P.dot(normal_4) / normal_4.magnitude(),
)

# calculate SDF
outside_distance = -1 * sqrt(Max(0, dist) ** 2 + Max(0, v1[2] - z) ** 2)
inside_distance = Min(Abs(Min(0, dist)), Abs(Min(0, v1[2] - z)))
sdf = outside_distance + inside_distance

# calculate bounds
bounds = Bounds(
{
Parameter("x"): (center[0] - radius, center[0] + radius),
Parameter("y"): (center[1] - radius, center[1] + radius),
Parameter("z"): (center[2] - radius, center[2] + radius),
},
parameterization=parameterization,
)

# initialize Tetrahedron
super().__init__(
curves,
_sympy_sdf_to_sdf(sdf),
dims=3,
bounds=bounds,
parameterization=parameterization,
)

[docs]class IsoTriangularPrism(Geometry):
"""
2D Isosceles Triangular Prism
Symmetrical axis parallel to y-axis

Parameters
----------
center : tuple with 3 ints or floats
center of base of triangle
base : int or float
base of triangle
height : int or float
height of triangle
height_prism : int or float
height of triangular prism
parameterization : Parameterization
Parameterization of geometry.
"""

def __init__(
self, center, base, height, height_prism, parameterization=Parameterization()
):
# make sympy symbols to use
x, y, z = Symbol("x"), Symbol("y"), Symbol("z")
t, h, hz = (
Symbol(csg_curve_naming(0)),
Symbol(csg_curve_naming(1)),
Symbol(csg_curve_naming(2)),
)

N = CoordSys3D("N")
P = (x) * N.i + y * N.j + center[2] * N.k
Q = x * N.i + y * N.j + center[2] * N.k
O = center[0] * N.i + center[1] * N.j + center[2] * N.k
H = center[0] * N.i + (center[1] + height) * N.j + center[2] * N.k
B = (center[0] + base / 2) * N.i + center[1] * N.j + center[2] * N.k
B_p = (center[0] - base / 2) * N.i + center[1] * N.j + center[2] * N.k

OP = P - O
OH = H - O
PH = OH - OP
OQ = Q - O
QH = OH - OQ
HP = OP - OH
HB = B - H
HB_p = B_p - H

norm = ((HB_p).cross(HB)).normalize()
norm_HB = (norm.cross(HB)).normalize()
hypo = sqrt(height**2 + (base / 2) ** 2)
angle = acos(PH.dot(OH) / sqrt(PH.dot(PH)) / sqrt(OH.dot(OH)))
apex_angle = asin(base / 2 / hypo)
hypo_sin = sqrt(height**2 + (base / 2) ** 2) * sin(apex_angle)
hypo_cos = sqrt(height**2 + (base / 2) ** 2) * cos(apex_angle)
dist = sqrt(PH.dot(PH)) * sin(Min(angle - apex_angle, pi / 2))

a = (center[0] - base / 2) * N.i + center[1] * N.j + center[2] * N.k
b = (center[0] + base / 2) * N.i + center[1] * N.j + center[2] * N.k
c = center[0] * N.i + (center[1] + height) * N.j + center[2] * N.k
s_1, s_2 = Symbol(csg_curve_naming(3)), Symbol(csg_curve_naming(4))

# curve for each side
ranges = {t: (-1, 1), h: (0, 1), hz: (-1, 1), s_1: (0, 1), s_2: (0, 1)}
curve_parameterization = Parameterization(ranges)
curve_parameterization = Parameterization.combine(
curve_parameterization, parameterization
)
curve_1 = SympyCurve(
functions={
"x": center[0] + t * base / 2,
"y": center[1] + t * 0,
"z": center[2] + 0.5 * hz * height_prism,
"normal_x": 0,
"normal_y": -1,
"normal_z": 0,
},
parameterization=curve_parameterization,
area=base * height_prism,
)
curve_2 = SympyCurve(
functions={
"x": center[0] + h * hypo_sin,
"y": center[1] + height - h * hypo_cos,
"z": center[2] + 0.5 * hz * height_prism,
"normal_x": 1 * cos(apex_angle),
"normal_y": 1 * sin(apex_angle),
"normal_z": 0,
},
parameterization=curve_parameterization,
area=sqrt(height**2 + (base / 2) ** 2) * height_prism,
)
curve_3 = SympyCurve(
functions={
"x": center[0] - h * hypo_sin,
"y": center[1] + height - h * hypo_cos,
"z": center[2] + 0.5 * hz * height_prism,
"normal_x": -1 * cos(apex_angle),
"normal_y": 1 * sin(apex_angle),
"normal_z": 0,
},
parameterization=curve_parameterization,
area=sqrt(height**2 + (base / 2) ** 2) * height_prism,
)
curve_4 = SympyCurve(
functions={
"x": (
(
(1 - sqrt(s_1)) * a
+ (sqrt(s_1) * (1 - s_2)) * b
+ s_2 * sqrt(s_1) * c
).dot(1 * N.i)
),
"y": (
(
(1 - sqrt(s_1)) * a
+ (sqrt(s_1) * (1 - s_2)) * b
+ s_2 * sqrt(s_1) * c
).dot(1 * N.j)
),
"z": (
(
(1 - sqrt(s_1)) * a
+ (sqrt(s_1) * (1 - s_2)) * b
+ s_2 * sqrt(s_1) * c
).dot(1 * N.k)
)
- height_prism / 2,
"normal_x": 0,
"normal_y": 0,
"normal_z": -1,
},
parameterization=curve_parameterization,
area=0.5 * base * height,
)
curve_5 = SympyCurve(
functions={
"x": (
(
(1 - sqrt(s_1)) * a
+ (sqrt(s_1) * (1 - s_2)) * b
+ s_2 * sqrt(s_1) * c
).dot(1 * N.i)
),
"y": (
(
(1 - sqrt(s_1)) * a
+ (sqrt(s_1) * (1 - s_2)) * b
+ s_2 * sqrt(s_1) * c
).dot(1 * N.j)
),
"z": (
(
(1 - sqrt(s_1)) * a
+ (sqrt(s_1) * (1 - s_2)) * b
+ s_2 * sqrt(s_1) * c
).dot(1 * N.k)
+ height_prism / 2
),
"normal_x": 0,
"normal_y": 0,
"normal_z": 1,
},
parameterization=curve_parameterization,
area=0.5 * base * height,
)

curves = [curve_1, curve_2, curve_3, curve_4, curve_5]

# calculate SDF
z_dist = Abs(z - center[2])
outside_distance = 1 * sqrt(
sqrt(Max(0, dist) ** 2 + Max(0, center[1] - y) ** 2) ** 2
+ Min(0.5 * height_prism - Abs(z - center[2]), 0) ** 2
)
inside_distance = -1 * Min(
Abs(Min(0, dist)),
Abs(Min(0, center[1] - y)),
Abs(Min(Abs(z - center[2]) - 0.5 * height_prism, 0)),
)
sdf = -(outside_distance + inside_distance)

# calculate bounds
bounds = Bounds(
{
Parameter("x"): (center[0] - base / 2, center[0] + base / 2),
Parameter("y"): (center[1], center[1] + height),
Parameter("z"): (center[2], center[2] + height_prism),
},
parameterization=parameterization,
)

# initialize IsoTriangularPrism
super().__init__(
curves,
_sympy_sdf_to_sdf(sdf),
dims=3,
bounds=bounds,
parameterization=parameterization,
)

[docs]class ElliCylinder(Geometry):
"""
3D Elliptical Cylinder
Axis parallel to z-axis

Approximation based on 4-arc ellipse construction
https://www.researchgate.net/publication/241719740_Approximating_an_ellipse_with_four_circular_arcs

Please manually ensure a>b

Parameters
----------
center : tuple with 3 ints or floats
center of base of ellipse
a : int or float
semi-major axis of ellipse
b : int or float
semi-minor axis of ellipse
height : int or float
height of elliptical cylinder
parameterization : Parameterization
Parameterization of geometry.
"""

def __init__(self, center, a, b, height, parameterization=Parameterization()):
# TODO Assertion creates issues while parameterization
# assert a > b, "a must be greater than b. To have a ellipse with larger b create a ellipse with flipped a and b and then rotate by pi/2"

# make sympy symbols to use
x, y, z = Symbol("x"), Symbol("y"), Symbol("z")
h = Symbol(csg_curve_naming(0))
r_1, r_2 = Symbol(csg_curve_naming(1)), Symbol(csg_curve_naming(2))
angle = Symbol(csg_curve_naming(3))

phi = asin(b / sqrt(a**2 + b**2))
# phi = atan2(b, a)
theta = pi / 2 - phi

r1 = (a * sin(theta) + b * cos(theta) - a) / (sin(theta) + cos(theta) - 1)
r2 = (a * sin(theta) + b * cos(theta) - b) / (sin(theta) + cos(theta) - 1)

# surface of the cylinder
ranges = {h: (-1, 1), r_1: (0, 1), r_2: (0, 1), angle: (-1, 1)}
curve_parameterization = Parameterization(ranges)
curve_parameterization = Parameterization.combine(
curve_parameterization, parameterization
)
curve_1 = SympyCurve(
functions={
"x": center[0] + a - r1 + r1 * cos(angle * theta),
"y": center[1] + r1 * sin(angle * theta),
"z": center[2] + 0.5 * h * height,
"normal_x": 1 * cos(angle * theta),
"normal_y": 1 * sin(angle * theta),
"normal_z": 0,
},
parameterization=curve_parameterization,
area=height * 2 * theta * r1,
)
curve_2 = SympyCurve(
functions={
"x": center[0] + r2 * cos(pi / 2 + angle * phi),
"y": center[1] - r2 + b + r2 * sin(pi / 2 + angle * phi),
"z": center[2] + 0.5 * h * height,
"normal_x": 1 * cos(pi / 2 + angle * phi),
"normal_y": 1 * sin(pi / 2 + angle * phi),
"normal_z": 0,
},
parameterization=curve_parameterization,
area=height * 2 * phi * r2,
)
curve_3 = SympyCurve(
functions={
"x": center[0] - a + r1 + r1 * cos(pi + angle * theta),
"y": center[1] + r1 * sin(pi + angle * theta),
"z": center[2] + 0.5 * h * height,
"normal_x": 1 * cos(pi + angle * theta),
"normal_y": 1 * sin(pi + angle * theta),
"normal_z": 0,
},
parameterization=curve_parameterization,
area=height * 2 * theta * r1,
)
curve_4 = SympyCurve(
functions={
"x": center[0] + r2 * cos(3 * pi / 2 + angle * phi),
"y": center[1] + r2 - b + r2 * sin(3 * pi / 2 + angle * phi),
"z": center[2] + 0.5 * h * height,
"normal_x": 1 * cos(3 * pi / 2 + angle * phi),
"normal_y": 1 * sin(3 * pi / 2 + angle * phi),
"normal_z": 0,
},
parameterization=curve_parameterization,
area=height * 2 * phi * r2,
)
# Flat surfaces top
curve_5 = SympyCurve(
functions={
"x": center[0] + a - r1 + sqrt(r_1) * r1 * cos(angle * theta),
"y": center[1] + sqrt(r_1) * r1 * sin(angle * theta),
"z": center[2] + 0.5 * height,
"normal_x": 0,
"normal_y": 0,
"normal_z": 1,
},
parameterization=curve_parameterization,
area=theta * r1**2,
)
curve_6 = SympyCurve(
functions={
"x": center[0] + sqrt(r_2) * r2 * cos(pi / 2 + angle * phi),
"y": center[1] - r2 + b + sqrt(r_2) * r2 * sin(pi / 2 + angle * phi),
"z": center[2] + 0.5 * height,
"normal_x": 0,
"normal_y": 0,
"normal_z": 1,
},
parameterization=curve_parameterization,
area=phi * r2**2 - 0.5 * (r2 - b) * (a - r1) * 2,
criteria=center[1] - r2 + b + sqrt(r_2) * r2 * sin(pi / 2 + angle * phi)
> center[1],
)
# criteria=(((x-(center[0]+r2-b))**2+y**2)<r2**2))
curve_7 = SympyCurve(
functions={
"x": center[0] - a + r1 + sqrt(r_1) * r1 * cos(pi + angle * theta),
"y": center[1] + sqrt(r_1) * r1 * sin(pi + angle * theta),
"z": center[2] + 0.5 * height,
"normal_x": 0,
"normal_y": 0,
"normal_z": 1,
},
parameterization=curve_parameterization,
area=theta * r1**2,
)
curve_8 = SympyCurve(
functions={
"x": center[0] + sqrt(r_2) * r2 * cos(3 * pi / 2 + angle * phi),
"y": center[1]
+ r2
- b
+ sqrt(r_2) * r2 * sin(3 * pi / 2 + angle * phi),
"z": center[2] + 0.5 * height,
"normal_x": 0,
"normal_y": 0,
"normal_z": 1,
},
parameterization=curve_parameterization,
area=phi * r2**2 - 0.5 * (r2 - b) * (a - r1) * 2,
criteria=center[1] + r2 - b + sqrt(r_2) * r2 * sin(3 * pi / 2 + angle * phi)
< center[1],
)
# criteria=(((x-(center[0]-r2+b))**2+y**2)<r2**2))
# Flat surfaces bottom
curve_9 = SympyCurve(
functions={
"x": center[0] + a - r1 + sqrt(r_1) * r1 * cos(angle * theta),
"y": center[1] + sqrt(r_1) * r1 * sin(angle * theta),
"z": center[2] - 0.5 * height,
"normal_x": 0,
"normal_y": 0,
"normal_z": -1,
},
parameterization=curve_parameterization,
area=theta * r1**2,
)
curve_10 = SympyCurve(
functions={
"x": center[0] + sqrt(r_2) * r2 * cos(pi / 2 + angle * phi),
"y": center[1] - r2 + b + sqrt(r_2) * r2 * sin(pi / 2 + angle * phi),
"z": center[2] - 0.5 * height,
"normal_x": 0,
"normal_y": 0,
"normal_z": -1,
},
parameterization=curve_parameterization,
area=phi * r2**2 - 0.5 * (r2 - b) * (a - r1) * 2,
criteria=center[1] - r2 + b + sqrt(r_2) * r2 * sin(pi / 2 + angle * phi)
> center[1],
)
# criteria=(((x-(center[0]+r2-b))**2+y**2)<r2**2))
curve_11 = SympyCurve(
functions={
"x": center[0] - a + r1 + sqrt(r_1) * r1 * cos(pi + angle * theta),
"y": center[1] + sqrt(r_1) * r1 * sin(pi + angle * theta),
"z": center[2] - 0.5 * height,
"normal_x": 0,
"normal_y": 0,
"normal_z": -1,
},
parameterization=curve_parameterization,
area=theta * r1**2,
)
curve_12 = SympyCurve(
functions={
"x": center[0] + sqrt(r_2) * r2 * cos(3 * pi / 2 + angle * phi),
"y": center[1]
+ r2
- b
+ sqrt(r_2) * r2 * sin(3 * pi / 2 + angle * phi),
"z": center[2] - 0.5 * height,
"normal_x": 0,
"normal_y": 0,
"normal_z": -1,
},
parameterization=curve_parameterization,
area=phi * r2**2 - 0.5 * (r2 - b) * (a - r1) * 2,
criteria=center[1] + r2 - b + sqrt(r_2) * r2 * sin(3 * pi / 2 + angle * phi)
< center[1],
)
# criteria=(((x-(center[0]-r2+b))**2+y**2)<r2**2))

curves = [
curve_1,
curve_2,
curve_3,
curve_4,
curve_5,
curve_6,
curve_7,
curve_8,
curve_9,
curve_10,
curve_11,
curve_12,
]

# calculate SDF
c1 = (center[0] + (a - r1), center[1], center[2])
c2 = (center[0], center[1] - (r2 - b), center[2])
c3 = (center[0] - (a - r1), center[1], center[2])
c4 = (center[0], center[1] + (r2 - b), center[2])

l1_m = (c1[1] - c2[1]) / (c1[0] - c2[0])
l1_c = c1[1] - l1_m * c1[0]

l2_m = (c1[1] - c4[1]) / (c1[0] - c4[0])
l2_c = c1[1] - l2_m * c1[0]

l3_m = (c3[1] - c4[1]) / (c3[0] - c4[0])
l3_c = c3[1] - l3_m * c3[0]

l4_m = (c3[1] - c2[1]) / (c3[0] - c2[0])
l4_c = c3[1] - l4_m * c3[0]

# (sign((x-min)*(max-x))+1)/2       # gives 0 if outside range, 0.5 if on min/max, 1 if inside range
# if negative is desired (1-sign(x))/2
# if positive is desired (sign(x)+1)/2

outside_distance_1 = (
Max((sqrt(((x) - c1[0]) ** 2 + ((y) - c1[1]) ** 2) - r1), 0)
* ((1 - sign((y) - l1_m * (x) - l1_c)) / 2)
* ((sign((y) - l2_m * (x) - l2_c) + 1) / 2)
)
outside_distance_2 = (
Max((sqrt(((x) - c2[0]) ** 2 + ((y) - c2[1]) ** 2) - r2), 0)
* ((sign((y) - l1_m * (x) - l1_c) + 1) / 2)
* ((sign((y) - l4_m * (x) - l4_c) + 1) / 2)
)
outside_distance_3 = (
Max((sqrt(((x) - c3[0]) ** 2 + ((y) - c3[1]) ** 2) - r1), 0)
* ((sign((y) - l3_m * (x) - l3_c) + 1) / 2)
* ((1 - sign((y) - l4_m * (x) - l4_c)) / 2)
)
outside_distance_4 = (
Max((sqrt(((x) - c4[0]) ** 2 + ((y) - c4[1]) ** 2) - r2), 0)
* ((1 - sign((y) - l2_m * (x) - l2_c)) / 2)
* ((1 - sign((y) - l3_m * (x) - l3_c)) / 2)
)

curved_outside_distance = (
outside_distance_1
+ outside_distance_2
+ outside_distance_3
+ outside_distance_4
)
flat_outside_distance = Max(Abs(z - center[2]) - 0.5 * height, 0)

outside_distance = sqrt(
curved_outside_distance**2 + flat_outside_distance**2
)

# (sign((x-min)*(max-x))+1)/2       # gives 0 if outside range, 0.5 if on min/max, 1 if inside range
inside_distance_1 = (
Max((r1 - sqrt(((x) - c1[0]) ** 2 + ((y) - c1[1]) ** 2)), 0)
* ((1 - sign((y) - l1_m * (x) - l1_c)) / 2)
* ((sign((y) - l2_m * (x) - l2_c) + 1) / 2)
)
inside_distance_2 = (
Max((r2 - sqrt(((x) - c2[0]) ** 2 + ((y) - c2[1]) ** 2)), 0)
* ((sign((y) - l1_m * (x) - l1_c) + 1) / 2)
* ((sign((y) - l4_m * (x) - l4_c) + 1) / 2)
* ((sign(y - center[1]) + 1) / 2)
)
inside_distance_3 = (
Max((r1 - sqrt(((x) - c3[0]) ** 2 + ((y) - c3[1]) ** 2)), 0)
* ((sign((y) - l3_m * (x) - l3_c) + 1) / 2)
* ((1 - sign((y) - l4_m * (x) - l4_c)) / 2)
)
inside_distance_4 = (
Max((r2 - sqrt(((x) - c4[0]) ** 2 + ((y) - c4[1]) ** 2)), 0)
* ((1 - sign((y) - l2_m * (x) - l2_c)) / 2)
* ((1 - sign((y) - l3_m * (x) - l3_c)) / 2)
* ((sign(center[1] - y) + 1) / 2)
)

curved_inside_distance = (
inside_distance_1
+ inside_distance_2
+ inside_distance_3
+ inside_distance_4
)
flat_inside_distance = Max(0.5 * height - Abs(z - center[2]), 0)

inside_distance = Min(curved_inside_distance, flat_inside_distance)

sdf = -outside_distance + inside_distance

# calculate bounds
bounds = Bounds(
{
Parameter("x"): (center[0] - a, center[0] + a),
Parameter("y"): (center[0] - b, center[0] + b),
Parameter("y"): (center[0] - height / 2, center[0] + height / 2),
},
parameterization=parameterization,
)

# initialize Cylinder
super().__init__(
curves,
_sympy_sdf_to_sdf(sdf),
dims=3,
bounds=bounds,
parameterization=parameterization,
)

© Copyright 2023, NVIDIA Modulus Team. Last updated on Aug 8, 2023.