simvx.core.light2d¶

2D lighting nodes: Light2D, PointLight2D, DirectionalLight2D, LightOccluder2D.

Backend-agnostic light description nodes. The Vulkan backend renders these via Light2DPass (additive light accumulation with optional shadow casting).

Module Contents¶

Classes¶

Light2D

Base class for 2D lights.

PointLight2D

Radial point light with configurable falloff curve.

DirectionalLight2D

Global directional light that illuminates the entire scene.

LightOccluder2D

Shadow-casting obstacle defined by a convex polygon.

Functions¶

collect_lights

Walk the subtree and return all enabled Light2D nodes.

collect_occluders

Walk the subtree and return all LightOccluder2D nodes with non-empty polygons.

Data¶

API¶

simvx.core.light2d.log¶

‘getLogger(…)’

simvx.core.light2d.__all__¶

[‘DEFAULT_SHADOW_COLOUR’, ‘Light2D’, ‘PointLight2D’, ‘DirectionalLight2D’, ‘LightOccluder2D’]

simvx.core.light2d.DEFAULT_SHADOW_COLOUR¶

(0.0, 0.0, 0.0, 0.5)

class simvx.core.light2d.Light2D(**kwargs)[source]¶

Bases: simvx.core.nodes_2d.node2d.Node2D

Base class for 2D lights.

Lights contribute additive (or mixed) colour to a light accumulation buffer that modulates the final 2D scene output. Attach as children of any Node2D to have their position follow the parent.

Attributes: colour: RGB light colour, each component in [0, 1]. energy: Intensity multiplier applied to the light colour. range: Radius of the light in pixels. blend_mode: "add" for additive blending, "mix" for alpha-based mix with ambient. enabled: Toggle the light on/off without removing it. shadow_enabled: When True, occluders in the scene cast shadows for this light. shadow_colour: RGBA shadow parameters: a = shadow strength (0 = no darkening, 1 = fully replaced by the tint), rgb = residual tint inside full shadow. shadow_softness: PCF penumbra width for the shadow edge (0 = hard edge). texture_scale: Scale multiplier for the light texture/gradient.

Initialization

colour¶

‘Colour(…)’

energy¶

‘Property(…)’

range¶

‘Property(…)’

blend_mode¶

‘Property(…)’

enabled¶

‘Property(…)’

shadow_enabled¶

‘Property(…)’

shadow_colour¶

‘Colour(…)’

shadow_softness¶

‘Property(…)’

texture_scale¶

‘Property(…)’

light_cull_mask¶

‘Bitmask(…)’

set_light_cull_mask_layer(index: int, enabled: bool = True) None[source]¶

Enable or disable a specific light cull mask layer (0-31).

is_light_cull_mask_layer_enabled(index: int) bool[source]¶

Check if a specific light cull mask layer is enabled (0-31).

gizmo_colour¶

‘Colour(…)’

get_gizmo_lines() list[tuple[simvx.core.math.types.Vec2, simvx.core.math.types.Vec2]][source]¶

Return a circle representing the light radius plus a centre crosshair.

position¶

‘_SpatialVecProperty(…)’

rotation¶

‘Property(…)’

scale¶

‘_SpatialVecProperty(…)’

z_index¶

‘Property(…)’

z_as_relative¶

‘Property(…)’

render_layer¶

‘Property(…)’

set_render_layer(index: int, enabled: bool = True) None¶
is_on_render_layer(index: int) bool¶
property absolute_z_index: int¶
property rotation_degrees: float¶
property world_position: simvx.core.math.types.Vec2¶
property world_rotation: float¶
property world_scale: simvx.core.math.types.Vec2¶
property world_transform: tuple[simvx.core.math.types.Vec2, simvx.core.math.types.Vec2, float]¶
property forward: simvx.core.math.types.Vec2¶
property right: simvx.core.math.types.Vec2¶
translate(offset: tuple[float, float] | numpy.ndarray)¶
rotate(radians: float)¶
rotate_deg(degrees: float)¶
look_at(target: tuple[float, float] | numpy.ndarray)¶
transform_points(points: list[simvx.core.math.types.Vec2]) list[simvx.core.math.types.Vec2]¶
draw_polygon(renderer, points: list[simvx.core.math.types.Vec2], closed=True, colour=None)¶
wrap_screen(margin: float = 20)¶
hdr¶

‘Property(…)’

