forge3d.editor — In-Engine Editor¶
forge3d ships an in-engine scene editor with play/pause/step controls, entity selection, transform gizmos, and a hierarchy/inspector panel.
EditorApp¶
EditorApp ¶
씬 에디터 애플리케이션.
v1 World + ECS EntityWorld를 함께 관리한다. ImGui 없는 환경에서는 상태 머신과 레이캐스트 로직만 동작한다.
__init__ ¶
__init__(world: World, entity_world: EntityWorld, config: LayoutConfig | None = None, dt: float = 1 / 60) -> None
pick_entity ¶
pick_entity(screen_x: float, screen_y: float, fov_deg: float = 45.0, view_matrix: ndarray | None = None) -> int | None
화면 좌표 (screen_x, screen_y)에서 레이캐스트로 엔티티를 선택한다.
move_selected ¶
선택된 엔티티를 지정 축으로 delta 만큼 이동한다 (0=X, 1=Y, 2=Z).
Gizmos¶
TranslateGizmo ¶
선택 엔티티의 위치를 축별로 이동시키는 기즈모.
pick ¶
pick(ray_origin: ndarray, ray_dir: ndarray, ew: EntityWorld, max_dist: float = 100.0) -> Entity | None
화면 레이로 가장 가까운 엔티티를 선택한다.
각 엔티티의 AABB 구(半경 1m 기본)에 레이-구 교차 테스트.
GizmoMode ¶
Bases: Enum
screen_to_ray ¶
screen_to_ray(screen_x: float, screen_y: float, width: int, height: int, fov_deg: float, view_matrix: ndarray) -> tuple[np.ndarray, np.ndarray]
화면 좌표 → 월드 공간 레이 (origin, direction).
view_matrix: (4,4) 카메라 뷰 행렬.
Usage examples¶
Launch the editor¶
import forge3d as f3d
world = f3d.World()
ew = f3d.EntityWorld()
editor = f3d.EditorApp(world, ew, title="My Scene")
# Populate the scene
ew.create_entity(
f3d.Transform(position=[0, 0, 2]),
f3d.MeshRenderer(shape="box", size=(1, 1, 1)),
)
editor.run() # opens an OS window with the editor UI
Headless testing (CI-safe)¶
editor = f3d.EditorApp(world, ew)
editor.run_headless(n_frames=10) # run 10 update cycles without a window
Play/pause/step controls¶
editor.play() # start simulation
editor.pause() # freeze time
editor.step_once() # advance exactly one physics tick while paused
editor.stop() # revert to pre-play snapshot
print(editor.play_state) # PlayState.PLAYING | PAUSED | EDITING
Transform gizmo¶
from forge3d.editor import TranslateGizmo
import numpy as np
gizmo = TranslateGizmo()
# Ray from camera
origin = np.array([0., 0., 10.])
direction = np.array([0., 0., -1.])
hit_entity = editor.pick_entity(
ew,
view_matrix=np.eye(4),
ray_origin=origin,
ray_dir=direction,
)
if hit_entity is not None:
editor.move_selected(axis=0, delta=0.1) # move +X by 0.1 m