Commit Graph
99 Commits
Author SHA1 Message Date
atdunbg ff955b9e63 新增脚本参数系统:声明式参数 + Inspector 自动控件 + 场景序列化
DeclareParams/ParamBuilder 声明字段,运行时反射读写;
Inspector 按类型生成控件,SceneSerializer 直通存取;
动画系统接入参数化裁剪;新增 OrbitMove 示例脚本
2026-10-08 17:02:19 +08:00
atdunbg f327f44269 新增 GameFlow 运行时状态机:PIE 与独立运行统一生命周期
Stopped→Loading→Running↔Paused 单一状态源,编辑器 Play/Stop
与 main.cpp 独立运行共用同一套加载/恢复/暂停语义
2026-10-08 17:02:09 +08:00
atdunbg cc837bc605 Add animation controller system, material assets and skeleton visualization
- .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
2026-10-07 01:50:04 +08:00
atdunbg a941207ae3 Raise bloom threshold to HDR 3.0; fix blit dst-mip layout warnings
The smooth brass case back of the pocket watch reflects large bright
sky regions of the Venice dawn environment at HDR values well above 1.
With the old threshold of 1.0 the entire metal surface passed the
bloom extract, and the blurred additive result washed it out to flat
white. Only genuinely hot highlights (HDR >= 3, near-white after ACES)
should bloom.

- Default bloomThreshold 1.0 -> 3.0; Stats slider range extended to 8
  with a logarithmic scale
- Texture mip generation: transition the whole mip chain to
  TRANSFER_DST up front; blitting into never-transitioned destination
  mips triggered one validation warning per level
2026-10-05 12:28:14 +08:00
atdunbg 5d88ed2057 Improve PBR quality: mip chains, anisotropic filtering, baseColorFactor
Root causes behind blurry/unphysical materials (4K pocket watch render
mushy, glass fully opaque, watch hands unreadable):

- Textures loaded via stbi had a single mip level: 4096x4096 albedo/MR
  maps sampled with no LOD -> minification aliasing turned fine detail
  (watch hands) into noise. Generate a full blit-based mip chain with
  per-level layout transitions.
- samplerAnisotropy was never enabled on the device and every sampler
  was created with anisotropy disabled: grazing-angle surfaces (watch
  face, ground) collapsed into a blur. Enable the device feature and
  16x anisotropic filtering on material texture samplers.
- glTF baseColorFactor was ignored: the glass material (alphaMode
  BLEND, no baseColor texture, baseColorFactor.a = 0.3) fell back to
  the default white texture with alpha = 1 and rendered fully opaque.
  MaterialParams gains a baseColor vec4 (rgb tints albedo, a scales
  the blend alpha) threaded through both PBR shaders.
- alphaMode MASK was treated as opaque: add a factors.w switch that
  performs an alpha cutout test in the opaque shader.
- Fix material cache key dropping factors.z (separate-roughness
  switch); it now encodes the full factors and baseColor vectors.
2026-10-05 12:01:07 +08:00
atdunbg a3037cd401 Input facade: add cursor capture, mouse delta and scroll delta
SetCursorCaptured / IsCursorCaptured / GetMouseDelta / ConsumeScrollDelta
now route through the Input facade, so the editor camera no longer takes
a Window& at all: Camera::Update and EditorCamera::Update lose their
window parameter and SandboxApp calls Update(dt) directly. App layer
now has zero Window/GLFW usage for input - everything goes through
Engine::Input.
2026-10-05 11:45:57 +08:00
atdunbg 641b255cb2 Unify input behind Engine::Input facade; fix Ctrl+P and Pause button
New Engine/Core/Input.h/.cpp: static input facade with a Key/MouseButton
enum (values map to GLFW key codes internally) so scripts and game logic
no longer touch GLFW directly - window backend becomes swappable and the
facade is the surface to expose when embedding scripting later. Wired up
in Application's constructor; Camera (editor camera WASD/RMB/MMB), the
WASDMove script and SandboxApp key handling now query Input only.

Ctrl+P hotkey fix: the !m_Playing guard made the hotkey dead exactly
when it was needed - after pressing Play. Now toggles both ways.