property transform_render_dirty: bool¶
strict_errors: ClassVar[bool]¶

True

dev_checks: ClassVar[bool]¶

None

script_error_raised¶

‘Signal(…)’

dynamic: bool¶

False

visible¶

‘Property(…)’

update_mode¶

‘Property(…)’

__properties__: ClassVar[dict[str, simvx.core.descriptors.Property]]¶

None

classmethod __init_subclass__(**kwargs)¶
property name: str¶
property visible_in_tree: bool¶
reset_error() None¶
add_child(node: simvx.core.node.T) simvx.core.node.T¶
remove_child(node: simvx.core.node.Node) None¶
reparent(new_parent: simvx.core.node.Node)¶
node_at(path, default=_NO_DEFAULT)¶
find(target, *, direct: bool = False)¶
find_all(target, *, direct: bool = False)¶
expect(target, *, direct: bool = False)¶
ancestor(target)¶
walk(*, include_self: bool = True) collections.abc.Iterator[simvx.core.node.Node]¶
property path: str¶
property is_scene_root: bool¶
add_to_group(group: str)¶
remove_from_group(group: str)¶
is_in_group(group: str) bool¶
on_ready() None¶
on_enter_tree() None¶
on_exit_tree() None¶
on_update(dt: float) None¶
on_fixed_update(dt: float) None¶
on_draw(renderer) None¶
on_picked(event: simvx.core.events.InputEvent) None¶
on_unhandled_input(event: simvx.core.events.TreeInputEvent) None¶
start_coroutine(gen: simvx.core.descriptors.Coroutine) simvx.core.descriptors.CoroutineHandle¶
stop_coroutine(gen_or_handle)¶
queue_redraw() None¶
property render_dirty: bool¶
clear_children()¶
destroy()¶
property destroying: bool¶
call_deferred(method: collections.abc.Callable[..., Any], *args: Any) None¶
property app¶
property tree: simvx.core.scene_tree.SceneTree¶
property physics¶
property physics_2d¶
__getitem__(key: str)¶
classmethod get_properties() dict[str, simvx.core.descriptors.Property]¶
__repr__()¶
class simvx.core.light2d.PointLight2D(**kwargs)[source]¶

Bases: simvx.core.light2d.Light2D

Radial point light with configurable falloff curve.

The falloff exponent controls the attenuation shape:

  • 1.0 = linear falloff (default)

  • 2.0 = quadratic (more concentrated center)

  • 0.5 = square-root (softer, wider spread)

inner_radius (in pixels) carves a flat full-brightness core: the attenuation curve runs from the inner edge out to range instead of from the centre, so the light reads as a solid disk that fades only past inner_radius. 0 (default) reproduces a plain centre-peaked light.

falloff_gradient overrides the analytic curve with a 1-D intensity LUT. Contract: a numpy float array of shape (N,) or (N, 1) with values in [0, 1] (N >= 1). It is sampled by the remapped normalised distance: index 0 is the inner edge / centre (full brightness end), index N-1 is the outer range edge. The sampled value replaces pow(1 - d_norm, falloff) (falloff is ignored while a gradient is set). None (default) keeps the analytic curve. Greyscale intensity only: colour gradients and sprite-shaped (2-D) light cookies are a future extension.

Example::

light = PointLight2D(
    colour=(1.0, 0.8, 0.3),
    energy=1.5,
    range=300.0,
    falloff=2.0,
    inner_radius=60.0,
    position=Vec2(400, 300),
)

Initialization

falloff¶

‘Property(…)’

inner_radius¶

‘Property(…)’

falloff_gradient¶

‘Property(…)’

colour¶

‘Colour(…)’

energy¶

‘Property(…)’

range¶

‘Property(…)’

blend_mode¶

‘Property(…)’

enabled¶

‘Property(…)’

shadow_enabled¶

‘Property(…)’

shadow_colour¶

‘Colour(…)’

shadow_softness¶

‘Property(…)’

texture_scale¶

‘Property(…)’

light_cull_mask¶

‘Bitmask(…)’

set_light_cull_mask_layer(index: int, enabled: bool = True) None¶
is_light_cull_mask_layer_enabled(index: int) bool¶
gizmo_colour¶

‘Colour(…)’

get_gizmo_lines() list[tuple[simvx.core.math.types.Vec2, simvx.core.math.types.Vec2]]¶
position¶

‘_SpatialVecProperty(…)’

rotation¶

‘Property(…)’

