Modules | PY

Core

Core data structures, primitives, and basic geometric queries.

The Core module provides fundamental geometric primitives and basic queries for geometric processing.

Primitives

Primitives are lightweight wrappers around numpy arrays for type safety and dispatch.

PrimitiveDescriptionDimensions
PointPoint in 2D or 3D space2D, 3D
RaySemi-infinite line (origin + direction)2D, 3D
LineInfinite line (origin + direction)2D, 3D
PlanePlane defined by normal and offset3D only
SegmentLine segment between two points2D, 3D
PolygonOrdered vertices forming a polygon2D, 3D
AABBAxis-aligned bounding box2D, 3D

Points

A point in 2D or 3D space.

import trueform as tf
import numpy as np

# Create a 3D point
point = tf.Point([1.0, 2.0, 3.0])
print(point.dims)  # 3
print(point.coords)  # [1. 2. 3.]

# Access coordinates
print(point.x, point.y, point.z)  # 1.0 2.0 3.0

# Factory methods
point2d = tf.Point.from_xy(1.0, 2.0)
point3d = tf.Point.from_xyz(1.0, 2.0, 3.0)

Line and Ray

Lines and rays are composites of an origin point and a direction vector.

# Ray (semi-infinite line with origin and direction)
ray = tf.Ray(origin=[0, 0, 0], direction=[1, 0, 0])
print(ray.origin)     # [0. 0. 0.]
print(ray.direction)  # [1. 0. 0.]

# Direction is NOT normalized by default
print(ray.normalized_direction)  # Get unit vector
print(ray.direction_norm)        # Get magnitude

# Factory method: ray from start point through another point
ray = tf.Ray.from_points(start=[0, 0, 0], through_point=[1, 1, 1])

# Line (infinite line through origin with direction)
line = tf.Line(origin=[0, 0, 0], direction=[1, 0, 0])
# Or from two points
line = tf.Line.from_points([0, 0], [1, 1])

Plane

A plane is a composite of a normal vector and offset d, where the equation is normal · x + d = 0.

# Using normal and a point on the plane
plane = tf.Plane(normal=[0, 0, 1], origin=[0, 0, 5])  # z = 5 plane
print(plane.normal)  # [0. 0. 1.] (normalized)
print(plane.offset)  # -5.0

# Using plane coefficients directly
plane = tf.Plane(coeffs=[0, 0, 1, -5])  # ax + by + cz + d = 0

# Factory methods
plane = tf.Plane.from_point_normal(origin=[0, 0, 5], normal=[0, 0, 1])
plane = tf.Plane.from_points([0, 0, 0], [1, 0, 0], [0, 1, 0])  # XY plane

# Note: Plane is 3D only

Segment

A line segment defined by two endpoints.

# Create segment from endpoints
segment = tf.Segment([[0, 0, 0], [1, 1, 1]])
print(segment.start)  # [0. 0. 0.]
print(segment.end)    # [1. 1. 1.]

# Properties
print(segment.length)    # Length of segment
print(segment.midpoint)  # Midpoint
print(segment.vector)    # Direction vector

# Factory method
seg = tf.Segment.from_points([0, 0], [1, 1])

Polygon

A polygon defined by ordered vertices.

# Create a triangle
triangle = tf.Polygon([[0, 0], [1, 0], [0.5, 1]])
print(triangle.num_vertices)  # 3
print(triangle.dims)          # 2

# Access vertices
vertices = triangle.vertices  # (3, 2) array

AABB

An axis-aligned bounding box is a composite of a min and max point, representing the minimal and maximal corners.

# Using min/max
aabb = tf.AABB(min=[0, 0, 0], max=[1, 1, 1])
print(aabb.min)     # [0. 0. 0.]
print(aabb.max)     # [1. 1. 1.]
print(aabb.center)  # [0.5 0.5 0.5]
print(aabb.size)    # [1. 1. 1.]
print(aabb.volume)  # 1.0

# Using bounds array
aabb = tf.AABB(bounds=[[0, 0, 0], [1, 1, 1]])

# Factory methods
aabb = tf.AABB.from_center_size(center=[5, 5], size=[2, 2])
aabb = tf.AABB.from_points([[0, 0], [1, 0], [1, 1], [0, 1]])  # Bounds all points

Transforming Primitives

Any primitive can be transformed using tf.transformed(primitive, transformation):

import numpy as np

# Create a 3D transformation matrix (4x4 homogeneous)
# Translation by (10, 0, 0)
translation = np.eye(4, dtype=np.float32)
translation[:3, 3] = [10, 0, 0]

# Transform a point
point = tf.Point([1, 2, 3])
transformed_point = tf.transformed(point, translation)
print(transformed_point.coords)  # [11. 2. 3.]

# Transform a segment
segment = tf.Segment([[0, 0, 0], [1, 0, 0]])
transformed_segment = tf.transformed(segment, translation)
print(transformed_segment.start)  # [10. 0. 0.]
print(transformed_segment.end)    # [11. 0. 0.]

# 90-degree rotation around Z-axis
rotation = np.array([
    [0, -1, 0, 0],
    [1,  0, 0, 0],
    [0,  0, 1, 0],
    [0,  0, 0, 1]
], dtype=np.float32)

# Transform a polygon
polygon = tf.Polygon([[1, 0, 0], [0, 1, 0], [0, 0, 1]])
transformed_polygon = tf.transformed(polygon, rotation)

# Combined transformation (rotation + translation)
combined = translation @ rotation
transformed = tf.transformed(polygon, combined)

