Source code for idrlnet.geo_utils.geo_obj

"""Concrete shape."""

import math
from math import pi
from typing import Union, List, Tuple
import numpy as np
from sympy import symbols, Abs, sqrt, Max, Min, cos, sin, log, sign, Heaviside, asin
from sympy.vector import CoordSys3D
from .geo import Edge, Geometry1D, Geometry2D, Geometry3D

__all__ = [
    "Line1D",
    "Line",
    "Tube2D",
    "Rectangle",
    "Circle",
    "Heart",
    "Triangle",
    "Polygon",
    "Plane",
    "Tube3D",
    "Tube",
    "CircularTube",
    "Box",
    "Sphere",
    "Cylinder",
]


[docs]class Line1D(Geometry1D): def __init__(self, point_1, point_2): x, none = symbols("x none") ranges = {none: (0, 1)} edge_1 = Edge(functions={"x": point_1, "normal_x": -1}, area=1.0, ranges=ranges) edge_2 = Edge(functions={"x": point_2, "normal_x": 1}, area=1.0, ranges=ranges) self.edges = [edge_1, edge_2] dist = point_2 - point_1 center_x = point_1 + dist / 2 self.sdf = dist / 2 - Abs(x - center_x) self.bounds = {"x": (point_1, point_2)}
[docs]class Line(Geometry2D): def __init__(self, point_1, point_2, normal=1): x, y, l = symbols("x y l") # noqa ranges = {l: (0, 1)} dist_x = point_2[0] - point_1[0] dist_y = point_2[1] - point_1[1] normal_vector = (-dist_y * normal, dist_x * normal) normal_norm = math.sqrt(normal_vector[0] ** 2 + normal_vector[1] ** 2) normal_vector = (normal_vector[0] / normal_norm, normal_vector[1] / normal_norm) line_1 = Edge( functions={ "x": point_1[0] + l * dist_x, "y": point_1[1] + l * dist_y, "normal_x": normal_vector[0], "normal_y": normal_vector[1], }, ranges=ranges, area=normal_norm, ) self.edges = [line_1] self.sdf = ((x - point_1[0]) * dist_y - (y - point_1[1]) * dist_x) / normal_norm self.bounds = { "x": (min(point_1[0], point_2[0]), max(point_1[0], point_2[0])), "y": (min(point_1[1], point_2[1]), max(point_1[1], point_2[1])), }
[docs]class Tube2D(Geometry2D): def __init__(self, point_1, point_2): l, y = symbols("l y") ranges = {l: (0, 1)} dist_x = point_2[0] - point_1[0] dist_y = point_2[1] - point_1[1] line_1 = Edge( functions={ "x": l * dist_x + point_1[0], "y": point_1[1], "normal_x": 0, "normal_y": -1, }, ranges=ranges, area=dist_x, ) line_2 = Edge( functions={ "x": l * dist_x + point_1[0], "y": point_2[1], "normal_x": 0, "normal_y": 1, }, ranges=ranges, area=dist_x, ) self.edges = [line_1, line_2] center_y = point_1[1] + (dist_y) / 2 y_diff = Abs(y - center_y) - (point_2[1] - center_y) outside_distance = sqrt(Max(y_diff, 0) ** 2) inside_distance = Min(y_diff, 0) self.sdf = -(outside_distance + inside_distance) self.bounds = { "x": (min(point_1[0], point_2[0]), max(point_1[0], point_2[0])), "y": (min(point_1[1], point_2[1]), max(point_1[1], point_2[1])), }
[docs]class Rectangle(Geometry2D): def __init__(self, point_1, point_2): l, x, y = symbols("l x y") ranges = {l: (0, 1)} dist_x = point_2[0] - point_1[0] dist_y = point_2[1] - point_1[1] edge_1 = Edge( functions={ "x": l * dist_x + point_1[0], "y": point_1[1], "normal_x": 0, "normal_y": -1, }, ranges=ranges, area=dist_x, ) edge_2 = Edge( functions={ "x": point_2[0], "y": l * dist_y + point_1[1], "normal_x": 1, "normal_y": 0, }, ranges=ranges, area=dist_y, ) edge_3 = Edge( functions={ "x": l * dist_x + point_1[0], "y": point_2[1], "normal_x": 0, "normal_y": 1, }, ranges=ranges, area=dist_x, ) edge_4 = Edge( functions={ "x": point_1[0], "y": -l * dist_y + point_2[1], "normal_x": -1, "normal_y": 0, }, ranges=ranges, area=dist_y, ) self.edges = [edge_1, edge_2, edge_3, edge_4] center_x = point_1[0] + (dist_x) / 2 center_y = point_1[1] + (dist_y) / 2 x_diff = Abs(x - center_x) - (point_2[0] - center_x) y_diff = Abs(y - center_y) - (point_2[1] - center_y) outside_distance = sqrt(Max(x_diff, 0) ** 2 + Max(y_diff, 0) ** 2) inside_distance = Min(Max(x_diff, y_diff), 0) self.sdf = -(outside_distance + inside_distance) self.bounds = { "x": (min(point_1[0], point_2[0]), max(point_1[0], point_2[0])), "y": (min(point_1[1], point_2[1]), max(point_1[1], point_2[1])), }
[docs]class Circle(Geometry2D): def __init__(self, center, radius): theta, x, y = symbols("theta x y") ranges = {theta: (0, 2 * math.pi)} edge = Edge( functions={ "x": center[0] + radius * cos(theta), "y": center[1] + radius * sin(theta), "normal_x": 1 * cos(theta), "normal_y": 1 * sin(theta), }, ranges=ranges, area=2 * pi * radius, ) self.edges = [edge] self.sdf = radius - sqrt((x - center[0]) ** 2 + (y - center[1]) ** 2) self.bounds = { "x": (center[0] - radius, center[0] + radius), "y": (center[1] - radius, center[1] + radius), }
[docs]class Heart(Geometry2D): def __init__(self, center=(0, 0.5), radius=0.5): c1, c2 = center theta, t, x, y = symbols("t theta x y") ranges = {theta: (0, math.pi), t: (0, 1)} edge_1 = Edge( functions={ "x": center[0] - t * radius, "y": center[1] - (1 - t) * radius, "normal_x": -1.0, "normal_y": -1.0, }, ranges=ranges, area=math.sqrt(2) * radius, ) edge_2 = Edge( functions={ "x": center[0] + t * radius, "y": center[1] - (1 - t) * radius, "normal_x": 1.0, "normal_y": -1.0, }, ranges=ranges, area=math.sqrt(2) * radius, ) edge_3 = Edge( functions={ "x": center[0] - radius / 2 + radius / math.sqrt(2) * cos(math.pi / 4 * 5 - theta), "y": center[1] + radius / 2 + radius / math.sqrt(2) * sin(math.pi / 4 * 5 - theta), "normal_x": cos(math.pi / 4 * 5 - theta), "normal_y": sin(math.pi / 4 * 5 - theta), }, ranges=ranges, area=math.sqrt(2) * radius * math.pi, ) edge_4 = Edge( functions={ "x": center[0] + radius / 2 + radius / math.sqrt(2) * cos(math.pi / 4 * 3 - theta), "y": center[1] + radius / 2 + radius / math.sqrt(2) * sin(math.pi / 4 * 3 - theta), "normal_x": cos(math.pi / 4 * 3 - theta), "normal_y": sin(math.pi / 4 * 3 - theta), }, ranges=ranges, area=math.sqrt(2) * radius * math.pi, ) self.edges = [edge_1, edge_2, edge_3, edge_4] x, y = symbols("x y") x = (x - c1) * 0.5 / radius y = (y - c2) * 0.5 / radius + 0.5 part1 = Heaviside(Abs(x) + y - 1) * ( sqrt((Abs(x) - 0.25) ** 2 + (y - 0.75) ** 2) - math.sqrt(2) / 4 ) part_i = 0.5 * Max(Abs(x) + y, 0) part2 = ( (1 - Heaviside(Abs(x) + y - 1)) * sign(Abs(x) - y) * Min( sqrt(Abs(x) ** 2 + (y - 1) ** 2), sqrt((Abs(x) - part_i) ** 2 + (y - part_i) ** 2), ) ) self.sdf = (-part1 - part2) * radius * 2 self.bounds = { "x": ( center[0] - 0.5 * radius - 0.5 * math.sqrt(2) * radius, center[0] + 0.5 * radius + 0.5 * math.sqrt(2) * radius, ), "y": ( center[1] - radius, center[1] + 0.5 * radius + 0.5 * math.sqrt(2) * radius, ), }
[docs]class Triangle(Geometry2D): def __init__(self, p0, p1, p2): x, y, t = symbols("x y t") N = CoordSys3D("N") P0 = p0[0] * N.i + p0[1] * N.j P1 = p1[0] * N.i + p1[1] * N.j P2 = p2[0] * N.i + p2[1] * N.j p = x * N.i + y * N.j e0, e1, e2 = P1 - P0, P2 - P1, P0 - P2 v0, v1, v2 = p - P0, p - P1, p - P2 pq0 = v0 - e0 * Max(Min(v0.dot(e0) / e0.dot(e0), 1), 0) pq1 = v1 - e1 * Max(Min(v1.dot(e1) / e1.dot(e1), 1), 0) pq2 = v2 - e2 * Max(Min(v2.dot(e2) / e2.dot(e2), 1), 0) s = sign(e0.dot(N.i) * e2.dot(N.j) - e0.dot(N.j) * e2.dot(N.i)) u = sqrt(Min(pq0.dot(pq0), pq1.dot(pq1), pq2.dot(pq2))) v = Min( s * (v0.dot(N.i) * e0.dot(N.j) - v0.dot(N.j) * e0.dot(N.i)), s * (v1.dot(N.i) * e1.dot(N.j) - v1.dot(N.j) * e1.dot(N.i)), s * (v2.dot(N.i) * e2.dot(N.j) - v2.dot(N.j) * e2.dot(N.i)), ) self.sdf = u * sign(v) l0 = sqrt(e0.dot(e0)) l1 = sqrt(e1.dot(e1)) l2 = sqrt(e2.dot(e2)) ranges = {t: (0, 1)} in_out_sign = -sign(e0.cross(e1).dot(N.k)) edge_1 = Edge( functions={ "x": p1[0] + t * (p0[0] - p1[0]), "y": p1[1] + t * (p0[1] - p1[1]), "normal_x": (p0[1] - p1[1]) / l0 * in_out_sign, "normal_y": (p1[0] - p0[0]) / l0 * in_out_sign, }, ranges=ranges, area=l0, ) edge_2 = Edge( functions={ "x": p2[0] + t * (p1[0] - p2[0]), "y": p2[1] + t * (p1[1] - p2[1]), "normal_x": (p1[1] - p2[1]) / l1 * in_out_sign, "normal_y": (p2[0] - p1[0]) / l1 * in_out_sign, }, ranges=ranges, area=l1, ) edge_3 = Edge( functions={ "x": p0[0] + t * (p2[0] - p0[0]), "y": p0[1] + t * (p2[1] - p0[1]), "normal_x": (p2[1] - p0[1]) / l2 * in_out_sign, "normal_y": (p0[0] - p2[0]) / l2 * in_out_sign, }, ranges=ranges, area=l2, ) self.edges = [edge_1, edge_2, edge_3] self.bounds = { "x": (min(p0[0], p1[0], p2[0]), max(p0[0], p1[0], p2[0])), "y": (min(p0[1], p1[1], p2[1]), max(p0[1], p1[1], p2[1])), }
[docs]class Polygon(Geometry2D): def __init__(self, points): v = points t = symbols("t") ranges = {t: (0, 1)} def _sdf(x: np.ndarray, y: np.ndarray, **kwargs): s = np.ones_like(x) _points = np.concatenate([x, y], axis=1) d = ((np.array(v[0]) - _points) ** 2).sum(axis=1, keepdims=True) for i in range(len(v)): e = np.array(v[i - 1]) - np.array(v[i]) w = _points - np.array(v[i]) b = w - e * np.clip( (w * e).sum(axis=1, keepdims=True) / (e * e).sum(), 0, 1 ) d = np.minimum(d, (b * b).sum(keepdims=True, axis=1)) cond1 = _points[:, 1:] >= v[i][1] cond2 = _points[:, 1:] < v[i - 1][1] cond3 = e[0] * w[:, 1:] > e[1] * w[:, :1] inverse_idx1 = np.all([cond1, cond2, cond3], axis=0) inverse_idx2 = np.all( [ np.logical_not(cond1), np.logical_not(cond2), np.logical_not(cond3), ], axis=0, ) inverse_idx = np.any([inverse_idx1, inverse_idx2], axis=0) s[inverse_idx] *= -1 return -np.sqrt(d) * s self.sdf = _sdf self.edges = [] for i, _ in enumerate(points): length = math.sqrt( (points[i - 1][0] - points[i][0]) ** 2 + (points[i - 1][1] - points[i][1]) ** 2 ) edge = Edge( functions={ "x": points[i - 1][0] - t * (points[i - 1][0] - points[i][0]), "y": points[i - 1][1] - t * (points[i - 1][1] - points[i][1]), "normal_x": (points[i][1] - points[i - 1][1]) / length, "normal_y": (points[i - 1][0] - points[i][0]) / length, }, ranges=ranges, area=length, ) self.edges.append(edge) _p = iter(zip(*points)) _p1 = next(_p) _p2 = next(_p) self.bounds = {"x": (min(_p1), max(_p1)), "y": (min(_p2), max(_p2))}
[docs] def translation(self, direction: Union[List, Tuple]): raise NotImplementedError
[docs] def rotation(self, angle: float, axis: str = "z", center=None): raise NotImplementedError
[docs] def scaling(self, scale: float, center: Tuple = None): raise NotImplementedError
[docs]class Plane(Geometry3D): def __init__(self, point_1, point_2, normal): assert point_1[0] == point_2[0], "Points must have the same x coordinate" x, y, z, s_1, s_2 = symbols("x y z s_1 s_2") 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] ranges = {s_1: (-1, 1), s_2: (-1, 1)} edge = Edge( 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, }, ranges=ranges, area=side_y * side_z, ) self.edges = [edge] self.sdf = normal * (center[0] - x) self.bounds = { "x": (min(point_1[0], point_2[0]), max(point_1[0], point_2[0])), "y": (min(point_1[1], point_2[1]), max(point_1[1], point_2[1])), "z": (min(point_1[2], point_2[2]), max(point_1[2], point_2[2])), }
[docs]class Tube3D(Geometry3D): def __init__(self, point_1, point_2): x, y, z, s_1, s_2 = symbols("x y z s_1 s_2") 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] ranges = {s_1: (-1, 1), s_2: (-1, 1)} edge_1 = Edge( 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, }, ranges=ranges, area=side_x * side_y, ) edge_2 = Edge( 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, }, ranges=ranges, area=side_x * side_y, ) edge_3 = Edge( 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, }, ranges=ranges, area=side_x * side_z, ) edge_4 = Edge( 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, }, ranges=ranges, area=side_x * side_z, ) self.edges = [edge_1, edge_2, edge_3, edge_4] 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) self.sdf = -(outside_distance + inside_distance) self.bounds = { "x": (min(point_1[0], point_2[0]), max(point_1[0], point_2[0])), "y": (min(point_1[1], point_2[1]), max(point_1[1], point_2[1])), "z": (min(point_1[2], point_2[2]), max(point_1[2], point_2[2])), }
[docs]class Tube(Tube3D): def __init__(self, point_1, point_2): super(Tube, self).__init__(point_1, point_2)
[docs]class CircularTube(Geometry3D): def __init__(self, center, radius, height): x, y, z, h, theta = symbols("x y z h theta") ranges = {h: (-1, 1), theta: (0, 2 * pi)} edge_1 = Edge( 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, }, ranges=ranges, area=height * 2 * pi * radius, ) self.edges = [edge_1] self.sdf = radius - sqrt((x - center[0]) ** 2 + (y - center[1]) ** 2) self.bounds = { "x": (center[0] - radius, center[0] + radius), "y": (center[1] - radius, center[1] + radius), "z": (center[2] - height / 2, center[2] + height / 2), }
[docs]class Box(Geometry3D): def __init__(self, point_1, point_2): x, y, z, s_1, s_2 = symbols("x y z s_1 s_2") 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] ranges = {s_1: (-1, 1), s_2: (-1, 1)} self.bounds = { "x": (min(point_1[0], point_2[0]), max(point_1[0], point_2[0])), "y": (min(point_1[1], point_2[1]), max(point_1[1], point_2[1])), "z": (min(point_1[2], point_2[2]), max(point_1[2], point_2[2])), } edge_1 = Edge( 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, }, ranges=ranges, area=side_x * side_y, ) edge_2 = Edge( 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, }, ranges=ranges, area=side_x * side_y, ) edge_3 = Edge( 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, }, ranges=ranges, area=side_x * side_z, ) edge_4 = Edge( 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, }, ranges=ranges, area=side_x * side_z, ) edge_5 = Edge( 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, }, ranges=ranges, area=side_y * side_z, ) edge_6 = Edge( 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, }, ranges=ranges, area=side_y * side_z, ) self.edges = [edge_1, edge_2, edge_3, edge_4, edge_5, edge_6] 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) self.sdf = -(outside_distance + inside_distance) self.bounds = { "x": (min(point_1[0], point_2[0]), max(point_1[0], point_2[0])), "y": (min(point_1[1], point_2[1]), max(point_1[1], point_2[1])), "z": (min(point_1[2], point_2[2]), max(point_1[2], point_2[2])), }
[docs]class Sphere(Geometry3D): def __init__(self, center, radius): x, y, z, v_1, v_2 = symbols("x y z v_1 v_2") ranges = {v_1: (0, 1), v_2: (0, 1)} theta = 2 * pi * v_1 r = sqrt(v_2) phi = 2 * asin(r) # latitude r_1 = cos(theta) * sin(phi) r_2 = sin(theta) * sin(phi) r_3 = cos(phi) # x, y, z, v_1, v_2, u_1, u_2 = symbols("x y z v_1 v_2 u_1 u_2") # ranges = {v_1: (0, 1), v_2: (0, 1), u_1: (0, 1), u_2: (0, 1)} # r_1 = sqrt(-log(v_1)) * cos(2 * pi * u_1) # r_2 = sqrt(-log(v_1)) * sin(2 * pi * u_1) # r_3 = sqrt(-log(v_2)) * cos(2 * pi * u_2) norm = sqrt(r_1 ** 2 + r_2 ** 2 + r_3 ** 2) edge_1 = Edge( functions={ "x": center[0] + radius * r_1 / norm, "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, }, ranges=ranges, area=4 * pi * radius ** 2, ) self.edges = [edge_1] self.sdf = radius - sqrt( (x - center[0]) ** 2 + (y - center[1]) ** 2 + (z - center[2]) ** 2 ) self.bounds = { "x": (center[0] - radius, center[0] + radius), "y": (center[1] - radius, center[1] + radius), "z": (center[2] - radius, center[2] + radius), }
[docs]class Cylinder(Geometry3D): def __init__(self, center, radius, height): x, y, z, h, r, theta = symbols("x y z h r theta") ranges = {h: (-1, 1), r: (0, 1), theta: (0, 2 * pi)} edge_1 = Edge( 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, }, ranges=ranges, area=height * 2 * pi * radius, ) edge_2 = Edge( 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, }, ranges=ranges, area=math.pi * radius ** 2, ) edge_3 = Edge( 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, }, ranges=ranges, area=pi * radius ** 2, ) self.edges = [edge_1, edge_2, edge_3] 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)) ) self.sdf = -(outside_distance + inside_distance) self.bounds = { "x": (center[0] - radius, center[0] + radius), "y": (center[1] - radius, center[1] + radius), "z": (center[2] - height / 2, center[2] + height / 2), }