# Hammer++ A level editor in the spirit of Valve Hammer, written in **C++17 with raylib, Dear ImGui (via rlImGui) and CMake**. It edits two kinds of geometry side by side and compiles both into one renderable, collidable, exportable map. | | **Brushes** (BSP) | **Meshes** | |---|---|---| | Representation | convex solid = intersection of planes | free-form polygon mesh | | Best for | rooms, walls, floors, collision | curves, terrain, organic detail, imported OBJ | | Compile | CSG (hidden faces removed) + BSP collision tree | straight to render batches, smooth normals | | Tools | block, clip, hollow, resize handles, tie-to-entity | vertex mode, extrude, noise terrain, flip, convert from brush | Plus entities and props (models loaded through raylib), UV/texture tools, baked vertex lighting with BSP-traced shadows, **FBX / OBJ export**, the **`.hmap` format** and a small **C++ / C runtime API** to load it in other apps. > ### Status: v0.1 foundation - read this first > * **`libhmap`** (geometry, CSG, BSP, lighting, file format, FBX/OBJ, C API) is covered by 2,939 automated checks and is > clean under AddressSanitizer + UBSan, including a randomised BSP-vs-brute-force test and a file-corruption fuzz test. > * **The editor** compiles and its logic is tested headless (1,515 checks: commands, undo, picking, camera maths, > scripted mouse drags for move / resize / box-select / gizmo / paint / vertex edit). **The GUI itself has not been run > against real raylib/ImGui yet** - the authoring environment had no network or GPU. Your first build is the real > integration test; if the dependency versions drift, see *Pinning dependencies*. > * **FBX** output follows the binary 7.4 layout and passes two independent structural parsers, but has **not** been > opened in Blender / Unity / Unreal yet. Try `samples/demo.fbx` first. > * Hammer parity is a **goal, not a fact**: see the checklist at the bottom for what exists and what does not. ## Build ```bash cmake -B build cmake --build build -j ./build/editor/hammerpp # opens the demo map (File > New for an empty room) ./build/examples/hmap_viewer samples/demo_runtime.hmap # tiny raylib-only viewer (example of integration) ./build/tools/hmap_info samples/demo.hmap --fbx out.fbx # inspect / convert from the command line ctest --test-dir build # library tests ``` Needs CMake >= 3.20, a C++17 compiler and OpenGL 3.3. raylib, Dear ImGui and rlImGui are fetched automatically. `libhmap` itself has **no** third-party dependencies (only `std::thread` for light baking). | CMake option | Default | | |---|---|---| | `HPP_BUILD_EDITOR` | ON | the editor (fetches raylib + ImGui + rlImGui) | | `HPP_BUILD_VIEWER` | ON | minimal raylib viewer example | | `HPP_BUILD_TOOLS` | ON | `hmap_info`, `hmap_sample` | | `HPP_BUILD_TESTS` | ON | `test_hmap`; add `-DHPP_BUILD_EDITOR_LOGIC_TESTS=ON` for the headless editor tests (Linux) | | `HPP_USE_SYSTEM_RAYLIB` | OFF | use an installed raylib | **Pinning dependencies.** Defaults: raylib `5.5` (the API the editor is written against), Dear ImGui `docking` branch, rlImGui `main`. They are cache variables, so a mismatch is a one-line fix: ```bash cmake -B build -DHPP_RAYLIB_TAG=5.5 -DHPP_IMGUI_TAG=v1.91.9b-docking -DHPP_RLIMGUI_TAG= ``` If rlImGui fails to compile, pin ImGui and rlImGui to a matching pair (ImGui 1.92 changed texture/font handling, so rlImGui and ImGui must be from the same era). Without the docking branch the editor still builds; windows just float. ## Using the editor Default layout: Top / Front / Side / 3D views, Outliner, Inspector, Assets (materials, models, entities) and Log. Everything is dockable; *View > Reset window layout* restores it. **F1** shows all controls in-app. | | | |---|---| | 3D view | hold **right mouse** + `W A S D`, `Q`/`E` down/up, `Shift` fast, wheel = speed. Wheel = dolly, middle drag = pan | | 2D views | wheel zooms to cursor, right/middle drag pans, `F` frames the selection | | Tools | `1` Select, `2` Block, `3` Entity, `4` Clip | | Select | click, `Shift`/`Ctrl` adds, drag empty space = box select, drag = move, orange handles = resize, gizmo arrows in 3D | | Block | drag a rectangle in a 2D view (height from *Create > Default height*) or click a surface in 3D. Shapes: box, wedge, cylinder, cone, stairs, sphere, torus, terrain patch; "as editable mesh" toggle | | Entity | pick a class in *Assets > Entities* (props: pick a model in *Assets > Models*), click to place | | Clip | two clicks in a 2D view, `Tab` = which side to keep, `Enter` = cut | | Surfaces | `Alt+click` paints the current material (`+Shift` = whole object). *Face* mode + Inspector: material, offset, scale, rotation, flip, **Fit** | | Meshes | *Vertex* mode: click / box-select vertices, drag them. Inspector > Mesh tools: extrude faces, noise terrain, flip normals | | Brush entities | select brushes, `Ctrl+T` (or *Tie to entity*) -> `func_door`, `trigger_once`, `func_detail`... the Inspector then shows the entity's properties | | Misc | `Ctrl+Z/Y` undo/redo, `Ctrl+D` duplicate, `Del`, `H` hide / `Alt+H` unhide, `G` snap, `[` `]` grid, **`F9` build** | **Build (F9)** runs CSG, builds the BSP and (optionally) bakes lighting. Switch the view to *Baked* to preview it; *Lit* is the live preview (ambient + sun + up to 16 point lights per pixel, no build needed); *Flat* and *Wireframe* too. **Entities** are defined in a tiny FGD-style text format (`assets/entities.def`, built-ins in `libhmap/src/entdefs.cpp`) and the Inspector builds its forms from it: ``` @point npc_spawner "NPC spawner" color(255,110,110) size(12,32,12) dir npc_type choice "NPC type" "grunt" "grunt=Grunt,sniper=Sniper,boss=Boss" count int "Count" "3" ``` Types: `string int float bool color255 vec3 choice model material target`. `@brush` classes become brush entities. ## Conventions Right-handed, **+Y up**, forward **-Z**, `unitsPerMeter` (default 64) converts to metres. CCW triangles. UV `v` grows downward (image space). Euler angles are `(pitch, yaw, roll)` degrees. Texture mapping is planar per face (`Material::tileSize` world units per repeat; per-face offset / scale / rotation). ## Integrating the format in another app C++ (`#include `, link `hmap::hmap`): ```cpp hmap::CompiledMap map; if (hmap::Status s = hmap::loadCompiled("level.hmap", map); !s) { /* s.message */ } for (const hmap::Batch& b : map.batches) { // one draw call each: model + material + (brush|mesh) const hmap::Material& m = map.materials[b.material]; // texture path, tint, tile size // upload b.vertices (pos, normal, uv, color = baked light) and b.indices } hmap::TraceResult hit = map.trace(from, to); // BSP brushes + solid meshes bool underwater = map.pointContents(eye) & hmap::CONTENTS_WATER; for (const hmap::Entity& e : map.entities) // lights, spawns, props, triggers... if (e.classname == "prop_static") spawn(e.get("model"), e.transform()); ``` `loadCompiled` accepts runtime files, editor files with a compiled chunk, and plain source files (it compiles on the fly). `hmap::loadMap` / `saveMap` give you the editable `hmap::Map`; the same library can create maps in code (`makeBox`, `hollowBrush`, `meshSphere`, ... then `compile`). See `examples/viewer.cpp` for a complete raylib consumer and `tools/` for the command line tools. Plain C (`#include `) for engines, Python/C#/Rust FFI: ```c hmap_map* m = hmap_load("level.hmap", err, sizeof err); for (uint32_t i = 0; i < hmap_batch_count(m); ++i) { hmap_batch b; hmap_get_batch(m, i, &b); /* draw */ } hmap_trace t; hmap_trace_segment(m, from, to, HMAP_CONTENTS_SOLID, &t); hmap_free(m); ``` The byte layout is in [`docs/FORMAT.md`](docs/FORMAT.md) if you would rather write your own reader. Editor saves are source-only unless *Embed compiled data* is ticked; *File > Export > Runtime map* writes a compact compiled-only file. ## Export * **FBX** (binary 7.4): one Model + Geometry per world / brush-entity with every material as a sub-mesh, per-vertex normals, UVs (V flipped to FBX convention), baked lighting as a colour layer, diffuse colour + texture path per material, entities and props as Null nodes carrying their key-values as custom properties. Y-up right-handed, `UnitScaleFactor` derived from *Units per metre* (and the export *Scale* option). No skeletons or animation. * **OBJ + MTL**, and the **runtime `.hmap`**. * Import: **OBJ** as an editable mesh. Props use raylib's loaders: obj, gltf/glb, iqm, vox, m3d. ## Project layout ``` libhmap/ dependency-free library: model, geometry, CSG, BSP, lighting, .hmap IO, FBX/OBJ, entity defs, C API editor/ raylib + ImGui front-end: editor.cpp (commands), viewport.cpp (cameras, tools, gizmo), render.cpp (one lit shader for brush + mesh batches, caches), ui.cpp (docking UI, inspector, file dialog) examples/ viewer.cpp - minimal raylib consumer tools/ hmap_info, hmap_sample tests/ libhmap tests + optional headless editor tests samples/ demo.hmap / demo_runtime.hmap / demo.fbx / demo.obj assets/ entities.def, sample props (crate.obj, pyramid.obj); put textures in assets/materials, models in assets/models ``` How the pieces fit: brushes -> `buildBrushGeometry` (plane clipping) -> CSG against other solids -> triangle batches **and** collision brushes -> BSP tree (nodes split on brush planes, leaves hold brush lists; used for `trace`, `pointContents` and light shadows). Meshes skip the BSP: they become batches (smooth/flat normals) and, if they collide, collision triangles. The editor viewport draws the *same* shader for per-object edit batches and for compiled batches. ## Hammer parity checklist | Feature | Status | |---|---| | Brush editing (block, clip, hollow, resize, move, rotate, scale, mirror) | yes (no vertex editing of brushes - convert to mesh) | | 2D + 3D views, grid snapping, undo/redo, hide, duplicate | yes | | Texture application, offset/scale/rotation/fit, texture lock on move | yes (lock for translation only) | | Entities with auto-generated property forms, brush entities, models/props | yes (own definition format; no FGD import) | | Displacements | partial: terrain meshes + noise + vertex edit, no sculpt brush | | Curves / arches / stairs primitives | stairs, cylinder, cone, sphere, torus yes; arch no | | Compile: CSG + BSP + lighting | yes (vertex lighting with shadows; **no** lightmaps, PVS/portals or game-specific BSP output) | | Visgroups, prefabs/instances, groups | no (hide only) | | Entity I/O connections, overlays/decals, cordon, measure tool, skybox | no | | Export | FBX, OBJ, own format (more than stock Hammer) | Ideas for next steps: prefab/instance support, sculpt brush for terrain, lightmap atlas, portal/PVS, FGD import, glTF export, multi-mesh vertex editing, material browser thumbnails with search. ## Troubleshooting * *Black / grey viewports* - needs OpenGL 3.3. Try `Shading = Flat` to rule out the lit shader. * *Textures missing* - put images in `assets/materials`, *Assets > Materials > Add texture*. Without a file a tinted checker is generated from the material colour. * *Props show as boxes* - the model path is relative to `assets/`; check *Assets > Models* and the Log panel. * *Layout broken after an update* - delete `hammerpp.ini` or *View > Reset window layout*.