Toolbar Pause button now reads "Resume" while paused (tooltip follows).
2026-10-05 11:34:27 +08:00
atdunbg 1a278d5945 PIE: viewport-embedded playback with UE-style main toolbar
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.
2026-10-05 11:14:24 +08:00
atdunbg 432089747f Environment settings become scene entities (UE Skybox/SkyLight semantics)
Skybox/SkyLight are now Actor-carried components instead of resident
global settings, matching UE where SkyAtmosphere/SkyLight/fog are
Actors added from Place Actors and configured in Details.

- New SkyboxComponent (active + envPath asset slot) and SkyLightComponent
  (active + ambientColor); first-active entity wins per frame
- SceneRenderer collects env from components into FrameSceneData
  (DrawSkybox/envPath/ambient); Renderer performs delta sync - env
  reloads only when the resolved path changes, neutral fallback when
  empty or unloadable
- App-side env plumbing removed: kEnvPaths table and getEnvIndex/
  setEnvIndex callbacks deleted; StatsPanel keeps ClearColor + debug
  view, env params move to Details
- Serializer: scene-level Settings stores ClearColor only; env
  components serialize per entity; legacy v1 scenes (Environment header
  + Settings.AmbientColor/DrawSkybox) auto-migrate into an Environment
  entity on load for visual parity
- Inspector: Skybox/Sky Light sections with .hdr asset slot dropdown
  and Add Component entries; Outliner "+ Create" popup offers
  Empty/Light/Camera/Environment
2026-10-05 01:02:21 +08:00
atdunbg dec171cd1e Fix selection outline never showing for the first entity
entt entity 0's integral bits are 0, colliding with the "no selection"
sentinel: u32 selected == 0 meant "clear selection", so the scene's
first-created entity could never be outlined. Replaced with
std::optional<u32> + FrameSceneData::selectedEntityValid.

Also restore per-batch instance contiguity broken by the batchMats
flattening (e038cb3): entities sharing a mesh scatter across the flat
instance array in collection order. ViewScratch now tracks per-batch
singly-linked lists and appendView aggregates each batch into a
contiguous range, preserving the flat-array performance intent.

Hierarchy click selection now routes through SelectEntity so outline
stays in sync when picking from the panel.
2026-10-05 00:39:48 +08:00
atdunbg e038cb39fa SceneRenderer 实例收集扁平化:batchMats 从 vector<vector> 改为单 vector 按 (first,count) 切片,优化大场景下批次实例存储与 cache 命中率 2026-10-04 21:26:48 +08:00
atdunbg 3e85285e41 修改SceneRenderer 的存储的世界矩阵存储方式 2026-10-04 20:10:49 +08:00
atdunbg 2268fa5f76 资产改为保存场景或者关闭软件时候更新guid文件,vulkan/DescriptorManager 创建DescriptorPool时候注释eFreeDescriptorSet,PhysicsWorld初始化时候改为推荐的inNumBodyMutexes 2026-10-04 19:55:49 +08:00
atdunbg c1854453e5 修改场景实体查询为unordered_set存储,提升查询效率, 修改Physics的SyncScene也为unordered_set 2026-10-04 19:12:53 +08:00
atdunbg c8a1cd90bf 修复physics物理数据计算不正确的问题,同时为物理添加map索引 2026-10-04 18:42:23 +08:00
atdunbg ee5ab8e94e feat: parent-child transform hierarchy (UE Actor attach semantics)
- TransformComponent TRS = local (relative to parent); cached world SRT
  composed hierarchically (parent pos/rot/scale, no matrix decomposition
  shear under non-uniform parent scale)
- HierarchyComponent (parent + children, lazily attached); Scene::
  SetParent (keepWorld, cycle-safe) / GetParent / IsDescendantOf /
  SetWorldPose (world->local inverse, used by physics writeback & gizmo)
- UpdateTransforms: DFS from roots, recompose only dirty-or-dirty-
  ancestor subtrees (parents before children)
- DestroyEntity: descendants buried with parent (UE cascade)
- Serialization v2: optional Parent uuid key, two-pass relink on load
- Hierarchy panel: tree view, drag-drop reparent / drop-to-root detach,
  Create Child / Detach context menu; Inspector shows parent name
- Consumers moved to world-space reads: lights, camera pos, physics
  bodies/characters/joints/collider baking, gizmo, F-focus, AlignTo