scale¶

‘_SpatialVecProperty(…)’

z_index¶

‘Property(…)’

z_as_relative¶

‘Property(…)’

render_layer¶

‘Property(…)’

set_render_layer(index: int, enabled: bool = True) None¶
is_on_render_layer(index: int) bool¶
property absolute_z_index: int¶
property rotation_degrees: float¶
property world_position: simvx.core.math.types.Vec2¶
property world_rotation: float¶
property world_scale: simvx.core.math.types.Vec2¶
property world_transform: tuple[simvx.core.math.types.Vec2, simvx.core.math.types.Vec2, float]¶
property forward: simvx.core.math.types.Vec2¶
property right: simvx.core.math.types.Vec2¶
translate(offset: tuple[float, float] | numpy.ndarray)¶
rotate(radians: float)¶
rotate_deg(degrees: float)¶
look_at(target: tuple[float, float] | numpy.ndarray)¶
transform_points(points: list[simvx.core.math.types.Vec2]) list[simvx.core.math.types.Vec2]¶
draw_polygon(renderer, points: list[simvx.core.math.types.Vec2], closed=True, colour=None)¶
wrap_screen(margin: float = 20)¶
hdr¶

‘Property(…)’

property transform_render_dirty: bool¶
strict_errors: ClassVar[bool]¶

True

dev_checks: ClassVar[bool]¶

None

script_error_raised¶

‘Signal(…)’

dynamic: bool¶

False

visible¶

‘Property(…)’

update_mode¶

‘Property(…)’

__properties__: ClassVar[dict[str, simvx.core.descriptors.Property]]¶

None

classmethod __init_subclass__(**kwargs)¶
property name: str¶
property visible_in_tree: bool¶
reset_error() None¶
add_child(node: simvx.core.node.T) simvx.core.node.T¶
remove_child(node: simvx.core.node.Node) None¶
reparent(new_parent: simvx.core.node.Node)¶
node_at(path, default=_NO_DEFAULT)¶
find(target, *, direct: bool = False)¶
find_all(target, *, direct: bool = False)¶
expect(target, *, direct: bool = False)¶
ancestor(target)¶
walk(*, include_self: bool = True) collections.abc.Iterator[simvx.core.node.Node]¶
property path: str¶
property is_scene_root: bool¶
add_to_group(group: str)¶
remove_from_group(group: str)¶
is_in_group(group: str) bool¶
on_ready() None¶
on_enter_tree() None¶
on_exit_tree() None¶
on_update(dt: float) None¶
on_fixed_update(dt: float) None¶
on_draw(renderer) None¶
on_picked(event: simvx.core.events.InputEvent) None¶
on_unhandled_input(event: simvx.core.events.TreeInputEvent) None¶
start_coroutine(gen: simvx.core.descriptors.Coroutine) simvx.core.descriptors.CoroutineHandle¶
stop_coroutine(gen_or_handle)¶
queue_redraw() None¶
property render_dirty: bool¶
clear_children()¶
destroy()¶
property destroying: bool¶
call_deferred(method: collections.abc.Callable[..., Any], *args: Any) None¶
property app¶
property tree: simvx.core.scene_tree.SceneTree¶
property physics¶
property physics_2d¶
__getitem__(key: str)¶
classmethod get_properties() dict[str, simvx.core.descriptors.Property]¶
__repr__()¶
class simvx.core.light2d.DirectionalLight2D(**kwargs)[source]¶

Bases: simvx.core.light2d.Light2D

Global directional light that illuminates the entire scene.

Unlike point lights, directional lights have no position-based attenuation. The direction vector determines the light angle for shadow casting (if enabled). The range setting is ignored for illumination but still used to size the shadow map.

Example::

sun = DirectionalLight2D(
    direction=(0.5, -1.0),
    colour=(1.0, 1.0, 0.9),
    energy=0.8,
)

Initialization

direction¶

‘Property(…)’

colour¶

‘Colour(…)’

energy¶

‘Property(…)’

range¶

‘Property(…)’

blend_mode¶

‘Property(…)’

enabled¶

‘Property(…)’

shadow_enabled¶

‘Property(…)’

shadow_colour¶

‘Colour(…)’

shadow_softness¶

‘Property(…)’

texture_scale¶

‘Property(…)’

light_cull_mask¶

‘Bitmask(…)’

