Mesh Renderer was missing from the Add Component menu and its model
reference was read-only, so a mesh could only be attached by loading
a saved scene. Add it to the menu (Unity Add Component semantics) and
give the block a UE-Details-style asset slot: a dropdown listing all
model files under assets/, selecting applies LoadModel (cache deduped).
Asset browser files now show a UE Content Browser style hover card:
kind, size, modified time, and loaded state (new AssetCache::Contains
query, read-only -- hovering never triggers loading).
Fix latent FATALs unlocked by manual assignment: AssetCache::Get
aborts on invalid handles, so rendering, animation sampling, and mesh
collider build now guard mr.model.Valid() before calling it.
The per-entity script list allowed picking the same script class
twice; the name-keyed runtime silently ignores duplicates, leaving
the extra rows dead weight with unusable enable bits. Enforce
DisallowMultipleComponent semantics: the dropdown of each row only
offers unattached names (own name kept for echo), "+ Add Script"
disables when every registered script is attached, and scene loading
dedupes as a safety net for hand-edited files.
The file list was hardwired to the root directory, so it was always
empty (all assets live in subdirectories). Directories are now
selectable from both the tree (click row) and the list (click [D]
row), with a breadcrumb + Root button. Prefix check done manually
(path::starts_with is missing in this libstdc++).
ScriptComponent now holds a list of {name, active} items (Unity
semantics: one entity, many scripts, each with its own enable bit;
component-level active stays as the master switch). ScriptRuntime
reconciles instances by name per entity -- rename/remove destroys
only the affected instances, untouched ones keep ticking. Scenes
persist Script.Items; old single-script key still loads.
AssetBrowserPanel (lightweight Unity Project window): fixed roots
(assets/scenes) as tabs, recursive directory tree, file list with
type filter (models/textures/scenes) and name substring filter,
double-click preloads models/HDRs into the asset caches (path-
deduped). Scene files point to the File menu for now; drag & drop
is a later iteration. Docked with Stats by default in fresh layouts.
Clearer accessor naming on ScriptBehavior (Get -> GetComponent,
TryGet -> TryGetComponent), WASDMove follows. Teleport marked const
(JPH Ref does not propagate constness). Spinner: auto-rotation
disabled for the moment, registration switched from the registration
macro to an explicit lambda to verify the mechanism.
UE calls it Trigger (Overlap Events / Trigger preset) and Unity's UI
says "Is Trigger"; "sensor" is the physics-backend term (Jolt sensor,
Box2D sensor). Rename the component/record flag and the Inspector
label. Scene files now persist "IsTrigger" with backward-compatible
read of the old "IsSensor" key.
ScriptBehavior gains App() host access (Application injected via
ScriptRuntime), TryGet<T> and EntityBits for physics facade addressing.
WASDMove maps WASD to the editor camera's horizontal heading (UE
gameplay convention): dynamic bodies go through the physics velocity
facade preserving the vertical component (zeroing Y would cancel
gravity mid-fall), bodiless entities translate their transform directly.
Editor-mode key conflict is naturally avoided: RMB-held flight consumes
WASD, release hands keys to the script.
ScriptComponent stores only a script name string (POD, serializable);
instances are reconciled by ScriptRuntime each frame (PhysicsWorld
pattern): orphan/rename cleanup with OnDestroy, per-name instantiation
from a file-level self-registering registry (ENGINE_REGISTER_SCRIPT),
OnCreate on bind, OnUpdate after physics step on scaled dt. Engine
framerate loop only sees the base interface, so future Lua/C# behaviors
plug in as foreign ScriptBehavior implementations with zero engine
changes. Ships a Spinner sample script and Inspector script picker.
- JointComponent: type, target UUID, per-body local anchors, hinge axis
+ angle limits (data only, zero Jolt deps)
- PhysicsWorld::SyncConstraints: create/remove/rebuild on snapshot
change; body rebuild contagion via invalidated-bits list; target
resolved lazily by UUID each sync (no two-pass load needed)
- Jolt FixedConstraint/HingeConstraint/PointConstraint in
LocalToBodyCOM space (anchors minus shape COM); fixed frames lock
creation-time relative pose; hinge axis aligned across bodies
- Inspector: Joint section with target entity name dropdown
- Serialization: Joint map with UUID target, as<T> fallbacks
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.
Engine-layer SceneSerializer (yaml-cpp submodule): entities/components
to versioned YAML, atomic clear-and-rebuild on load with per-entity
fault tolerance, missing-field defaults, unknown-component skip and
UUID dedup. Asset references are {Guid, Path} pairs resolved through
the new AssetManager GUID registry (assets/asset_registry.yaml) with
path fallback; procedural:// pseudo-paths rebuild from parameters.
Editor File menu gets Save/Load Scene modals; app re-resolves stale
entity handles via onSceneLoaded callback.
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