Skip to content

Scene

FluxRender.core.Scene

Scene(name: str, coords: CoordinateSystem, background_color: Sequence[float] = (0.101, 0.105, 0.149), trail_fade_factor: float = 0.95)

Manages the main rendering loop, graphics layers, and object physics.

This class implements a rendering pipeline based on two distinct layers: a trail layer for fading effects and a final layer for crisp overlays. It handles alpha composition, physics updates, and the Taichi GUI window.

Advanced Usage: Custom Frame Hook

While FluxRender is designed to handle UI and rendering autonomously without requiring manual loop management, advanced users can inject custom logic to be executed every frame.

If a global or passed function named update() is defined, the Scene will detect it and call it exactly once per frame, just before the rendering phase. This is ideal for custom animations, complex state machines, or physical simulations.

Parameters:

Name Type Description Default
name str

Title of the window.

required
coords CoordinateSystem

Coordinate system

required
background_color Sequence[float]

Background color in RGB format (0.0 - 1.0). Defaults to dark navy.

(0.101, 0.105, 0.149)
trail_fade_factor float

Decay factor for the trail layer (0.0 - 1.0). A value of 0.95 means the trails will fade by 5% each frame. Defaults to 0.95.

0.95
Example

Basic initialization:

import FluxRender as fr

coords = fr.CoordinateSystem((-10, 10), (-10, 10), 1200, 800, keep_aspect_ratio=True)

scene = fr.Scene(
    "My Simulation",
    coords,
    background_color=(0.208, 0.122, 0.361), # Dark purple background color in RGB format (0.0 - 1.0).
    trail_fade_factor=0.98 # Trails will fade by 2% each frame, creating a longer-lasting trail effect.
)

# [Define objects and add them to the scene here]

scene.run()

Source code in FluxRender/core.py
def __init__(self,
             name: str,
             coords: CoordinateSystem,
             background_color: Sequence[float] = (0.101, 0.105, 0.149),
             trail_fade_factor: float = 0.95
            ):
    """
    Args:
        name (str): Title of the window.
        coords (CoordinateSystem): Coordinate system
        background_color (Sequence[float]): Background color in RGB format (0.0 - 1.0).
            Defaults to dark navy.
        trail_fade_factor (float): Decay factor for the trail layer (0.0 - 1.0).
            A value of 0.95 means the trails will fade by 5% each frame. Defaults to 0.95.

    Example:
        Basic initialization:
        ```python
        import FluxRender as fr

        coords = fr.CoordinateSystem((-10, 10), (-10, 10), 1200, 800, keep_aspect_ratio=True)

        scene = fr.Scene(
            "My Simulation",
            coords,
            background_color=(0.208, 0.122, 0.361), # Dark purple background color in RGB format (0.0 - 1.0).
            trail_fade_factor=0.98 # Trails will fade by 2% each frame, creating a longer-lasting trail effect.
        )

        # [Define objects and add them to the scene here]

        scene.run()
        ```
    """
    self._has_run = False

    self.coords = coords
    self.width = coords.width
    self.height = coords.height
    self.background_color = background_color
    self.trail_fade_factor = trail_fade_factor
    self.name = name
    self.window = ti.ui.Window(name, res=(self.width, self.height), vsync=False)
    self.canvas = self.window.get_canvas()

    self.trail_layer = ti.Vector.field(4, dtype=float, shape=(self.width, self.height))
    self.scene_layer = ti.Vector.field(4, dtype=float, shape=(self.width, self.height))
    self.ui_layer = ti.Vector.field(4, dtype=float, shape=(self.width, self.height))
    self.ui_layer.fill(0)
    self.pixels = ti.Vector.field(3, dtype=float, shape=(self.width, self.height))

    self.zoom_speed = 0.02

    self._use_trails = False

    self.objects = []

    self.time = 0.0
    self.dt = 0.005

    atexit.register(self._warn_if_not_run)

add

add(*args)

Adds a renderable object to the scene.

The object must implement the render(scene) and update(scene) methods.

Parameters:

Name Type Description Default
*args

One or more objects to be added to the scene. Each object must have render(scene) and update(scene) methods. Optionally, they can have coords and scene attributes, which will be set to the current scene's coordinate system and the scene itself if not already set.

()
Notes
  • Order of rendering: Objects are rendered in the order they are added. The first object added will be rendered first (at the bottom layer), and the last object will be rendered last (on top). For example, if you added an Axis first and then a VectorField, the vector field will partially cover the coordinate axes.
Source code in FluxRender/core.py
def add(self, *args):
    """
    Adds a renderable object to the scene.

    The object must implement the `render(scene)` and `update(scene)` methods.

    Args:
        *args: One or more objects to be added to the scene. Each object must have
            render(scene) and update(scene) methods. Optionally, they can have
            coords and scene attributes, which will be set to the current scene's
            coordinate system and the scene itself if not already set.

    Notes:
        * **Order of rendering**: Objects are rendered in the order they are added. The first object added will be rendered first (at the bottom layer), and the last object will be rendered last (on top).
        For example, if you added an Axis first and then a VectorField, the vector field will partially cover the coordinate axes.
    """

    for new_object in args:
        self._process_addition(new_object)
        if hasattr(new_object, '_init'):
            new_object._init(self)

run

run()

Starts the main application loop.

Performs the following actions on each frame: - Updates the physics and renders all objects. - Combines the layers into a single image and displays it. - Calls the update() function on each frame if the user has declared it. - Handles mouse and keyboard clicks. - Gets the mouse cursor position.

Source code in FluxRender/core.py
def run(self):
    """
    Starts the main application loop.

    Performs the following actions on each frame:
    - Updates the physics and renders all objects.
    - Combines the layers into a single image and displays it.
    - Calls the update() function on each frame if the user has declared it.
    - Handles mouse and keyboard clicks.
    - Gets the mouse cursor position.
    """

    self._has_run = True

    window = self.window
    coords = self.coords
    canvas = self.canvas

    zoom_speed = self.zoom_speed

    last_mouse_pos_x = 0
    last_mouse_pos_y = 0

    LMB = ti.ui.LMB
    RMB = ti.ui.RMB

    # Checking if the user has declared an update() function (function executed in every frame)
    caller_frame = inspect.currentframe().f_back
    user_update_func = caller_frame.f_locals.get('update')
    if not callable(user_update_func):
        user_update_func = None

    while window.running:
        # if self._use_trails:
        self._fade_trails(self.trail_fade_factor)

        self.scene_layer.fill(0) # Completely clear scene_layer

        # Handling user interactions (e.g., mouse clicks)
        mx, my = window.get_cursor_pos()
        self.mouse_pos = (mx*self.width, my*self.height)

        self.is_lmb_pressed = window.is_pressed(LMB)
        self.is_rmb_pressed = window.is_pressed(RMB)


        # Support for panning and zooming the coordinate system
        if self.is_rmb_pressed:
            if self.was_rmb_pressed:
                dx = mx - last_mouse_pos_x
                dy = my - last_mouse_pos_y
                coords.move(dx, dy)

            last_mouse_pos_x = mx
            last_mouse_pos_y = my

        self.was_rmb_pressed = self.is_rmb_pressed

        if window.is_pressed("e"):
            coords.zoom(1-zoom_speed, (mx, my))
        elif window.is_pressed('q'):
            coords.zoom(1+zoom_speed, (mx, my))


        # Basic support for updating and rendering objects
        for obj in self.objects:
            obj.render(self)
            obj.update(self)


        # User update() function executed every frame
        if user_update_func:
            user_update_func()

        self.time += self.dt
        self._compose_layers()
        canvas.set_image(self.pixels)
        window.show()