set_light_cull_mask_layer(index: int, enabled: bool = True) None¶
is_light_cull_mask_layer_enabled(index: int) bool¶
gizmo_colour¶

‘Colour(…)’

get_gizmo_lines() list[tuple[simvx.core.math.types.Vec2, simvx.core.math.types.Vec2]]¶
position¶

‘_SpatialVecProperty(…)’

rotation¶

‘Property(…)’

scale¶

‘_SpatialVecProperty(…)’

z_index¶

‘Property(…)’

z_as_relative¶

‘Property(…)’

render_layer¶

‘Property(…)’

set_render_layer(index: int, enabled: bool = True) None¶
is_on_render_layer(index: int) bool¶
property absolute_z_index: int¶
property rotation_degrees: float¶
property world_position: simvx.core.math.types.Vec2¶
property world_rotation: float¶
property world_scale: simvx.core.math.types.Vec2¶
property world_transform: tuple[simvx.core.math.types.Vec2, simvx.core.math.types.Vec2, float]¶
property forward: simvx.core.math.types.Vec2¶
property right: simvx.core.math.types.Vec2¶
translate(offset: tuple[float, float] | numpy.ndarray)¶
rotate(radians: float)¶
rotate_deg(degrees: float)¶
look_at(target: tuple[float, float] | numpy.ndarray)¶
transform_points(points: list[simvx.core.math.types.Vec2]) list[simvx.core.math.types.Vec2]¶
draw_polygon(renderer, points: list[simvx.core.math.types.Vec2], closed=True, colour=None)¶
wrap_screen(margin: float = 20)¶
hdr¶

‘Property(…)’

property transform_render_dirty: bool¶
strict_errors: ClassVar[bool]¶

True

dev_checks: ClassVar[bool]¶

None

script_error_raised¶

‘Signal(…)’

dynamic: bool¶

False

visible¶

‘Property(…)’

update_mode¶

‘Property(…)’

__properties__: ClassVar[dict[str, simvx.core.descriptors.Property]]¶

None

classmethod __init_subclass__(**kwargs)¶
property name: str¶
property visible_in_tree: bool¶
reset_error() None¶
add_child(node: simvx.core.node.T) simvx.core.node.T¶
remove_child(node: simvx.core.node.Node) None¶
reparent(new_parent: simvx.core.node.Node)¶
node_at(path, default=_NO_DEFAULT)¶
find(target, *, direct: bool = False)¶
find_all(target, *, direct: bool = False)¶
expect(target, *, direct: bool = False)¶
ancestor(target)¶
walk(*, include_self: bool = True) collections.abc.Iterator[simvx.core.node.Node]¶
property path: str¶
property is_scene_root: bool¶
add_to_group(group: str)¶
remove_from_group(group: str)¶
is_in_group(group: str) bool¶
on_ready() None¶
on_enter_tree() None¶
on_exit_tree() None¶
on_update(dt: float) None¶
on_fixed_update(dt: float) None¶
on_draw(renderer) None¶
on_picked(event: simvx.core.events.InputEvent) None¶
on_unhandled_input(event: simvx.core.events.TreeInputEvent) None¶
start_coroutine(gen: simvx.core.descriptors.Coroutine) simvx.core.descriptors.CoroutineHandle¶
stop_coroutine(gen_or_handle)¶
queue_redraw() None¶
property render_dirty: bool¶
clear_children()¶
destroy()¶
property destroying: bool¶
call_deferred(method: collections.abc.Callable[..., Any], *args: Any) None¶
property app¶
property tree: simvx.core.scene_tree.SceneTree¶
property physics¶
property physics_2d¶
__getitem__(key: str)¶
classmethod get_properties() dict[str, simvx.core.descriptors.Property]¶
__repr__()¶
class simvx.core.light2d.LightOccluder2D(**kwargs)[source]¶

Bases: simvx.core.nodes_2d.node2d.Node2D

Shadow-casting obstacle defined by a convex polygon.

The polygon attribute is a sequence of (x, y) tuples defining the occluder shape in local coordinates. The occluder follows its parent’s transform (position, rotation, scale).

When one_way is True, shadows are only cast in the direction of the polygon’s winding normal.

Example::

wall = LightOccluder2D(
    polygon=[(-50, -10), (50, -10), (50, 10), (-50, 10)],
    position=Vec2(400, 400),
)

Initialization

polygon¶

‘Property(…)’

one_way¶

‘Property(…)’

gizmo_colour¶

‘Colour(…)’