2026-10-04 15:22:25 +08:00
atdunbg 97408e506c fix: ImGui hover jumps while cursor captured (RMB look / MMB orbit)
With GLFW_CURSOR_DISABLED the official GLFW backend keeps feeding
virtual unbounded cursor coords to ImGui each frame, so hover state
wanders across hidden panels while the physical cursor is invisible.
Append a "mouse left the window" pos event before ImGui::NewFrame
whenever the cursor is captured - ImGui treats the frame as mouseless.
2026-10-04 15:00:29 +08:00
atdunbg 175ce35def fix: animation-only FBX assets loadable; clip data viewer
Pure-animation FBX (no meshes, skeleton clips only) was rejected at
the cache layer: ParsedModel::Valid() required non-empty meshes, so
Model::FromParsed/LoadModel returned null and the asset never made it
into the cache. Animation assets are now first-class:

- ParsedModel::Valid() = meshes or clips non-empty; parse failures
  (both empty) still rejected. Empty-mesh consumers already guarded
  (rendering skips empty mesh loop, mesh collider falls back to Box)
- ParseFromDisk: fix null-deref when assimp returns a null scene
- AssetCache::Find(path) + AssetManager::GetModelByPath for panels
- Asset Browser hover card shows meshes/bones/clips for loaded models
- Inspector model line flags "animation-only" when meshes == 0
- Animation section: collapsible clip data table (name/duration/
  channel count) to verify bone-name matching of attached clips
2026-10-04 02:31:30 +08:00
atdunbg 2577809ee4 feat: Play-In-Editor (PIE) with in-memory scene snapshot
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.
2026-10-04 02:22:49 +08:00
atdunbg 90097a756e fix(editor): rotation drag accumulates without wrap (UE session semantics)
Dragging rotation hit a wall at the wrap point: each frame re-read
euler from the quat and wrapped to [0,360), so dragging past 360
snapped the display back to 0 (and backwards dragging wrapped up to
360) -- values "jumped around" instead of accumulating.

Port UE TransformDetails session semantics: the rotation row shows a
local mirror value while the user edits it (accumulating freely in
both directions, never wrapping), and re-syncs from the quat only on
foreign changes (gizmo drag, physics writeback, entity switch).
Detection is exact component-wise quat equality: the mirror quat comes
from the same QuatFromEuler used by SetRotation, so an untouched pair
compares bit-equal, while any foreign writeback -- including sub-0.05
degree gizmo steps that a dot-product threshold would miss -- differs
in at least one float. Edit frames skip the check (mirror leads the
quat by one drag delta), gated on the previous frame's item-active
state.
2026-10-04 02:00:42 +08:00
atdunbg 4498bc7535 feat(editor): rotation display in UE's 0-360 convention
User pointed out the real gap: UE Details shows rotation in [0,360)
(-90 reads 270); our readback exposed [-180,180] with negative zeros.
GetRotation now wraps each axis to [0,360) via fmod -- strictly
equivalent math (each axis has 360-degree period), so matrix-rebuild
consumers like Camera::EulerRotMat are unaffected. The one consumer
needing a continuous domain is the free-look pitch clamp [-89,89] in
Camera::Update, which wraps back to [-180,180) before applying deltas.
2026-10-04 01:46:05 +08:00
atdunbg 4fc48c21ac fix(editor): euler display precision at gimbal lock (90 -> 89.98)
Typing 90 degrees on the pitch axis showed 89.98. The stored quat is
exact (matrices differ by 0.0) -- the noise is display-side: quat's
sin(90) components square to 0.99999994 in float, and asin() diverges
at |sin|->1, amplifying 6e-8 into 0.02 degrees. Yaw/roll use atan2 and
were always exact, matching the user's report being X-axis only.

Two fixes, both aligned with how UE gets away with it (internally UE
is quat + equally pathological, its Details panel just shows one
decimal):
- GetRotation gimbal-lock threshold 1.0 -> 1-1e-6: pitch near +-90
  now takes the composite-angle atan2 branch (ratio-stable) instead
  of asin, so typing 90.0 reads back exactly 90.0. Probed: X=90 and
  X=-90 now round-trip to 0 error; generic poses stay ~1e-6 deg.
- Rotation row shows one decimal ("%.1f", UE Details default), masking
  the residual asin noise for angles near (not at) the pole; Ctrl+Click
  input stays full precision. Negative zero from atan2 cleaned up.

Remaining asin noise for e.g. 89.9 (~9e-4 deg) is below the 1-decimal
display and unreachable in practice.
2026-10-04 01:37:49 +08:00
atdunbg 90bf63e641 feat(animation): attach external animation files to a model
Animation-only FBX files (no mesh, clips only) could not be used:
clips are keyed by node index into the parsed model's node table, so
there was no path to get them onto a different model's skeleton.

