simvx.core.nodes_3d.camera

Camera3D, OrbitCamera3D – 3D camera nodes.

Module Contents

Classes

Camera3D

3D camera providing view and projection matrices.

OrbitCamera3D

Camera that orbits a pivot point, for editors, inspectors and strategy views.

Data

API

simvx.core.nodes_3d.camera.ORBIT_DRAG

‘orbit_camera.drag’

simvx.core.nodes_3d.camera.ORBIT_STICK

‘orbit_camera.orbit’

simvx.core.nodes_3d.camera.ORBIT_ZOOM

‘orbit_camera.zoom’

simvx.core.nodes_3d.camera.ORBIT_PAN

‘orbit_camera.pan’

simvx.core.nodes_3d.camera.ORBIT_CAMERA_ACTIONS

()

class simvx.core.nodes_3d.camera.Camera3D(look_at=None, up=None, **kwargs)[source]

Bases: simvx.core.nodes_3d.node3d.Node3D

3D camera providing view and projection matrices.

The first Camera3D found in the scene tree is used by the renderer. Position and orientation are inherited from Node3D; the camera adds projection parameters (field of view, clip planes).

Conventions (full reference: docs/graphics/cameras.md):

  • Right-handed, Y-up.

  • Forward = local -Z. camera.forward is world_rotation * (0, 0, -1). Matches Godot / glTF / OpenGL; clashes with Unity / Unreal (+Z forward): assets ported from those engines must negate the forward vector once at import.

  • look_at(target) and :meth:~Node3D.face_along both align the local -Z axis with the supplied direction.

  • Internal angles in radians; fov is stored in degrees only as an inspector convenience.

  • Matrices are row-major; the renderer transposes at the GPU boundary and applies a Vulkan-only Y-flip in

    meth:

    projection_matrix.

Attributes: projection: "perspective" (default) or "orthographic". Selects which of the two parameter sets below builds :meth:projection_matrix; the other set is ignored but retained, so toggling back and forth is lossless. fov: Vertical field of view in degrees (1 – 179). Perspective only. ortho_size: Full vertical height of the orthographic view volume in world units, matching Godot’s Camera3D.size: at 20.0 the camera sees 10 units above and 10 below its centre line, and the horizontal extent follows from the aspect ratio. Orthographic only. near: Near clip plane distance. far: Far clip plane distance. clip_plane: Optional world-space oblique near-clip plane (nx, ny, nz, d) with equation n . x + d = 0; geometry on the n . x + d > 0 side is kept. None (default) keeps the standard near plane. Set per frame by :func:~simvx.core.planar_reflection_camera so a mirrored camera never renders geometry behind the reflection plane (Lengyel oblique-frustum technique). A plain runtime attribute, not a serialised Property: it is a derived, per-frame value.

Example::

camera = Camera3D(position=(0, 5, 10), look_at=(0, 0, 0), fov=75.0)

isometric = Camera3D(
    position=(10, 10, 10),
    look_at=(0, 0, 0),
    projection="orthographic",
    ortho_size=16.0,
)

Initialization

projection

‘Property(…)’

fov

‘Property(…)’

ortho_size

‘Property(…)’

near

‘Property(…)’

far

‘Property(…)’

exposure

‘Property(…)’

cull_mask

‘Bitmask(…)’

gizmo_colour

‘Colour(…)’

on_enter_tree()[source]
set_cull_mask_layer(index: int, enabled: bool = True) → None[source]

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

is_cull_mask_layer_enabled(index: int) → bool[source]

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

get_gizmo_lines() → list[tuple[simvx.core.math.types.Vec3, simvx.core.math.types.Vec3]][source]

Return view-volume wireframe lines: a frustum, or a box when orthographic.

property view_matrix: numpy.ndarray[source]

View matrix computed from this node’s global transform.

Cached: the matrix is rebuilt only after the camera’s transform changes (its own or an ancestor’s), so projecting many points per frame pays for one build. Each read returns a fresh copy that the caller may mutate freely.

Returns: 4x4 view matrix as numpy array (row-major)

projection_matrix(aspect: float = 16 / 9) → numpy.ndarray[source]