property global_polygon: list[tuple[float, float]][source]¶

Polygon vertices transformed to global coordinates.

Applies the node’s global position, rotation, and scale to each vertex in the polygon.

property edge_segments: list[tuple[tuple[float, float], tuple[float, float]]][source]¶

Edge segments ((x0, y0), (x1, y1)) in global space.

get_gizmo_lines() list[tuple[simvx.core.math.types.Vec2, simvx.core.math.types.Vec2]][source]¶

Return the occluder polygon outline as line segments in world space.

position¶

‘_SpatialVecProperty(…)’

rotation¶

‘Property(…)’

scale¶

‘_SpatialVecProperty(…)’

z_index¶

‘Property(…)’

z_as_relative¶

‘Property(…)’

render_layer¶

‘Property(…)’

set_render_layer(index: int, enabled: bool = True) None¶
is_on_render_layer(index: int) bool¶
property absolute_z_index: int¶
property rotation_degrees: float¶
property world_position: simvx.core.math.types.Vec2¶
property world_rotation: float¶
property world_scale: simvx.core.math.types.Vec2¶
property world_transform: tuple[simvx.core.math.types.Vec2, simvx.core.math.types.Vec2, float]¶
property forward: simvx.core.math.types.Vec2¶
property right: simvx.core.math.types.Vec2¶
translate(offset: tuple[float, float] | numpy.ndarray)¶
rotate(radians: float)¶
rotate_deg(degrees: float)¶
look_at(target: tuple[float, float] | numpy.ndarray)¶
transform_points(points: list[simvx.core.math.types.Vec2]) list[simvx.core.math.types.Vec2]¶
draw_polygon(renderer, points: list[simvx.core.math.types.Vec2], closed=True, colour=None)¶
wrap_screen(margin: float = 20)¶
hdr¶

‘Property(…)’

property transform_render_dirty: bool¶
strict_errors: ClassVar[bool]¶

True

dev_checks: ClassVar[bool]¶

None

script_error_raised¶

‘Signal(…)’

dynamic: bool¶

False

visible¶

‘Property(…)’

update_mode¶

‘Property(…)’

__properties__: ClassVar[dict[str, simvx.core.descriptors.Property]]¶

None

classmethod __init_subclass__(**kwargs)¶
property name: str¶
property visible_in_tree: bool¶
reset_error() None¶
add_child(node: simvx.core.node.T) simvx.core.node.T¶
remove_child(node: simvx.core.node.Node) None¶
reparent(new_parent: simvx.core.node.Node)¶
node_at(path, default=_NO_DEFAULT)¶
find(target, *, direct: bool = False)¶
find_all(target, *, direct: bool = False)¶
expect(target, *, direct: bool = False)¶
ancestor(target)¶
walk(*, include_self: bool = True) collections.abc.Iterator[simvx.core.node.Node]¶
property path: str¶
property is_scene_root: bool¶
add_to_group(group: str)¶
remove_from_group(group: str)¶
is_in_group(group: str) bool¶
on_ready() None¶
on_enter_tree() None¶
on_exit_tree() None¶
on_update(dt: float) None¶
on_fixed_update(dt: float) None¶
on_draw(renderer) None¶
on_picked(event: simvx.core.events.InputEvent) None¶
on_unhandled_input(event: simvx.core.events.TreeInputEvent) None¶
start_coroutine(gen: simvx.core.descriptors.Coroutine) simvx.core.descriptors.CoroutineHandle¶
stop_coroutine(gen_or_handle)¶
queue_redraw() None¶
property render_dirty: bool¶
clear_children()¶
destroy()¶
property destroying: bool¶
call_deferred(method: collections.abc.Callable[..., Any], *args: Any) None¶
property app¶
property tree: simvx.core.scene_tree.SceneTree¶
property physics¶
property physics_2d¶
__getitem__(key: str)¶
classmethod get_properties() dict[str, simvx.core.descriptors.Property]¶
__repr__()¶
simvx.core.light2d.collect_lights(root: simvx.core.nodes_2d.node2d.Node2D) list[simvx.core.light2d.Light2D][source]¶

Walk the subtree and return all enabled Light2D nodes.

simvx.core.light2d.collect_occluders(root: simvx.core.nodes_2d.node2d.Node2D) list[simvx.core.light2d.LightOccluder2D][source]¶

Walk the subtree and return all LightOccluder2D nodes with non-empty polygons.