Files
CFDManager/CLAUDE.md
T
NotBigGhostandClaude Opus 5 11ff7b79b4 Начальный коммит: Vulkan-редактор SimVulcan + исследование KBC-LBM
Состояние на момент заведения репозитория.

C++ приложение (src/, shaders/, tests/) — минимальный редактор 3D-моделей
на Vulkan 1.3: орбитальная камера, три опорные сетки через начало координат,
загрузка .obj с режимами отображения. Весь Vulkan изолирован в src/vk/.

Исследование (docs/) — оригинальные статьи по KBC (docs/origins) и
Python-решатель D2Q9 KBC-N1 с AMR 2x и SDF+Bouzidi (docs/theory).

В решателе перед коммитом исправлены дефекты, найденные сверкой с
первоисточниками: относительный порог знаменателя энтропийного стабилизатора
(абсолютный вырождал KBC в LBGK на 77-99% узлов), заворот вход/выход в углах
домена, диагностика средней плотности по фиктивным узлам тела, зашитый
refine=2. Подробности — docs/theory/solver_2x_sdf/README.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 16:37:38 +03:00

10 KiB
Raw Blame History

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Overview

SimVulcan is a minimal Vulkan 1.3 / C++20 3D model editor. It renders a 3D editor space — three reference grid planes (XY, XZ, YZ) through the origin plus coloured X/Y/Z axes — viewed through an orbit camera, and loads/displays .obj models with selectable display modes (solid, wireframe, solid + wireframe).

The C++ side runs no simulation: it is a focused rendering skeleton with an ImGui interface (a Viewport control panel and a Mesh load panel). The repository also carries an unrelated Python research prototype under docs/theory/ — see Research prototype below; it is not part of the CMake build.

Build

Prerequisites: Vulkan SDK 1.3.290+ (provides glslangValidator for offline shader compilation), CMake 3.26+, Ninja, a C++20 compiler (MSVC 19.36+, gcc 11+, clang 14+). On macOS, Vulkan is via MoltenVK (Apple Silicon only).

cmake --preset windows-msvc-release
cmake --build --preset windows-msvc-release

Presets: windows-msvc-debug, windows-msvc-release, linux-gcc-release, linux-clang-release, macos-arm64-release (all Ninja, one dir per preset under build/<presetName>/). The first configure fetches dependencies via FetchContent (GLFW, GLM, volk, vk-bootstrap, VulkanMemoryAllocator, Dear ImGui, spdlog, tinyobjloader, Catch2) and needs network access. VK_NO_PROTOTYPES is set project-wide; Vulkan entry points load through volk.

Warnings come from simv_set_warnings (/W4 /permissive-, or -Wall -Wextra -Wpedantic -Wshadow -Wold-style-cast …); they are not errors. cmake/Sanitizers.cmake defines simv_enable_sanitizers (Debug-only ASan/UBSan) but no target currently calls it — wire it in manually when chasing memory bugs.

Running

The executable resolves SPIR-V relative to the working directory (FindSpvPath probes spirv/<rel> and current_path()/spirv/<rel>). The SimVulcan POST_BUILD step copies the compiled spirv/ tree and assets/ next to the executable, so run from the executable's own directory (build/<preset>/src/app/). main.cpp also probes several ../ ancestors for assets/meshes. Meshes load at runtime through the ImGui Mesh panel.

Running writes two files into the CWD: pipeline_cache.bin (serialised VkPipelineCache, reloaded on the next start) and ImGui's imgui.ini. Both are disposable — delete them if pipeline creation or the panel layout misbehaves.

ContextOptions::enableValidation / enableDebugUtils default to true in every build config, so the Khronos validation layer is requested even in Release; messages (error + warning severity) go through spdlog.

run.bat at the repo root is stale: it launches build/vs2022/src/app/Release/SimVulcan.exe, a path the Ninja presets never produce. Do not point users at it without fixing the path first.

Tests

Catch2 unit tests, pure CPU/math (mesh bounds + welding). No GPU required.

cmake --build --preset windows-msvc-debug --target simv_tests
ctest --preset windows-msvc-debug

windows-msvc-debug is the only preset with a testPreset — for the other configs, invoke ctest in build/<preset>/ directly.

Single test / subset — either through CTest (each TEST_CASE is registered individually by catch_discover_tests):

ctest --preset windows-msvc-debug -R "WeldVertices" -V

or by running the binary with a Catch2 name or tag filter:

./build/windows-msvc-debug/tests/simv_tests.exe "[decimator]"
./build/windows-msvc-debug/tests/simv_tests.exe --list-tests

Architecture

Library / target map

SimVulcan (exe) → simv_core, simv_vk, simv_mesh, simv_editor
simv_core   → simv_vk            (App owns Window + vk::Renderer; no Vulkan calls)
simv_editor → simv_core, simv_mesh   (Camera, input, ImGui panels; no Vulkan)
simv_mesh   → simv_core              (CPU mesh only; no Vulkan)
simv_vk     → third-party (volk, vk-bootstrap, VMA, GLFW, GLM, spdlog, imgui)
simv_shaders → glslangValidator (GLSL → SPIR-V)