Projection matrix for the given aspect ratio (Vulkan clip space).

Built from :attr:fov when :attr:projection is "perspective" and from :attr:ortho_size when it is "orthographic". In both cases the vertical extent is the one held fixed and the horizontal extent follows from aspect, so widening the window reveals more of the scene rather than stretching it.

Cached: the matrix is rebuilt only when :attr:projection,

Attr:

fov, :attr:ortho_size, :attr:near, :attr:far or aspect differ from the previous call. Each read returns a fresh array that the caller may mutate freely. An oblique :attr:clip_plane is applied on top of the cached base per call, since it and the view matrix it depends on change per frame.

Args: aspect: Aspect ratio (width / height)

Returns: 4x4 projection matrix as numpy array (row-major) Includes Y-flip for Vulkan rendering

ray_at(screen_pos) → simvx.core.math.raycast.Ray[source]

The world-space ray through screen_pos, a point in this camera’s view.

screen_pos is in the units of :attr:viewport_size, (0, 0) at the top left, so Input.mouse_position passes straight in. The ray starts on the near plane; under an orthographic projection every ray is parallel to :attr:forward.

world_to_screen(point) → simvx.core.math.types.Vec2 | None[source]

Where the world-space point lands in this camera’s view, or None.

The inverse of :meth:ray_at, in the same units. A point outside the view volume (off to one side, or past the far plane) still returns its position, off screen, which is what an edge indicator wants. A point level with or behind the camera has no position on screen and returns None.

position

‘_SpatialVecProperty(…)’

rotation

‘Property(…)’

scale

‘_SpatialVecProperty(…)’

render_layer

‘Property(…)’

property rotation_degrees: simvx.core.math.types.Vec3
property world_position: simvx.core.math.types.Vec3
property world_rotation: simvx.core.math.types.Quat
property world_scale: simvx.core.math.types.Vec3
property forward: simvx.core.math.types.Vec3
property right: simvx.core.math.types.Vec3
property up: simvx.core.math.types.Vec3
translate(offset: tuple[float, float, float] | numpy.ndarray)
translate_global(offset: tuple[float, float, float] | numpy.ndarray)
rotate(axis: tuple[float, float, float] | numpy.ndarray, angle: float)
rotate_x(angle: float)
rotate_y(angle: float)
rotate_z(angle: float)
look_at(target: tuple[float, float, float] | numpy.ndarray, up=None)
face_along(forward: tuple[float, float, float] | numpy.ndarray, up: tuple[float, float, float] | numpy.ndarray | None = None) → None
set_render_layer(index: int, enabled: bool = True) → None
is_on_render_layer(index: int) → bool
wrap_bounds(bounds: tuple[float, float, float] | numpy.ndarray, margin: float = 1.0)
strict_errors: ClassVar[bool]

True

dev_checks: ClassVar[bool]

None

dev_warnings: ClassVar[bool]

None

freeze_check_interval: ClassVar[float]

1.0

dynamic: bool

False

visible

‘Property(…)’

update_mode

‘Property(…)’

focus_mode

‘Property(…)’

drag_threshold

‘Property(…)’

focus_entered

‘Signal(…)’

focus_exited

‘Signal(…)’

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

None

classmethod __init_subclass__(**kwargs)
property name: str
property visible_in_tree: bool
property input_player: simvx.core.input.players.InputPlayer
property input_context: simvx.core.input.contexts.InputContext | None
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_exit_tree() → None
on_update(dt: float) → None
on_fixed_update(dt: float) → None
on_draw(canvas) → None
on_draw_3d(gizmos) → None
on_picked(event: simvx.core.input.events.PickEvent) → bool | None
on_gui_input(event: simvx.core.input.events.GuiInputEvent) → bool | None
start_coroutine(gen: simvx.core.descriptors.Coroutine) → simvx.core.descriptors.CoroutineHandle
stop_coroutine(gen_or_handle)
property focused: bool
property focus_rect: tuple[float, float, float, float] | None
drag_threshold_for(pointer_type: simvx.core.input.enums.PointerType | str) → float
grab_focus() → None
release_focus() → None
capture_pointer(pointer_id: int | None = None) → None
release_pointer(pointer_id: int | None = None) → None
queue_redraw() → None
property render_dirty: bool
clear_children()
destroy()
property destroying: bool
property destroyed: bool
call_deferred(method: collections.abc.Callable[..., Any], *args: Any) → None
property app
property tree: simvx.core.scene_tree.SceneTree
property viewport_size: simvx.core.math.types.Vec2
property viewport_insets: tuple[float, float, float, float]
property physics
property physics_2d
__getitem__(key: str)
classmethod get_properties() → dict[str, simvx.core.descriptors.Property]
__repr__()
class simvx.core.nodes_3d.camera.OrbitCamera3D(**kwargs)[source]