Add AssetManager::AttachAnimationClips: parses the container with the
CPU-only path (no GPU upload -- empty meshes cannot build a Model),
remaps each clip's channels to the target model's node indices by
bone name, and appends to its clip list. Channels whose bone name is
absent are skipped and counted; clips with zero matching channels are
dropped. Same-source attach is idempotent via a new
AnimationClip::sourcePath marker; name clashes get the file stem
prefixed.

Inspector Animation block gains an attach slot (UE Details semantics)
listing model-container files plus a bullet list of attached sources;
already-attached paths are replayed idempotently when the entity is
selected, so clips survive restarts. The paths persist in the scene
(Animation.Attached); the clips themselves stay runtime state.

Limitations (documented, by design): same-skeleton retargeting only
-- bone names must match between source and target (same DCC export
or Mixamo-style per-animation downloads); cross-skeleton retargeting
is out of scope.
2026-10-04 01:25:04 +08:00
atdunbg 69671cf4eb feat(editor): manual mesh renderer setup + asset hover info card
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.
2026-10-04 01:07:38 +08:00
atdunbg ab7a43a686 feat(editor): multi-script entities + asset browser panel
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.
2026-10-04 00:43:06 +08:00
atdunbg 74a2044441 chore(scripts): rename script API to GetComponent/TryGetComponent; misc
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.
2026-10-04 00:33:47 +08:00
atdunbg 134812dae3 feat(physics): debug-draw character controller capsule
CharacterVirtual is not a registered body, so PhysicsSystem::DrawBodies
never renders it (Jolt provides no CharacterVirtual::DebugDraw either).
Add CharacterController::DebugDraw (shape wireframe at foot-origin +
shapeOffset, green) and drive it from CollectDebugData under the
colliders bit.
2026-10-03 19:01:57 +08:00
atdunbg 29c413579d refactor(physics): rename isSensor to isTrigger (Unity/UE terminology)
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.
2026-10-03 18:53:38 +08:00
atdunbg b6d0868e15 feat(ecs): per-component enable/disable + inspector rework
Add `active` flag to the 8 behavior components (Unity enabled / UE
SetActive semantics; structural Id/Name/Transform excluded). Consumers
skip disabled components: renderer (mesh/light/anim), primary camera
election, physics reconciliation (body/constraint/character destroyed
and rebuilt on toggle), scripts keep instance but stop ticking.

Inspector: component headers now carry an enable checkbox and a remove
button, Add-Component menu greys out attached entries, and conflict
hints added (RigidBody <-> CharacterController mutual exclusion, Joint
requires RigidBody, Animation requires MeshRenderer, unregistered
script name). Scenes persist Active per component (default true).
2026-10-03 18:45:03 +08:00
atdunbg cad0481bc0 feat(app): WASDMove script (WASD camera-relative movement)
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.
2026-10-03 18:27:12 +08:00
atdunbg 3403822a52 feat(engine): C++ native script system (ScriptBehavior + registry)
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.
2026-10-03 18:02:49 +08:00
atdunbg ea6beee593 fix(physics): editor drag teleports dynamic bodies
SyncScene only reconciled static/kinematic transforms into Jolt bodies;
dragging a dynamic entity (esp. while paused, where Writeback never runs)
moved the visual but left the body -- and the collider debug wireframe --
at the old pose. Dynamic bodies now get the same drag reconciliation
(teleport + zero velocity + activate), and Writeback syncs
lastPos/lastRot to the body pose so physics motion is never misread as
editor dragging (which would teleport the body back).
2026-10-03 17:51:11 +08:00
atdunbg d5a86c170e fix(renderer): mesh collider source positions in render space for skinned meshes
Skinned meshes keep vertices in raw mesh space (bakeTransform=false);
rendering transforms them via bind palette (global(bone) x invBind, folds
FBX unit scale). Physics MeshShape/ConvexHull and the mesh bounding sphere
consumed the raw positions directly -> M1911 hull measured ~10x the visual
size. ParseFromDisk now bakes bind-palette-skinned positions per vertex
(same formula as the skinning shader) into ParsedMesh::cpuPositions; Mesh
uses them for m_CpuPositions and bounds. GPU vertex data and rendering are
untouched.
2026-10-03 17:43:55 +08:00
atdunbg e1cef53158 physics: character controller (Jolt CharacterVirtual)
- CharacterControllerComponent: capsule params (radius/halfHeight, mass,
  max slope, step height); entity origin = feet via shape offset
