The Sphere
Perfect Symmetry
A Sphere is the set of all points in 3D space that are at a fixed distance (the radius) from a single point (the center). In the world of computational geometry, the sphere is the ultimate symbol of symmetry and efficiency.
Unlike a Mesh, which is an approximation made of flat faces, a mathematical Sphere is infinitely smooth.
x² + y² + z² = r²). Implicit geometry never loses precision.Data Structure
Because of its mathematical perfection, a Sphere requires almost no memory. To define a sphere in a CAD system, you only need:
Center: APoint3d.Radius: A singledouble(floating point number).
1class Sphere:2 """3 Represents a mathematical sphere.4 """5 def __init__(self, center: Point3d, radius: float):6 self.Center = center7 self.Radius = radius
PointAt(u, v):
1def PointAt(self, u_degrees: float, v_degrees: float) -> Point3d:2 """Maps longitude (u) and latitude (v) to a 3D Point."""3 u_rad = math.radians(u_degrees)4 v_rad = math.radians(v_degrees)5 x = self.Radius * sin(v_rad) * cos(u_rad)6 y = self.Radius * sin(v_rad) * sin(u_rad)7 z = self.Radius * cos(v_rad)8 return Point3d(x + self.Center.X, y + self.Center.Y, z + self.Center.Z)
U maps to Longitude and V maps to Latitude. This mapping allows us to treat a 3D sphere as a 2D flat image, though it inevitably creates distortion at the North and South poles.The Singularities: At the exact top and bottom of a sphere (the poles), all longitudinal lines converge to a single point. This creates "Tris" or "Poles" that can cause issues during subdivision or smoothing.Translate(vector):
Moving a sphere is computationally trivial — you only need to add a translation vector to its Center point.
1def Translate(self, vector: 'Vector3d'):2 """Moving a sphere only requires moving its Center point."""3 self.Center += vector
Scale(factor):
Radius by a scalar factor.1def Scale(self, factor: float):2 """Scaling a sphere is simply multiplying its Radius."""3 self.Radius *= factor
Rotate(axis, angle):
Compute(sphere):
(4/3)πr³ and surface area is 4πr². These are exact, unlike mesh-based approximations. Essential for material estimation, hydrostatic calculations, and structural BIM data.1import math23def SphereVolume(sphere: Sphere) -> dict:4 """Computes exact analytical properties of a sphere."""5 r = sphere.Radius67 volume = (4 / 3) * math.pi * r ** 38 surface_area = 4 * math.pi * r ** 2910 # Or use the unified API:11 props = VolumeMassProperties.Compute(sphere)12 return { "volume": props.Volume, "centroid": props.Centroid }
Intersection:
1def RaySphereIntersection(ray: 'Ray3d', sphere: 'Sphere') -> list['Point3d']:2 """Finds intersection points between a ray and a sphere."""34 # Solved using the quadratic formula: at^2 + bt + c = 05 # where t is the distance along the ray67 L = ray.Position - sphere.Center8 a = Vector3d.DotProduct(ray.Direction, ray.Direction)9 b = 2.0 * Vector3d.DotProduct(ray.Direction, L)10 c = Vector3d.DotProduct(L, L) - (sphere.Radius ** 2)1112 discriminant = b*b - 4*a*c1314 if discriminant < 0:15 return [] # No intersection16 elif discriminant == 0:17 t = -b / (2*a)18 return [ray.PointAt(t)] # One hit (tangent)19 else:20 t1 = (-b + math.sqrt(discriminant)) / (2*a)21 t2 = (-b - math.sqrt(discriminant)) / (2*a)22 return [ray.PointAt(t1), ray.PointAt(t2)] # Two hits
SphereSphere(a, b):
1def SphereSphereIntersect(a: Sphere, b: Sphere) -> Circle:2 """Calculates the circle of intersection between two spheres."""3 # Vector from center A to center B4 dx = b.Center.X - a.Center.X5 dy = b.Center.Y - a.Center.Y6 dz = b.Center.Z - a.Center.Z7 d = math.sqrt(dx**2 + dy**2 + dz**2)89 # Check for no intersection10 if d > (a.Radius + b.Radius) or d < abs(a.Radius - b.Radius) or d == 0:11 return None1213 # Distance from center A to the circle plane14 x = (d**2 - b.Radius**2 + a.Radius**2) / (2 * d)1516 # Radius of the intersection circle17 circle_radius = math.sqrt(a.Radius**2 - x**2)1819 # Center of the intersection circle20 cx = a.Center.X + (dx / d) * x21 cy = a.Center.Y + (dy / d) * x22 cz = a.Center.Z + (dz / d) * x23 circle_center = Point3d(cx, cy, cz)2425 # Normal of the circle's plane26 normal = Vector3d(dx/d, dy/d, dz/d)2728 return Circle(circle_center, normal, circle_radius)
Contains:
1def SphereContains(sphere: 'Sphere', pt: 'Point3d', strict: bool) -> bool:2 """Checks if a point is inside a sphere."""34 # Calculate the distance from the point to the sphere's center5 dist = pt.DistanceTo(sphere.Center)67 # Compare with radius8 if strict:9 return dist < sphere.Radius10 else:11 return dist <= sphere.Radius
NormalAt(u, v):
1def NormalAt(self, u_degrees: float, v_degrees: float) -> Vector3d:2 """Returns the normal vector pointing outward from the surface at a UV coordinate."""3 # Calculate the point on the sphere surface4 u_rad = math.radians(u_degrees)5 v_rad = math.radians(v_degrees)6 x = sin(v_rad) * cos(u_rad)7 y = sin(v_rad) * sin(u_rad)8 z = cos(v_rad)910 # The normal of a sphere is simply the normalized vector from the center to the point11 return Vector3d(x, y, z)
Tangent Plane:
1import math23# Sphere parametrization:4# u = longitude (0 to 2PI), v = latitude (-PI/2 to PI/2)56def GetTangentPlane(sphere: Sphere, u: float, v: float) -> Plane:7 """Returns the tangent plane at a specific (u,v) coordinate on the sphere."""8 # Get the 3D point9 pt = sphere.PointAt(u, v)1011 # The normal of a sphere always points from the center to the surface point12 normal = sphere.NormalAt(u, v)1314 # Create a plane at that point, facing the normal direction15 tangent_plane = Plane(pt, normal)1617 return tangent_plane
Great Circle:
1def CreateGreatCircle(sphere: Sphere, ptA: Point3d, ptB: Point3d) -> Circle:2 """Creates the great circle passing through two points on a sphere."""34 # A Great Circle is created by slicing the sphere with a plane5 # that passes through the sphere's center and the two target points.67 # Plane through 3 points: Center, A, B8 plane = Plane(sphere.Center, ptA, ptB)910 # The great circle has the exact same radius as the sphere11 great_circle = Circle(plane, sphere.Radius)1213 return great_circle
ClosestPoint:
1def ClosestPoint(sphere: Sphere, test_pt: Point3d) -> Point3d:2 """Projects a point onto the sphere boundary."""3 # 1. Get direction from center4 dir = test_pt - sphere.Center5 # 2. Force length to match Radius6 dir.Unitize()7 return sphere.Center + (dir * sphere.Radius)
DistanceTo(point):
1def DistanceTo(self, pt: Point3d) -> float:2 """Calculates the shortest distance from a point to the surface of the sphere."""3 # Calculate distance from the test point to the center of the sphere4 dist_to_center = self.Center.DistanceTo(pt)56 # Subtract the radius. If the point is inside, this will be negative,7 # so we return max(0, distance) if we only want exterior distance.8 return max(0.0, dist_to_center - self.Radius)
Plane Intersection:
1def PlaneSphereIntersection(sphere: 'Sphere', plane: 'Plane') -> tuple[bool, 'Circle']:2 """Finds the circle of intersection between a sphere and a plane."""3 # Distance from sphere center to plane4 # Assuming plane is defined by an Origin and Normal5 vector_to_center = sphere.Center - plane.Origin6 dist = Vector3d.DotProduct(vector_to_center, plane.Normal)78 if abs(dist) > sphere.Radius:9 return False, None # No intersection1011 # Radius of the resulting intersection circle12 circle_radius = math.sqrt(sphere.Radius**2 - dist**2)1314 # Center of the intersection circle15 circle_center = sphere.Center - (plane.Normal * dist)1617 return True, Circle(Plane(circle_center, plane.Normal), circle_radius)
Box Intersection:
1def SphereIntersectsBox(sphere: 'Sphere', box_min: Point3d, box_max: Point3d) -> bool:2 """Checks if a sphere intersects an AABB by finding the closest point on the box to the sphere."""3 # Find closest point on box to sphere center4 closest_x = max(box_min.X, min(sphere.Center.X, box_max.X))5 closest_y = max(box_min.Y, min(sphere.Center.Y, box_max.Y))6 closest_z = max(box_min.Z, min(sphere.Center.Z, box_max.Z))78 # Check if distance to closest point is within radius9 closest_point = Point3d(closest_x, closest_y, closest_z)10 distance = sphere.Center.DistanceTo(closest_point)1112 return distance <= sphere.Radius
Bounding Box Extraction:
1def SphereBoundingBox(sphere: 'Sphere') -> 'BoundingBox':2 """Computes the Axis-Aligned Bounding Box (AABB) of a sphere."""3 # A sphere's AABB is perfectly symmetric around its center4 min_pt = Point3d(sphere.Center.X - sphere.Radius,5 sphere.Center.Y - sphere.Radius,6 sphere.Center.Z - sphere.Radius)78 max_pt = Point3d(sphere.Center.X + sphere.Radius,9 sphere.Center.Y + sphere.Radius,10 sphere.Center.Z + sphere.Radius)1112 return BoundingBox(min_pt, max_pt)
BoundingSphere(box):
1def BoundingSphere(bbox: BoundingBox) -> Sphere:2 """Creates a sphere that exactly bounds an Axis-Aligned Bounding Box."""3 # The center of the sphere is the center of the bounding box4 center = bbox.Center56 # The radius is the distance from the center to any of the box's corners7 radius = center.DistanceTo(bbox.Max)89 return Sphere(center, radius)
FitSphere(points):
1def FitSphere(pts: list[Point3d]) -> Sphere:2 """Calculates a best-fit sphere for a collection of points."""3 # A simple bounding approach is taking the bounding box,4 # finding its center, and setting the radius to the farthest point.56 # 1. Find bounding box center7 min_pt, max_pt = GetBoundingBox(pts)8 center = Point3d(9 (min_pt.X + max_pt.X) / 2,10 (min_pt.Y + max_pt.Y) / 2,11 (min_pt.Z + max_pt.Z) / 212 )1314 # 2. Find the farthest point from the center15 max_dist = 0.016 for pt in pts:17 dist = center.DistanceTo(pt)18 if dist > max_dist:19 max_dist = dist2021 return Sphere(center, max_dist)