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.

API

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

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

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_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.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: they are recomputed from :attr:pivot, :attr:distance, :attr:yaw and

Attr:

pitch by :meth:update_transform, and the camera always looks at the pivot. Assigning position directly is therefore pointless, since the next :meth:update_transform overwrites it: move :attr:pivot instead.

Meth:

orbit, :meth:pan and :meth:zoom are the intended controls and call :meth:update_transform for you. After assigning the orbit properties by hand (including from a scene file or the inspector), call

Meth:

update_transform yourself.

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.

Meth:

orbit clamps pitch just short of the poles, but direct assignment does not.

Example::

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

Initialization

pivot

‘Property(…)’

distance

‘Property(…)’

yaw

‘Property(…)’

pitch

‘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.

update_transform()[source]

Recompute :attr:position and look-at orientation from the orbit parameters (:attr:pivot, :attr:distance, :attr:yaw, :attr:pitch).

Call this after assigning orbit parameters directly so the world-space transform reflects the new values immediately. :meth:orbit,

Meth:

pan, and :meth:zoom invoke it for you.

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
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

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_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__()