- CharacterController per-entity wrapper: gravity integration + grounded
  clamp, ExtendedUpdate (sweep/slide + stick-to-floor + walk-stairs),
  SetLinearVelocity/Jump/IsGrounded facade
- PhysicsWorld: SyncCharacters reconcile (create/rebuild on param
  change, editor-drag teleport), UpdateCharacters after physics Update
  (Jolt recommended order), writes back to Transform
- Inspector: params + grounded/velocity telemetry + jump debug button
- Serialization: params only, state rebuilt by reconcile
2026-10-03 16:34:20 +08:00
atdunbg 82910c41e8 physics: joint constraints (fixed/hinge/ball)
- 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
2026-10-03 16:26:20 +08:00
atdunbg a744b332eb physics: capsule/cylinder/compound collider shapes
- ColliderType + Capsule/Cylinder/Compound; CompoundChild list (Box/
  Sphere/Capsule, local pos/quat, non-recursive)
- CreateBody: CapsuleShape/CylinderShape (ctor param orders differ) +
  StaticCompoundShape; empty/failed compound falls back to Box
- BodyRecord caches radius/halfHeight/compound; SyncScene rebuilds on
  change (per-collider param extraction)
- Inspector: combo entries + shape params + compound child list editor
- Serialization: new fields with as<T> fallback, compound seq round-trip
2026-10-03 16:17:44 +08:00
atdunbg 6c81bfd70d physics: rigid body drive API (force/impulse/torque/velocity)
PhysicsWorld facade methods addressed by entity bits via body records:
AddForce/AddImpulse/AddTorque (dynamic bodies only, auto-wake), Set/
GetLinearVelocity/SetAngularVelocity (dynamic + kinematic). Inspector
RigidBody block gains impulse debug controls for manual response tests.
Transient by design — nothing serialized.
2026-10-03 16:10:22 +08:00
atdunbg fae6614e70 editor camera: dedicated Scene-view camera decoupled from camera entities
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.
2026-10-03 15:52:28 +08:00
atdunbg 007d175314 serialization: scene save/load (Unity-style YAML + UE-style GUID registry)
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.
2026-10-03 14:47:02 +08:00
atdunbg ffe33e9967 camera: UE Scene-view semantics — RMB fly, MMB focus-orbit, wheel dolly, F focus
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).
2026-10-03 13:02:55 +08:00
atdunbg e5161439dd refactor: TransformComponent 旋转存储改四元数(UE FTransform 语义)
欧拉角内部存储每帧矩阵↔欧拉往返,是 Gizmo 拖拽乱跳/缩放串轴的
结构性根源(74d2f6c 的单点公式修复治标不治本)。对齐 UE/Unity
方案:内部存 glm::quat,欧拉只在 UI 表皮。

- Components.h:m_Rotation 改 quat;Compose 用 mat4_cast;
  SetRotation(euler) 转存(q=qy·qx·qz);新增 SetRotationQuat/
  GetRotationQuat;GetRotation() 改派生欧拉(YXZ 序矩阵反解,
  仅 UI 显示/编辑用)
- ViewportPanel:Gizmo 写回 decompose 的 quat 直写,删除整个
  欧拉提取块
- PhysicsWorld:删 QuatToEulerYXZ/EulerYXZToQuat,改 ToJolt/
  ToGlm 分量直转;物理写回 SetRotationQuat;BodyRecord.lastRot
  改存 quat

验证(1 万随机姿态 round-trip):Gizmo 链路矩阵差 7.9e-07
(旧欧拉方案 3.56e-04,好 3 个数量级);万向锁 pitch=±90° 穿
表皮恒定;欧拉表皮误差 4.5e-04 为欧拉参数化固有病态(随
pitch→±90 指数增长,仅影响 UI 显示数值),热路径不经过。
2026-10-03 00:47:11 +08:00
atdunbg b0d5ed4550 refactor: ShaderManager 引用计数句柄——shader 生命周期自动化
- ShaderObject 值类型 → ShaderRef(shared_ptr 语义,UE TShaderRef
  对标):拷贝共享所有权,最后一个引用析构时自动销毁 VkShaderEXT,
  持有方零清理代码——"新 pass 忘销毁"整类 bug 被机制消灭
