Commit Graph
141 Commits
Author SHA1 Message Date
atdunbg 2df6e57b61 透明分层屏幕空间折射 + 透明排序修正 + 默认清屏色归零
默认 ClearColor 0.1 → 0(策划阶段结束,无用)。

透明分层折射(玻璃见玻璃):透明段原来单个 ForwardTrans pass,折射
采样源 SceneColor 副本只在不透明段后快照一次(不含任何透明体)→ 前
玻璃折射不出后玻璃。改为按绘制单位远→近拆成至多 kTransparentLayers
层,每层绘制前刷新 SceneColor 副本,使层 L 玻璃采样含所有更远透明体。
RenderGraph pass 编译期定序,故层数固定;超出并入末层。深度 storeOp
改 eStore(多层需 Load 承接有效深度内容)。shader 不改。

透明排序修正:排序键由"相机到实例的欧氏距离"改为视图空间深度
(-(view·p).z)——欧氏距离把侧向偏移计入距离,偏侧物体被误判为更远
(正方体右上的甜甜圈在特定视角被排到后方)。并让透明队列逐实例成批
(不合并):透明本就是每层一个绘制单位,合并成多实例 batch 会让排序
只能用批平均深度、批内实例无法各自定序。零额外 draw 代价。
2026-10-18 17:09:42 +08:00
atdunbg f107854236 MeshFactory:程序化几何单一源 + CreatePrimitive 链路
新增 MeshFactory(Cube/Plane/Sphere/Cylinder/Cone/Torus + 切线计算),
Model 程序化几何改走 MeshFactory(收敛为单一几何源,消除重复的顶点
生成),并新增 CreatePrimitive;打通 AssetManager/IAssetUploader/
VulkanAssetUploader 的 CreatePrimitive 链路;编辑器 Procedural 弹窗
下拉 + ResolveProcedural 伪路径反解 + 场景序列化(程序化图元可存取)。
2026-10-18 17:09:25 +08:00
atdunbg 22309d24f0 材质与环境光照:UE 打包 Specular 导入(R=AO/G=Rough/B=Metal 与 glTF MR 同序 → 同一贴图直供 MetallicRoughness + Ao 槽,factors 直用贴图,默认置翻绿位 misc.w);MappedNormal 支持 DirectX 法线翻绿(misc.w,编辑器加 Flip Green 开关);环境遮蔽拆分漫反射与高光(高光遮蔽更狠:室内高光须压暗,此前表面反射天空贴图 = 室内穿帮);ufbx 导入日志补齐 mr/normal/ao/emissive 槽位 2026-10-18 15:23:07 +08:00
atdunbg e7711134fe 物理修复与相机防穿墙:Jolt Trace 接管(缺省 DummyTrace 带 JPH_ASSERT(false),调试版一被调用即断;同文去重防多线程刷屏);MeshShape 三角索引补基址(此前把 materialIndex/userData 当顶点偏移传 → 每个 mesh 三角全指向前一 mesh 顶点区,碰撞形状错位 + AABBTree 随机分割告警);调试绘制巨型网格三角预算(>5 万三角退化为包围盒线框,避免百万级 DrawLine 打到个位数帧率);角色 mEnhancedInternalEdgeRemoval 消网格地面 ghost contact 顶起/抖动;新增 RayCastStatic(只命中非移动层,碎块/动态物不当遮挡)→ ThirdPersonCamera 防穿墙拉近(瞬时拉近、平滑回弹、贴墙最小距离与回退余量可配) 2026-10-18 15:23:01 +08:00
atdunbg 2024772bac TAA 时域速度链路贯通:天空盒补写 velocity(此前恒 0,相机移动时天空/背景重投影错误 → 闪烁);skyPrevVP(上一帧未抖动 proj × 去平移视图)落帧;TAA 加去遮挡深度检测(重投影点历史深度与当前像素比对,新暴露区拒历史,需 near/far + 投影模式落帧);移除 min-depth dilation(把前景速度泄漏给轮廓旁背景像素 → 边界闪);Forward 不透明段 velocity 附件 Clear→Load 承接天空结果;velocity 不入抖动注释订正(抖动进 velocity = 静止画面持续亚纹素重采样发糊) 2026-10-18 15:22:48 +08:00
atdunbg beba491bcb 阶段性合并:动画重定向、碎裂共享库、第三人称玩法、编辑器面板与 vendored ufbx
- 动画:HumanoidRig 三层架构(standard 标准槽 / dialects 方言映射 / mapper 配对),
  指/趾统一走世界增量两遍式烘焙;AnimationAttach 拆为纯算法 + facade;修正 HumanoidBasis
  行走基朝向与跨骨架去重、根运动通道
