- .animctrl state machine asset (params/transitions/blend trees, 1D+2D
directional blending) with Unity Animator-style editor panel: state
list, transitions, params, directional pad preview driving live
CrossFade on the selected entity
- Split animation evaluation out of SceneRenderer into
AnimationSystem/AnimationEvaluator; skin palettes with rest-pose
fallback and blend-weight epsilon filtering
- MaterialAsset (.material) with reload-safe preview tile pool
(DropMaterial clears dirty refs before instance destruction)
- EditorGizmoPass skeleton wires + viewport joint overlay; camera
projection rework (baked Vulkan Y-flip, ortho support)
- Editor/game build split via HELLOVULKAN_EDITOR option
BuildFrame returned false (skip frame) when the game view had no
primary camera, skipping the WaitFence/ResetFence/submit chain - the
frame-slot fence never got signaled, so the next WaitFence on that
slot blocked forever. Reproduced instantly by playing an empty scene
with embedded PIE; latent in manual fullscreen preview too.
Game view now falls back to the editor camera instead of skipping the
frame, keeping the per-slot fence cadence intact. Viewport warning
text updated to mention the editor-view fallback.
Play no longer switches to fullscreen direct rendering: PIE now keeps
the editor UI alive and renders the primary camera into the Viewport
panel (new SceneRenderer::SetGameView flag; kDisplayDirect stays a
manual fullscreen preview for later release form). Editor camera stops
being driven during PIE, and viewport click-pick is gated off while
playing (clicks are game input now).
The Play/Stop buttons move out of the menu bar into a dedicated main
toolbar row below it (centered, UE Main Toolbar semantics): green Play
when idle; Pause (yellow, highlighted while paused) + Stop (orange)
while playing. Ctrl+P hotkey kept. Stats "Pause Simulation" merged
into the toolbar Pause; viewport overlay shows PAUSED accordingly.
Editor toolbar gains Play/Stop (Ctrl+P) with UE semantics:
- Play: serialize current scene to an in-memory YAML snapshot via new
SceneSerializer::SerializeToString, switch display to kDisplayDirect
(primary camera entity renders), unfreeze time (timeScale 0 -> 1)
- Stop: DeserializeFromString atomically rebuilds the pre-play scene,
back to kDisplayViewport (editor camera), refreeze time; selection
cleared and gizmo drag reset since all entity handles dangle
- SceneSerializer refactored: SerializeDoc/ParseRoot shared by file
and memory variants; file Save/Load behavior unchanged
- Running state disables gizmo + editor hotkeys in the viewport (input
goes to game scripts), draws yellow PLAYING hint and a no-camera
warning; File->Save/Load disabled while playing (UE behavior)
Physics bodies/constraints/characters and script instances are
reconciled automatically by the existing per-frame sync on restore.
EditorCamera (UE FEditorViewportClient semantics) owned by SceneRenderer:
own TransformComponent + reused Camera controller. BuildFrame dispatches
render source by display mode — editor form always renders from the
editor camera (usable in empty scenes), game form renders from the
scene's primary camera entity. AlignTo copies an entity pose into the
editor camera ("Align Viewport to Camera" button in Inspector). F focus
and viewport gizmo now follow the same dispatch. main.cpp stripped of
dangling sandbox camera/spinner state; onSceneLoaded hook removed.
Key conflict fix (UE semantics): while RMB is held the keyboard belongs
to the camera, so WASD no longer collides with the gizmo W/E/R tool
hotkeys. Editor mode gates fly movement behind RMB; game mode keeps
always-on WASD. Viewport tool hotkeys (W/E/R/T/F) are suppressed while
RMB is held.
Focus orbit (pivot): MMB drag rotates the camera around a pivot keeping
distance; first orbit picks a pivot 5m ahead on the view ray. Shift+MMB
pans in screen plane (camera and pivot move together). Mouse wheel
dollies toward/away from the pivot with exponential steps (no pivot:
move along view ray). F focuses the selected entity (frame-selection):
pivot set to its position, camera slides along the view ray; orbit
distance persists.
Window gains a polled scroll-delta channel (ConsumeScrollDelta) that
respects ImGui mouse capture; Camera::Update mutates orbit state (no
longer const).
User feedback: the self-drawn scale handles clash visually with the
ImGuizmo translate/rotate rods (different size/style, clearly not one
widget). Keep the library widget for all three ops for visual
consistency and prevent mis-drags by masking degenerate axes via
SetAxisMask:
- translate: axis ~ parallel to camera-to-object line (sin < 0.25)
- scale: axis ~ perpendicular to camera-to-object line (|dot| < 0.1,
intersection numerator identically zero -> grab fails/values jump)
OR axis ~ parallel to view direction (|dot| > 0.98, zero screen
displacement -> value never changes)
- rotate: screen-space math, no degeneracy, no mask
Masked axes are neither drawn nor activatable; a slight view change
restores them. Center uniform-scale square (MT_SCALE_XYZ, pure screen
space) stays available in any view.
ImGuizmo scale hover/drag relies on ray-plane intersection with the
axis as plane normal; in front views (axis ⟂ camera-to-object) the
intersection numerator is identically zero, so handles can't be
grabbed and values jump randomly; axes parallel to the view have zero
screen displacement. Submodule is read-only and AxisMask can only hide
degenerate axes, not replace them.
Replace the library scale path entirely with a screen-space gizmo:
- three axis rods (line + end square) + center uniform-scale square,
constant screen size in any view
- axis drag: mouse delta projected onto the axis' screen direction
- axis pointing at camera: radial mapping (drag away = grow)
- center square: uniform scale (library MT_SCALE_XYZ semantics)
- Ctrl snapping quantizes the result; scale clamped >= 0.01
- pick suppressed while hovering/dragging the self-drawn handles