Namespaces follow directories: simv::core, simv::vk, simv::mesh, simv::editor. Each library exports src/ as its include root, so includes are written module-qualified (#include "mesh/Mesh.h", #include "vk/Renderer.h").

Frame loop

main.cpp creates core::App, which owns the core::Window and a vk::Renderer, then runs the loop. Each frame Renderer::DrawFrame calls the UI callback (between ImGui NewFrame/Render), then records the scene: grid + mesh into one dynamic-rendering pass with a depth attachment, followed by ImGui, then presents (2 frames in flight, sync2 submits).

main.cpp wires the editor via the UI callback: it draws the panels, applies mouse input to the editor::Camera, and pushes the resulting state into the renderer (SetViewProj, SetRenderMode, SetGridVisible). Mesh loads go through MeshLoadPanel's callback → Renderer::SetMeshCpu.

Mesh load path

MeshLoadPanel lists *.obj in the mesh directory and kicks off mesh::LoadObjAsync (worker thread, std::future). The future is drained on the main thread at the top of MeshLoadPanel::Draw, so the OnLoaded callback — and therefore the GPU upload — always runs on the render thread. Loader exceptions surface as the panel's status string.

main.cpp's OnLoaded welds the mesh (WeldVertices, tolerance 1e-4), logs the counts, uploads via SetMeshCpu, and reframes the camera to the bbox radius. Despite the file name, mesh/MeshDecimator.h implements only spatial-hash vertex welding (collapse near-duplicates, drop degenerate triangles, recompute bounds) — there is no LOD/decimation.

Non-obvious invariants (read before editing)

  • All Vulkan lives in src/vk/. core/, mesh/, editor/ and app/ make no Vulkan API calls. vk::Renderer's public header is deliberately Vulkan-free (pImpl + glm/RenderMode only) so App can own it without pulling in volk. Keep it that way — do not leak Vk* types into the public interfaces of those modules.
  • Single render pass with depth. Scene (grid + mesh) and ImGui draw into one vkCmdBeginRendering pass that has both a colour and a D32_SFLOAT depth attachment. The ImGui backend is initialised with depthAttachmentFormat set so its pipeline matches the pass; the depth buffer is recreated with the swapchain.
  • Wireframe needs fillModeNonSolid. MeshRenderer builds a fill pipeline and a VK_POLYGON_MODE_LINE pipeline; the line pipeline uses a small depth bias so the overlay sits on top of the fill. The device feature is requested in Context. Culling is off (VK_CULL_MODE_NONE) — loaded models may have mixed winding.
  • Camera is fixed on the origin. editor::Camera orbits (yaw/pitch/distance) the world origin; loaded models are recentred there via a translate-only model matrix. Projection uses Vulkan clip space (GLM_FORCE_DEPTH_ZERO_TO_ONE + Y flip, isolated to Camera.cpp).
  • Buffers. vk::GpuMesh owns the model's vertex/index buffers (staged upload on the transfer queue). GridRenderer builds a static host-visible line buffer once. Both scene renderers use only a push constant — no descriptor sets.
  • Push-constant layout is a cross-file contract. MeshRenderer.cpp's anonymous MeshPC { mat4 mvp; vec4 color; } must stay byte-identical to the push_constant block in mesh.vert/mesh.frag; color.a is a flag, not alpha (1 = flat-shaded, 0 = constant colour for wireframe). GridRenderer pushes a bare mat4 (vertex stage only). Change either side and you must change both.
  • Swapchain recreation rebuilds sync objects. RecreateSwapDependent recreates the per-frame imageAvailable semaphores (a failed acquire can leave one signalled) and the per-swapchain-image renderFinished semaphores (the image count may change), then re-ensures both pipelines. Keep that ordering if you touch resize handling.
  • Mesh upload stalls the device. SetMeshCpu/ClearMesh call WaitIdle before touching GpuMesh — acceptable because loads are rare; do not copy that pattern into per-frame paths.

Shaders

GLSL under shaders/editor/ (mesh.{vert,frag}, grid.{vert,frag}). simv_shaders compiles each to build/<preset>/spirv/editor/<name>.spv targeting vulkan1.3, with shaders/ as the -I root (so #include "common/foo.glsl" would resolve). Adding a shader under that globbed dir is picked up automatically (CONFIGURE_DEPENDS). mesh.frag reconstructs a flat normal from screen-space derivatives, so the vertex stream carries only positions (tight float3, one binding, one attribute).

Logging

simv::core::Logger (spdlog-backed, singleton) with category-based throttling. Log via LogFmt(LogCategory, LogLevel, fmt, args...). Categories: Core, Vulkan, MeshIO, UI, Test. Per-category level, throttle interval and on/off are settable at runtime (SetMinLevel / SetThrottle / SetEnabled). Some low-level Vulkan code still calls spdlog:: directly.

Research prototype (docs/theory/)

Separate from the C++ application and from the CMake build: a Python Lattice Boltzmann (D2Q9, KBC-N1) research codebase — cylinder flow with a ×2 nested AMR patch and SDF+Bouzidi boundaries. GPU/CuPy only, no CPU fallback. Its documentation and the running experiment log are in Russian.

  • docs/theory/solver_2x_sdf/ — the component-split solver (run.py, run_blockage.py, run_factors.py); its README.md is the authoritative status/experiment log and maps every file to the physics it owns.
  • docs/theory/demos_gpu/ — demo runs that produce the notebook's figures/GIFs; they import physics from solver_2x_sdf, never duplicate it.
  • docs/theory/kbc_lbm.ipynb — the write-up; it embeds pre-rendered artefacts and does not execute the simulations.
  • docs/origins/ — source PDFs behind the theory.

Do not fold this into the CMake build, and do not treat it as dead code — it is active research with a documented experiment history.