From a9ae435321d9a4f6f9f285eb6022b62bfcf158a8 Mon Sep 17 00:00:00 2001 From: Acrux Development Date: Sun, 25 Oct 2026 03:29:04 -0300 Subject: [PATCH] Add project files. --- .gitignore | 5 + CMakeLists.txt | 37 + README.md | 183 + assets/entities.def | 22 + assets/models/crate.mtl | 2 + assets/models/crate.obj | 28 + assets/models/pyramid.obj | 17 + cmake/Dependencies.cmake | 51 + docs/FORMAT.md | 48 + editor/CMakeLists.txt | 16 + editor/editor.cpp | 790 + editor/editor.h | 284 + editor/main.cpp | 39 + editor/render.cpp | 252 + editor/render.h | 79 + editor/ui.cpp | 1131 + editor/viewport.cpp | 1002 + examples/CMakeLists.txt | 2 + examples/viewer.cpp | 117 + libhmap/CMakeLists.txt | 27 + libhmap/include/hmap/hmap.h | 359 + libhmap/include/hmap/hmap_c.h | 84 + libhmap/include/hmap/hmath.h | 226 + libhmap/src/bsp.cpp | 301 + libhmap/src/capi.cpp | 95 + libhmap/src/compile.cpp | 500 + libhmap/src/entdefs.cpp | 188 + libhmap/src/fbx.cpp | 464 + libhmap/src/geometry.cpp | 655 + libhmap/src/io.cpp | 410 + libhmap/src/map.cpp | 106 + libhmap/src/sample.cpp | 95 + samples/demo.fbx | Bin 0 -> 3185132 bytes samples/demo.hmap | Bin 0 -> 77136 bytes samples/demo.mtl | 30 + samples/demo.obj | 56568 +++++++++++++++++ samples/demo_runtime.hmap | Bin 0 -> 735316 bytes tests/CMakeLists.txt | 19 + tests/editor_headless/README.md | 12 + tests/editor_headless/impl.cpp | 24 + tests/editor_headless/stubs/imgui.h | 98 + tests/editor_headless/stubs/imgui_internal.h | 13 + tests/editor_headless/stubs/imgui_stdlib.h | 7 + tests/editor_headless/stubs/raylib.h | 103 + tests/editor_headless/stubs/raymath.h | 4 + tests/editor_headless/stubs/rlImGui.h | 6 + tests/editor_headless/stubs/rlgl.h | 12 + tests/editor_headless/test_editor.cpp | 324 + tests/test_hmap.cpp | 474 + tools/CMakeLists.txt | 4 + tools/hmap_info.cpp | 35 + tools/hmap_sample.cpp | 30 + 52 files changed, 65378 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 assets/entities.def create mode 100644 assets/models/crate.mtl create mode 100644 assets/models/crate.obj create mode 100644 assets/models/pyramid.obj create mode 100644 cmake/Dependencies.cmake create mode 100644 docs/FORMAT.md create mode 100644 editor/CMakeLists.txt create mode 100644 editor/editor.cpp create mode 100644 editor/editor.h create mode 100644 editor/main.cpp create mode 100644 editor/render.cpp create mode 100644 editor/render.h create mode 100644 editor/ui.cpp create mode 100644 editor/viewport.cpp create mode 100644 examples/CMakeLists.txt create mode 100644 examples/viewer.cpp create mode 100644 libhmap/CMakeLists.txt create mode 100644 libhmap/include/hmap/hmap.h create mode 100644 libhmap/include/hmap/hmap_c.h create mode 100644 libhmap/include/hmap/hmath.h create mode 100644 libhmap/src/bsp.cpp create mode 100644 libhmap/src/capi.cpp create mode 100644 libhmap/src/compile.cpp create mode 100644 libhmap/src/entdefs.cpp create mode 100644 libhmap/src/fbx.cpp create mode 100644 libhmap/src/geometry.cpp create mode 100644 libhmap/src/io.cpp create mode 100644 libhmap/src/map.cpp create mode 100644 libhmap/src/sample.cpp create mode 100644 samples/demo.fbx create mode 100644 samples/demo.hmap create mode 100644 samples/demo.mtl create mode 100644 samples/demo.obj create mode 100644 samples/demo_runtime.hmap create mode 100644 tests/CMakeLists.txt create mode 100644 tests/editor_headless/README.md create mode 100644 tests/editor_headless/impl.cpp create mode 100644 tests/editor_headless/stubs/imgui.h create mode 100644 tests/editor_headless/stubs/imgui_internal.h create mode 100644 tests/editor_headless/stubs/imgui_stdlib.h create mode 100644 tests/editor_headless/stubs/raylib.h create mode 100644 tests/editor_headless/stubs/raymath.h create mode 100644 tests/editor_headless/stubs/rlImGui.h create mode 100644 tests/editor_headless/stubs/rlgl.h create mode 100644 tests/editor_headless/test_editor.cpp create mode 100644 tests/test_hmap.cpp create mode 100644 tools/CMakeLists.txt create mode 100644 tools/hmap_info.cpp create mode 100644 tools/hmap_sample.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a66268b --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/build/ +/out/ +/cmake-build-*/ +*.ini +/.vs/ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..c9c5b55 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,37 @@ +cmake_minimum_required(VERSION 3.20) +project(HammerPP VERSION 0.1.0 DESCRIPTION "Hammer++ map editor" LANGUAGES C CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE) +endif() + +option(HPP_BUILD_EDITOR "Build the Hammer++ editor (raylib + Dear ImGui + rlImGui)" ON) +option(HPP_BUILD_VIEWER "Build the minimal raylib example viewer" ON) +option(HPP_BUILD_TOOLS "Build the command line tools (hmap_info, hmap_sample)" ON) +option(HPP_BUILD_TESTS "Build the libhmap test-suite" ON) + +# libhmap: map model, BSP/CSG compiler, lighting, .hmap format, FBX/OBJ export. No third-party dependencies. +add_subdirectory(libhmap) + +if(HPP_BUILD_TOOLS) + add_subdirectory(tools) +endif() + +if(HPP_BUILD_TESTS) + enable_testing() + add_subdirectory(tests) +endif() + +if(HPP_BUILD_EDITOR OR HPP_BUILD_VIEWER) + include(cmake/Dependencies.cmake) # raylib (+ Dear ImGui and rlImGui for the editor) + if(HPP_BUILD_VIEWER) + add_subdirectory(examples) + endif() + if(HPP_BUILD_EDITOR) + add_subdirectory(editor) + endif() +endif() diff --git a/README.md b/README.md new file mode 100644 index 0000000..1d93111 --- /dev/null +++ b/README.md @@ -0,0 +1,183 @@ +# 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*. diff --git a/assets/entities.def b/assets/entities.def new file mode 100644 index 0000000..37bfbdc --- /dev/null +++ b/assets/entities.def @@ -0,0 +1,22 @@ +# Hammer++ custom entity definitions. Loaded on start-up after the built-in set, so you can add +# your game's entities here - or redefine a built-in class by repeating its name. +# +# @point "