- 删除公开 Destroy 与全部持有方手工 destroyShaderEXT(8 处);
  各类析构只清 sampler/layout/set 等其余原生句柄
- RecordFullscreen 参数收窄为 const ShaderRef&(裸 handle 无法
  再被误传);bind 点统一 GetHandle()
- Outline/SelectionMask 撤掉上一轮补的 Device&(不再需要)
- 验证:5s 探针自动退出 → exit 0、无 VUID-05137 泄漏、无 SIGSEGV
2026-10-02 20:20:07 +08:00
atdunbg 47dd879977 fix: 退出 SIGSEGV 与 VkShaderEXT 泄漏——生命周期三层秩序
- 根因:~Application 体内 Logger::Shutdown() 先于成员析构执行,
  Device 销毁期间 Vulkan validation 回调仍打日志 → spdlog 悬空调用
- 修复:app 置于 main 独立作用域,所有成员析构完毕后再 Shutdown
- 退出序 = waitIdle(~Application 体内)→ 成员析构(shader 等
  Device 子对象销毁)→ Logger::Shutdown
- 顺带注释掉窗口全透明(调试期遗留)
2026-10-02 20:09:12 +08:00
atdunbg 1449bea86d feat: HitProxy GPU 拾取 + UE 选中描边 + 编辑器拾取交互
- HitProxy 拾取:RG 管线渲染实体 id → host readback,点击帧
  PickEntity 精确命中;拾取按视觉几何(Model 联合包围球射线求交),
  与 Collider 解耦(UE/Unity 编辑器语义),命中打印实体名+碰撞体参数
- 选中描边(UE 语义):SelectionMask pass(R8Unorm 完整投影轮廓,
  无深度测试透墙可见)→ Outline pass(边缘检测 + alpha 叠加,
  必须注册在 Tonemap 后);edgeCut 贴视口边缘;widthPx=6 可调
- shader object 实证修复:VUID-08879 同程序 set layouts 超集一致、
  VUID-10065 push block 4×float 平铺、新 pass 析构销毁 VkShaderEXT
- RG:SideEffect() 保活"产出无 RG 内消费者"的 pass 链
- Editor:层次面板点击选中、Inspector 姿态编辑、Stats 面板拾取信息
- 相机 Update 改 const(姿态写入 Transform 引用,Camera 本身无状态变更)
2026-10-02 20:09:05 +08:00
atdunbg 1c4b6f37ff feat: Jolt 物理系统——刚体/碰撞体/物理调试图元 + 引擎时间单源
- PhysicsWorld:Jolt 集成,Dynamic/Kinematic/Static 刚体,姿态写回
  Transform(Unity FixedUpdate 语义,吃 scaled dt,暂停自动冻结)
- 碰撞体:Box(默认/手动 halfExtents)+ Mesh 碰撞体(Static→精确
  MeshShape / Dynamic·Kinematic→ConvexHull,数据源 = Mesh ctor 缓存
  的 CPU 位置副本,退化三角剔除,失败回退 Box 并告警)
- PhysicsDebugPass:物理调试图元每帧上传绘制(debug_line shader)
- Time:引擎时间单源(BeginFrame 落定 FrameTime,scaled/unscaled 分离)
- JoltPhysics 以 submodule 接入(官方 url)
2026-10-02 20:08:54 +08:00
atdunbg 20f0035baa feat: ImGui docking Editor + 显示链/帧同步/输入语义重构
- App/editor: Hierarchy/Inspector/Viewport/Stats 四面板 + DockBuilder 默认布局,main.cpp 瘦身为场景搭建与生命周期
- 显示链: Tonemap→PresentTarget 经 ImGui::Image 呈现(零色偏),backbuffer 仅承载 ImGui;Direct/Viewport 双显示模式 + 面板分辨率上报
- 帧首同步 BeginFrameSync: resize/vsync/尺寸/模式意图统一帧首落定(修描述符悬垂);修复 vsync 意图未落 swapchain 导致的每帧重建交换链
- 输入: Editor 每帧判定键盘/鼠标归属(打字让位、Viewport hover/focus 放行、Fullscreen 全放行)
- 材质: roughness 独立槽位 + 非 glTF 金属/粗糙贴图映射;透明队列 depthWrite=FALSE + translucent 独立 shader + scratch 清帧
- FBX: 非蒙皮网格烘焙节点链全局变换(Z-up 旋转/单位换算落顶点)
- Hierarchy 行 ImGui ID 以 UUID 作用域(重名不冲突、改名不丢选中)
2026-09-30 00:24:53 +08:00
atdunbg b25fa29fe5 feat: 透明材质 alpha blending——独立队列 + 线性 over 混合 + 距离排序
透明来源:glTF alphaMode=BLEND(解析期读取,OBJ/FBX 恒不透明——
资产格式定义写死);MASK 后续演进。Material 加透明语义(入缓存键),
程序化测试资产 CreateTransparentPlane(RGBA 纯色纹理)。