Queries on Primitives

Primitives support distance, intersection, and ray casting queries.

Distance and Proximity

All pairs of primitives support the following queries:

QueryReturns
distance2Squared distance between primitives
distanceDistance between primitives
closest_metric_point(dist2, point) on first argument
closest_metric_point_pair(dist2, point0, point1)
intersectsbool - do the primitives intersect
import trueform as tf

# Squared distance (faster, avoids sqrt)
d2 = tf.distance2(point, polygon)

# Distance
d = tf.distance(segment0, segment1)

# Closest point on polygon to a point
dist2, closest_pt = tf.closest_metric_point(polygon, point)

# Closest points between two primitives
dist2, pt_on_poly0, pt_on_poly1 = tf.closest_metric_point_pair(polygon0, polygon1)

# Intersection test
do_intersect = tf.intersects(aabb0, aabb1)

Ray Casting

Ray casting tests whether a ray intersects a primitive, returning the parametric distance t along the ray (or None if no intersection).

QueryReturns
ray_castt (parametric distance) or None

Supported primitives: Point, Line, Ray, Segment, Plane, Polygon, AABB

ray = tf.Ray(origin=[0.5, 0.3, 2.0], direction=[0, 0, -1])
polygon = tf.Polygon([[0, 0, 0], [2, 0, 0], [1, 2, 0]])

# Basic ray cast
t = tf.ray_cast(ray, polygon)
if t is not None:
    hit_point = ray.origin + t * ray.direction
    print(f"Hit at {hit_point}, t={t}")

# With parametric bounds [min_t, max_t]
aabb = tf.AABB(min=[0, 0, 0], max=[1, 1, 1])
t = tf.ray_cast(ray, aabb, config=(0.0, 100.0))

Data Structures

OffsetBlockedArray

OffsetBlockedArray is a data structure for representing variable-length blocks of data, commonly used for curves and paths returned by various trueform functions. It efficiently stores multiple sequences of different lengths using two arrays.

Structure

The data structure consists of:

  • offsets: Array of block boundaries (starting at 0, ending at len(data))
  • data: Packed array containing all block elements

For example, to represent 3 curves with 3, 4, and 2 points respectively:

offsets = [0, 3, 7, 9]  # Block boundaries
data = [0,1,2, 3,4,5,6, 7,8]  # Packed data
# Block 0: data[0:3] = [0,1,2]
# Block 1: data[3:7] = [3,4,5,6]
# Block 2: data[7:9] = [7,8]

Usage

Functions like intersection_curves and isocontours return paths as OffsetBlockedArray:

import trueform as tf
import numpy as np

# Example: intersection_curves returns (paths, points)
paths, curve_points = tf.intersection_curves(mesh1, mesh2)

# paths is an OffsetBlockedArray
# curve_points is a numpy array of all curve vertices

# Get number of curves
print(f"Number of curves: {len(paths)}")

# Iterate over each curve
for i, path_indices in enumerate(paths):
    # path_indices is a numpy array of indices into curve_points
    pts = curve_points[path_indices]
    print(f"Curve {i}: {len(pts)} points")

# Access individual curves by index
first_curve_indices = paths[0]
first_curve_points = curve_points[first_curve_indices]

# Access underlying arrays
print(f"Offsets: {paths.offsets}")
print(f"Data: {paths.data}")

Creating OffsetBlockedArray

You can create an OffsetBlockedArray directly from offsets and data arrays:

# Create from offsets and data arrays
offsets = np.array([0, 3, 7, 10], dtype=np.int32)
data = np.array([0,1,2, 3,4,5,6, 7,8,9], dtype=np.int32)

blocks = tf.OffsetBlockedArray(offsets, data)

# Iterate over blocks
for block in blocks:
    print(block)  # Each block is a view into the data array

# Access by index
print(blocks[0])  # [0 1 2]
print(blocks[1])  # [3 4 5 6]
print(blocks[2])  # [7 8 9]

Creating from Uniform Arrays

For uniform arrays (where all blocks have the same size), use from_uniform() or the convenience function tf.as_offset_blocked():

import trueform as tf
import numpy as np

# Quad faces as a 2D array (4 vertices per face)
quads = np.array([
    [0, 1, 2, 3],
    [4, 5, 6, 7],
    [8, 9, 10, 11]
], dtype=np.int32)

# Method 1: Class method
faces = tf.OffsetBlockedArray.from_uniform(quads)

# Method 2: Factory function (equivalent)
faces = tf.as_offset_blocked(quads)

# Now create a mesh with dynamic face support
mesh = tf.Mesh(faces, points)
print(mesh.is_dynamic)  # True
print(mesh.ngon)        # 4 (uniform quad mesh)

This is particularly useful for creating meshes with quad or higher-order polygon faces, enabling the full dynamic mesh pipeline while maintaining the familiar 2D array input format.

Properties

PropertyReturnsDescription
offsetsnp.ndarrayBlock boundary indices
datanp.ndarrayPacked data array
dtypenp.dtypeData type of the data array

Requirements

  • Both offsets and data must have the same dtype (int32 or int64)
  • offsets[0] must be 0
  • offsets[-1] must equal len(data)
  • offsets must be non-decreasing

This data structure is memory-efficient and enables zero-copy iteration over variable-length sequences.

For queries on forms (Mesh, EdgeMesh, PointCloud) with spatial acceleration, see the Spatial module. For file I/O operations, see the I/O module. For detailed implementation, see the C++ Core documentation.