- 物理:CharacterController 角色互碰撞 / shapeOffset / 接触事件;PhysicsWorld 接触合成与
  字符冲量判定
- 渲染与材质:Model 多材质按材质拆子 mesh 与贴图路径解析、DDS 直传、GLB 内嵌纹理落盘、
  不透明通道 alpha 固定 1.0、glTF FlipUVs 对齐;SceneSerializer 支持 meshIndex 与材质覆盖
- 脚本:FractureLib 抽为共享库(HostView 桥接)、WallFracture 参数直绑、新增
  ThirdPersonCamera / Follower / WASDMove 玩法;移除 GameDirector / PlayerController / Target
- 编辑器:新增 GamepadPanel、LogPanel(LogRing),改进 Inspector / 材质 / 统计 / 视口面板
- 依赖:vendored ufbx(FBX 5.x/6.x/7.x 全版本解析,替代 assimp 对 FBX 6.1 的拒读)
2026-10-11 22:22:19 +08:00
atdunbg 6854d53f11 per-mesh 引用:MeshRendererComponent.meshIndex(-1=整模型)贯穿渲染收集/物理 Mesh 碰撞(自动跟随)/场景序列化(缺省键向前兼容)/Inspector mesh 下拉 2026-10-09 12:38:05 +08:00
atdunbg 505217ff7e Spawn/Instantiate 选型约定落进基类与注册宏文档(判定问题+三条纪律;注册=Blueprintable 表态,UE 同题靠约定治理) 2026-10-09 12:31:33 +08:00
atdunbg f14aba4238 修自发光回归:glTF 导入端写 emissiveFactor(EMISSIVE_COLOR,贴图无 factor 时设白);Emissive 兜底纹理黑→白(黑兜底会把 factor/strength 一起乘没) 2026-10-09 00:12:35 +08:00
atdunbg 6447b5c0fa 材质表面参数接通:新增 emissiveFactor(无 emissive 贴图时的发光入口,glTF 语义)入 UBO/YAML/缓存键/两 shader;Transmission/Thickness 未勾 Transparent 时禁用编辑(不透明段 shader 不读) 2026-10-09 00:04:55 +08:00
atdunbg 235482733c 修渲染两处:透明段剔除背面(闭合体正反面乱序 over 混合=深度错乱,单面玻璃 UE/Unity 语义);prefilter mip0 128→256(roughness 0 镜面反射块状马赛克) 2026-10-08 23:50:11 +08:00
atdunbg c43adc339d 资产系统侧线第一波:ResolveProcedural 提为公共静态(键格式唯一定义点);Inspector 程序化模型入口;修 Prefab hover 误导性 not loaded;Content Browser 模型拖拽 → Outliner 创建实体 2026-10-08 23:36:08 +08:00
atdunbg 2cf8d6e076 修复碎片模型卸载的在飞 GPU 引用(vkDestroyBuffer in-use):门面批量卸载先 WaitDeviceIdle 再销毁(与 ReloadMaterialAsset 同一先例) 2026-10-08 23:23:15 +08:00
atdunbg affa1a7a56 碎片形状随机化:唯一代键替代命中点量化缓存键(每次命中重新随机 Voronoi),配套碎片模型生命周期回收(销毁次帧卸载 + OnDestroy 兜底) 2026-10-08 23:18:35 +08:00
atdunbg d4d37b4b28 修复 PIE 快照丢相机组件(序列化写出键 Attached ≠ 读侧 Camera)与子弹同帧多接触重复特效(Bullet 命中防重入) 2026-10-08 23:06:18 +08:00
atdunbg d2701a3d9b 新增程序化立方体与可碎墙验收:Model::CreateCube 五层链路 + WallFracture 碎片刚体群(命中碎裂/径向飞散/寿命清理) 2026-10-08 22:28:49 +08:00
atdunbg ed7cb417bd 新增 Prefab 系统:实体子树存取 + 编辑器 Save As Prefab/拖入实例化 + 脚本 Instantiate 2026-10-08 22:01:26 +08:00
atdunbg b89f431896 新增脚本实体底座与碰撞事件:Spawn/Destroy + OnCollisionEnter/Exit + TestShoot 验收脚本
- ScriptBehavior::Spawn/Destroy:Scene::CreateEntity / 延迟销毁队列直达(实现下沉 ScriptRuntime.cpp,避 Application 不完整类型)
- ScriptRuntime::DispatchCollisions:帧首把 PhysicsWorld::GetEvents 派发到涉事实例(Persisted 跳过、禁用不收;回调内 spawn/destroy 安全)
- TestShoot:左键 spawn 动态球刚体子弹,OnCollisionEnter 命中点生成 ImpactFx 特效实体并自毁,超时防泄漏
2026-10-08 21:45:10 +08:00
atdunbg 26e222a9cf 修复编辑器相机与 game 形态黑屏;中文字体与 Q 选择热键
FPS 式视角合成(yaw 世界轴/pitch 本地轴,永不 roll);
RMB 自由视角后环绕 pivot 失效待重建(修 Focus 后 MMB/滚轮
瞬移锁回);SceneRenderer 默认 kDisplayDirect(game 形态
Blit 空转+清屏黑屏根因);swapchain 优先 Opaque 合成;
ImGui 加载思源黑体(CJK 常用集);Q=纯选择不出杆;
新增 InputMap 动作映射
2026-10-08 17:02:39 +08:00
atdunbg 36b1fc0566 新增调试绘制体系:DebugDraw 服务 + 物理调试 pass + 脚本侧力学线
通用图元(线/箭头四翼头/球/胶囊/盒)瞬时+持久双缓冲聚合;
物理 pass 画刚体速度/重力/接触反力;角色 collider 与力学
线归位(引擎只画资产、脚本画行为);线宽按设备上限钳制
2026-10-08 17:02:27 +08:00
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 f73e52926f Reorganize directory layout: editor panels/, grouped shaders, drop stale header
- App/editor: move 7 panels into panels/ (Editor core stays at root)
- shaders/: group into pbr/ post/ debug/ env/ (runtime load paths updated)
- Delete outdated duplicate Engine/src/Renderer/VulkanAssetUploader.h
2026-10-07 09:52:17 +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 c083ee5cb7 Fix PIE deadlock when playing a scene without a primary camera
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.
2026-10-05 11:21:50 +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 42757bab36 AssetManager: drop immediate SaveGUIDRegistry on registration
2268fa5 added the dirty-flag deferred flush but left the immediate
save in place, so new GUID registrations wrote the registry twice
(once inline, once at the next FlushGUIDRegistry). Remove the inline
save so the deferred path is the only writer.
2026-10-05 00:39:48 +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 b78d9fb4c9 修改错误的TryBitsByBodyId返回值 2026-10-04 18:49:18 +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 230df67db7 fix: editor camera draw distance 100m -> 10km (UE semantics)
UE's far clip is effectively unlimited and shadow distance is an
independent parameter. Split kCameraFar (main projection, 10km) from
kShadowDistance (cascade splits, unchanged 100m) so distant geometry
is no longer clipped while shadow quality stays bit-identical.
Main projection now references the shared constants instead of
duplicated literals.
2026-10-04 14:49:27 +08:00
atdunbg 13055cc8ac fix: assimp INCOMPLETE flag no longer rejects animation-only FBX
Assimp flags every mesh-less scene as AI_SCENE_FLAGS_INCOMPLETE
(FBXConverter), so pure animation FBX never reached clip parsing -
the scene was discarded before ParseAnimations ran. Only treat null
scene / missing root node as hard failure; log INCOMPLETE as a
warning and continue. Final validity stays with ParsedModel::Valid().
2026-10-04 09:31:23 +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