Bases: simvx.core.nodes_3d.camera.Camera3D

Camera that orbits a pivot point, for editors, inspectors and strategy views.

The camera’s position and orientation are derived: assigning

Attr:

pivot, :attr:distance, :attr:yaw or :attr:pitch, from code, a scene file or the inspector, recomputes them at once, and the camera always looks at the pivot. Assigning position directly is therefore pointless, since the next orbit change overwrites it: move :attr:pivot instead. Assign the pivot whole (cam.pivot = target or cam.pivot += offset); writing one component in place (cam.pivot.x = 3) is not seen.

It takes its own input. Adding the node is the whole of it: dragging with the primary pointer (a left click, a pen tip, one finger) or pushing the controller’s right stick orbits, a two-finger pinch, the mouse wheel and the triggers zoom (one action, in the pinch’s unit, each source scaled into it by its binding), and with :attr:pan_enabled a secondary or middle drag pans. The bindings are the camera’s own input_context, under the names in

Data:

ORBIT_CAMERA_ACTIONS, namespaced so they never take a game’s names. A player changes one with InputMap.rebind("orbit_camera.drag", Mouse.RIGHT); a game replaces the set by assigning input_context, and turns the camera’s input off with :attr:interactive.

A press a UI control claimed never reaches the camera, so dragging a slider does not also orbit. The camera hears input exactly while it can receive it: a paused or disabled camera stands still, and a context that hides everything below it (a pause menu) takes the camera’s names too. Two orbit cameras in one tree both answer; set :attr:interactive on the one that is not being looked through.

Meth:

orbit, :meth:pan and :meth:zoom are the controls the input drives, and a game calls them too (an auto-orbit, a scripted fly-in).

Angles are radians, like everywhere else in the engine. Yaw sweeps around the world Y axis; negative pitch places the camera above the pivot looking down, which is why the default view is a raised three-quarter angle. Every assignment clamps pitch just short of the poles, where the view direction would be undefined, and distance to at least 0.01.

Meth:

orbit further clamps pitch to :attr:min_pitch and

Attr:

max_pitch, and :meth:zoom distance to :attr:min_distance and

Attr:

max_distance; those are the player’s limits, and direct assignment is not held to them.

Example::

cam = OrbitCamera3D(pivot=Vec3(0, 1, 0), distance=12.0, max_distance=40.0)
cam.orbit(math.radians(15.0), 0.0)   # sweep 15 degrees to the side
cam.zoom(2.0)                        # move 2 units closer

Initialization

input_context: simvx.core.input.contexts.InputContext | None

‘InputContext(…)’

pivot

‘Property(…)’

distance

‘Property(…)’

yaw

‘Property(…)’

pitch

‘Property(…)’

min_distance

‘Property(…)’

max_distance

‘Property(…)’

min_pitch

‘Property(…)’

max_pitch

‘Property(…)’

interactive

‘Property(…)’

drag_sensitivity

‘Property(…)’

stick_speed

‘Property(…)’

pan_enabled

‘Property(…)’

pan_speed

‘Property(…)’

invert_x

‘Property(…)’

invert_y

‘Property(…)’

orbit(dyaw: float, dpitch: float)[source]

Orbit the camera around its pivot point by yaw and pitch deltas (radians).

pan(dx: float, dz: float)[source]

Pan the camera pivot horizontally in the XZ plane.