SceneRenderer:透明材质实例收集进独立 scratch(仅主视图,不投
阴影——alpha 阴影后续演进),batch 按平均距离降序排序(远→近,
batchMats 同步重排),SSBO 扁平化顺序 = 排序后顺序。

ForwardPass 两段绘制:不透明(无混合 + 深度写)→ 透明(Shader
Object 动态状态切 SRC_ALPHA/ONE_MINUS_SRC_ALPHA 线性 over + 深度
只测不写);velocity 附件透明段直写(透明表面 velocity 即自身运动)。
frag alpha = albedo 贴图 a 直通,预乘在 tonemap 段(hard constraint
不变)。已知边界:TAA alpha 时域混合(透明边缘几帧渐显);同 batch
多实例不做实例级排序。
2026-09-25 16:57:27 +08:00
atdunbg 8dbfb9f554 feat: LOD 运行时开关(ImGui Features 横排,关 = 实例收集恒选 LOD0) 2026-09-25 16:39:16 +08:00
atdunbg a975845214 perf: BuildFrame 收集段优化——实体级粗剔除 + 数组化 batch 查找 + 合并矩阵写
定位数据(100 背包 + SunTemple,RTX 5060):GPU 全帧仅 ~3ms
(Forward 1.91 + Shadow 0.82),CPU BuildFrame 收集段 5.94ms 才是
帧率瓶颈。三段细分测量:pre 0.2 / each+cull 5.0 / flatten 0.4。

优化(collect 5.94 → 4.6ms,-23%):
1. Mesh 全局连续槽位(GetSlot);ViewScratch batch 查找 hash →
   数组寻址(slot × kMeshLodCount 槽)
2. Model 级联合包围球(各 mesh bounds 并集):实体在四视图全部
   不可见 → 跳过整个 mesh 循环(多 mesh 模型的粗剔除)
3. worlds/prevWorlds 合并为 stride-2 InstanceMatrices 单次写
   (每实例每视图 2 次随机堆访问 → 1 次,cache miss 减半)

遥测:Renderer::GetCollectMs 暴露收集段耗时。剩余 4.6ms 的进一步
压缩(并行收集 / GPU-driven culling)单独立项。
2026-09-25 08:08:51 +08:00
atdunbg d9b1742327 feat: LOD 网格分级——meshopt 解析期简化 + 按距离/级联选级
vendored meshoptimizer 1.2(submodule 环境故障改直接 vendor)。Mesh
多 LOD:各级索引连续拼进同一 index buffer(共享顶点,LodRange 区间
选择),解析期 meshopt_simplify 生成链(LOD0=原始,每级目标减半,
相对误差 1e-2,小网格复用上级);都在 worker 线程,加载零额外卡顿。

SceneRenderer 按 (mesh, LOD) 聚合 batch(lookup key = 指针<<8|lod):
主视图按屏占比近似 radius/dist 选级(阈值 10/20 倍半径);阴影级联
按级联序降级(越远越简)。Draw 按 batch.lod 选索引区间。

A/B 对比(100 背包 10×10,RTX 5060 Laptop,vsync off):
- 纯背包:LOD 开 93-95 fps / 关 90-107 fps —— 无显著差异
- 加 SunTemple:LOD 开 52-56 / 关 51-55 —— 无显著差异
结论:当前场景瓶颈不在顶点数(5060 顶点吞吐富余),LOD 基础设施
保留(几何复杂资产/低端 GPU/更大场景生效),帧率问题转向 Tracy
GPU 时间线定位真实瓶颈。

坑:LodRange 记录"LOD 不足复用最后一级"时 first 不得继续累加
(buffer 无重复数据,累加必越界——VUID-08798 抓的)。
2026-09-24 20:53:19 +08:00