Skip to content

Colliders

FluxRender.physics.ImageCollider

ImageCollider(image_path: str, center: Sequence[float] = (0.0, 0.0), x_scale: float | None = None, y_scale: float | None = None)

Bases: Collider

A solid collider generated from an external RGBA image file.

It uses the image's alpha channel to define solid obstacle boundaries within the fluid simulation.

Parameters:

Name Type Description Default
image_path str

The local file path to the image (e.g., a transparent PNG). Pixels with over 50% opacity are converted into impenetrable concrete blocks in the LBM grid.

required
center Sequence[float]

The (x, y) spatial coordinates in world space where the exact center of the image will be anchored.

(0.0, 0.0)
x_scale float

The exact physical width the image should occupy in world space. If omitted, it scales proportionally based on the provided y_scale to preserve the original aspect ratio.

None
y_scale float

The exact physical height the image should occupy in world space. If omitted, it scales proportionally based on the provided x_scale.

None
Example

Importing a car profile as a solid wind tunnel obstacle:

import FluxRender as fr

# The image will be exactly 3.0 units wide. Its height will automatically
# adjust to maintain the original file's aspect ratio.
car_profile = fr.ImageCollider(
    image_path="assets/sports_car_silhouette.png",
    center=(0.0, -2.0),
    x_scale=3.0
)

Source code in FluxRender/physics.py
def __init__(self,
             image_path: str,
             center: Sequence[float] = (0.0, 0.0),
             x_scale: float | None = None,
             y_scale: float | None = None
):
    """
    Args:
        image_path (str): The local file path to the image (e.g., a transparent PNG).
            Pixels with over 50% opacity are converted into impenetrable concrete blocks in the LBM grid.
        center (Sequence[float]): The (x, y) spatial coordinates in world space where
            the exact center of the image will be anchored.
        x_scale (float, optional): The exact physical width the image should occupy in world space.
            If omitted, it scales proportionally based on the provided `y_scale` to preserve the original aspect ratio.
        y_scale (float, optional): The exact physical height the image should occupy in world space.
            If omitted, it scales proportionally based on the provided `x_scale`.

    Example:
        Importing a car profile as a solid wind tunnel obstacle:
        ```python
        import FluxRender as fr

        # The image will be exactly 3.0 units wide. Its height will automatically
        # adjust to maintain the original file's aspect ratio.
        car_profile = fr.ImageCollider(
            image_path="assets/sports_car_silhouette.png",
            center=(0.0, -2.0),
            x_scale=3.0
        )
        ```
    """

    super().__init__()

    self.image_path = image_path
    self.center = center
    self.image_data = None

    self.original_image = Image.open(self.image_path).convert("RGBA")

    self.aspect_ratio = self.original_image.width / self.original_image.height
    self.world_width_scale = 1
    self.world_height_scale = 1

    self.x_scale = x_scale
    self.y_scale = y_scale

    self.center_world_x = center[0]
    self.center_world_y = center[1]

    # Convert 0-255 RGB values to 0.0-1.0 floats required for graphical blending
    rgba_array = np.array(self.original_image, dtype=np.float32) / 255.0

    # Transpose from Pillow (Height, Width, 4) to Taichi Cartesian (Width, Height, 4)
    rgba_array = np.swapaxes(rgba_array, 0, 1)

    # Flip the Y-axis (Pillow originates top-left, Mathematics originates bottom-left)
    rgba_array = np.flip(rgba_array, axis=1)

    self.texture_width = rgba_array.shape[0]
    self.texture_height = rgba_array.shape[1]

    # Allocate 4-channel (RGBA) VRAM in Taichi and upload the NumPy array
    self.gpu_texture = ti.Vector.field(4, dtype=ti.f32, shape=(self.texture_width, self.texture_height))
    self.gpu_texture.from_numpy(rgba_array)

FluxRender.physics.EquationCollider

EquationCollider(equation_function: Callable[[float, float], bool], color: Sequence[float] = (1.0, 1.0, 1.0, 1.0))

Bases: Collider

A solid collider defined by a mathematical inequality.

It evaluates spatial coordinates (x, y) to generate solid boundaries for the fluid simulation based on the provided boolean function.

Parameters:

Name Type Description Default
equation_function Callable

A mathematical function taking (world_x, world_y) and returning a boolean. A return value of True indicates the spatial point is solid concrete; False indicates open, navigable fluid space.

required
color Sequence[float]

The RGBA color used to render the solid mask overlay on the visual screen.

(1.0, 1.0, 1.0, 1.0)
Example

Creating a simple circular pillar and a solid floor boundary:

import FluxRender as fr

# A solid circular pillar centered at (0, 0) with a radius of 1.5
pillar = fr.EquationCollider(
    equation_function=lambda x, y: (x**2 + y**2) <= 1.5**2,
)

Creating a flower shape using a polar equation:

import FluxRender as fr

def flower_equation(x, y):
    r = np.sqrt(x**2 + y**2)
    theta = np.arctan2(y, x)
    return r <= 0.5 + 0.4 * abs(np.sin(5*theta))

flower_collider = fr.EquationCollider(flower_equation)

Source code in FluxRender/physics.py
def __init__(self,
            equation_function: Callable[[float, float], bool],
            color: Sequence[float] = (1.0, 1.0, 1.0, 1.0)
    ):
    """
    Args:
        equation_function (Callable): A mathematical function taking `(world_x, world_y)`
            and returning a boolean. A return value of True indicates the spatial point is solid
            concrete; False indicates open, navigable fluid space.
        color (Sequence[float], optional): The RGBA color used to render the solid mask
            overlay on the visual screen.

    Example:
        Creating a simple circular pillar and a solid floor boundary:
        ```python
        import FluxRender as fr

        # A solid circular pillar centered at (0, 0) with a radius of 1.5
        pillar = fr.EquationCollider(
            equation_function=lambda x, y: (x**2 + y**2) <= 1.5**2,
        )
        ```

        Creating a flower shape using a polar equation:
        ```python
        import FluxRender as fr

        def flower_equation(x, y):
            r = np.sqrt(x**2 + y**2)
            theta = np.arctan2(y, x)
            return r <= 0.5 + 0.4 * abs(np.sin(5*theta))

        flower_collider = fr.EquationCollider(flower_equation)
        ```

    """
    super().__init__()

    self.scene = cr.get_scene()
    self.equation_function = equation_function
    self.color = color
    self._color_vector = ti.Vector(self.color)

    self._current_mask = ti.field(dtype=bool, shape=(self.scene.width, self.scene.height))

    self._last_camera_state = None