zoom(delta: float)[source]

Zoom the camera by adjusting its distance to the pivot: positive moves closer.

projection

‘Property(…)’

fov

‘Property(…)’

ortho_size

‘Property(…)’

near

‘Property(…)’

far

‘Property(…)’

exposure

‘Property(…)’

cull_mask

‘Bitmask(…)’

gizmo_colour

‘Colour(…)’

on_enter_tree()
set_cull_mask_layer(index: int, enabled: bool = True) → None
is_cull_mask_layer_enabled(index: int) → bool
get_gizmo_lines() → list[tuple[simvx.core.math.types.Vec3, simvx.core.math.types.Vec3]]
property view_matrix: numpy.ndarray
projection_matrix(aspect: float = 16 / 9) → numpy.ndarray
ray_at(screen_pos) → simvx.core.math.raycast.Ray
world_to_screen(point) → simvx.core.math.types.Vec2 | None
position

‘_SpatialVecProperty(…)’

rotation

‘Property(…)’

scale

‘_SpatialVecProperty(…)’

render_layer

‘Property(…)’

property rotation_degrees: simvx.core.math.types.Vec3
property world_position: simvx.core.math.types.Vec3
property world_rotation: simvx.core.math.types.Quat
property world_scale: simvx.core.math.types.Vec3
property forward: simvx.core.math.types.Vec3
property right: simvx.core.math.types.Vec3
property up: simvx.core.math.types.Vec3
translate(offset: tuple[float, float, float] | numpy.ndarray)
translate_global(offset: tuple[float, float, float] | numpy.ndarray)
rotate(axis: tuple[float, float, float] | numpy.ndarray, angle: float)
rotate_x(angle: float)
rotate_y(angle: float)
rotate_z(angle: float)
look_at(target: tuple[float, float, float] | numpy.ndarray, up=None)
face_along(forward: tuple[float, float, float] | numpy.ndarray, up: tuple[float, float, float] | numpy.ndarray | None = None) → None
set_render_layer(index: int, enabled: bool = True) → None
is_on_render_layer(index: int) → bool
wrap_bounds(bounds: tuple[float, float, float] | numpy.ndarray, margin: float = 1.0)
strict_errors: ClassVar[bool]

True

dev_checks: ClassVar[bool]

None

dev_warnings: ClassVar[bool]

None

freeze_check_interval: ClassVar[float]

1.0

dynamic: bool

False

visible

‘Property(…)’

update_mode

‘Property(…)’

focus_mode

‘Property(…)’

drag_threshold

‘Property(…)’

focus_entered

‘Signal(…)’

focus_exited

‘Signal(…)’

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

None

classmethod __init_subclass__(**kwargs)
property name: str
property visible_in_tree: bool
property input_player: simvx.core.input.players.InputPlayer
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_exit_tree() → None
on_update(dt: float) → None
on_fixed_update(dt: float) → None
on_draw(canvas) → None
on_draw_3d(gizmos) → None
on_picked(event: simvx.core.input.events.PickEvent) → bool | None
on_gui_input(event: simvx.core.input.events.GuiInputEvent) → bool | None
start_coroutine(gen: simvx.core.descriptors.Coroutine) → simvx.core.descriptors.CoroutineHandle
stop_coroutine(gen_or_handle)
property focused: bool
property focus_rect: tuple[float, float, float, float] | None
drag_threshold_for(pointer_type: simvx.core.input.enums.PointerType | str) → float
grab_focus() → None
release_focus() → None
capture_pointer(pointer_id: int | None = None) → None
release_pointer(pointer_id: int | None = None) → None
queue_redraw() → None
property render_dirty: bool
clear_children()
destroy()
property destroying: bool
property destroyed: bool
call_deferred(method: collections.abc.Callable[..., Any], *args: Any) → None
property app
property tree: simvx.core.scene_tree.SceneTree
property viewport_size: simvx.core.math.types.Vec2
property viewport_insets: tuple[float, float, float, float]
property physics
property physics_2d
__getitem__(key: str)
classmethod get_properties() → dict[str, simvx.core.descriptors.Property]
__repr__()