// Hammer++ editor: shared types. The editor is a thin UI layer over libhmap. #pragma once #include #include #include #include #include #include #include "imgui.h" #include "raylib.h" #include "render.h" namespace hpp { using hmap::AABB; using hmap::Mat34; using hmap::Plane; using hmap::Vec2; using hmap::Vec3; enum class Tool { Select, Block, Entity, Clip }; enum class SelMode { Object, Face, Vertex }; enum class Shading { Wire, Flat, Lit, Baked }; enum class ViewKind { Persp, Top, Front, Side }; enum class ObjType { Brush, Mesh, Entity }; enum class Shape { Box, Wedge, Cylinder, Cone, Stairs, Sphere, Torus, Terrain }; struct ObjRef { ObjType type = ObjType::Brush; uint32_t id = 0; bool operator==(const ObjRef& o) const { return type == o.type && id == o.id; } }; struct Selection { std::vector objs; std::vector faces; // faces (brush) / polygons (mesh) of the primary object std::vector verts; // vertex indices of the primary mesh bool has(const ObjRef& r) const; void clear() { objs.clear(); faces.clear(); verts.clear(); } bool empty() const { return objs.empty(); } ObjRef primary() const { return objs.empty() ? ObjRef{} : objs.back(); } }; struct HRay { Vec3 o, d; }; struct Hit { bool hit = false; float t = 1e30f; ObjRef obj; int face = -1; Vec3 point, normal; }; struct Viewport { const char* title = ""; ViewKind kind = ViewKind::Persp; bool open = true; RenderTexture2D rt = {}; int w = 0, h = 0; // perspective camera Vec3 pos{0, 256, 512}; float yaw = 0, pitch = -25, fov = 70, flySpeed = 320; // orthographic camera Vec3 center{0, 0, 0}; float zoom = 0.5f; // pixels per world unit // per-frame interaction state ImVec2 imgMin{0, 0}; bool hovered = false, flying = false; bool ortho() const { return kind != ViewKind::Persp; } Vec3 forward() const; Vec3 right() const; Vec3 up() const; int depthAxis() const; // ortho: world axis along the view direction Camera3D camera() const; HRay ray(float sx, float sy) const; bool project(const Vec3& p, float& sx, float& sy) const; // false when behind the camera Vec3 planePoint(float sx, float sy, float depth) const; // ortho: world point on the plane at `depth` }; struct DragSnap { std::vector brushes; std::vector meshes; std::vector entities; }; struct DragState { enum Kind { None, Move, Resize, Gizmo, BoxSelect, Block } kind = None; Viewport* view = nullptr; Vec3 startWorld; ImVec2 startScreen{0, 0}, curScreen{0, 0}; bool moved = false; bool tookUndo = false; int hx = 0, hy = 0; // resize handle float rectU0 = 0, rectU1 = 0, rectV0 = 0, rectV1 = 0; // selection rect in view-plane coordinates int axis = -1; // gizmo axis float s0 = 0; Vec3 origin; Vec3 blockA, blockB; DragSnap snap; }; struct SceneCache { uint64_t version = 0; std::vector brushGeo; // parallel to Map::brushes std::vector meshBounds; // parallel to Map::meshes }; class FileDialog { public: using Callback = std::function; void open(const std::string& title, const std::string& startDir, std::vector exts, bool save, Callback cb, const std::string& defaultName = ""); void draw(); private: std::string title_, dir_, name_; std::vector exts_; bool save_ = false, pending_ = false; Callback cb_; }; class Editor { public: // ------------------------------------------------------------ lifecycle void init(); void frame(); // one full UI frame; call between rlImGuiBegin() and rlImGuiEnd() void shutdown(); // ------------------------------------------------------------ document hmap::Map doc; std::string docPath; bool dirty = false; uint64_t docVersion = 1; hmap::EntityRegistry entdefs; std::string assetRoot = "assets"; std::vector undoStack, redoStack; // ------------------------------------------------------------ state Selection sel; Tool tool = Tool::Select; SelMode selMode = SelMode::Object; Shading shading = Shading::Lit; bool snapOn = true; float grid = 16; int curMaterial = 0; Shape shape = Shape::Box; bool createAsMesh = false; int shapeSides = 12; int stairSteps = 8; std::string curEntity = "prop_static"; std::string curModel; float blockHeight = 64; bool saveCompiledToo = false; hmap::CompiledMap compiled; bool hasCompiled = false, compiledStale = false; hmap::CompileOptions compileOpt; hmap::FbxOptions fbxOpt; Renderer renderer; Viewport views[4]; DragState drag; SceneCache cache; std::vector lights; uint64_t lightsVersion = 0; FileDialog fileDialog; std::vector logLines; bool showDemo = false, resetLayout = false, showHelp = false, wantQuit = false; std::function pendingAction; bool askDiscard = false; void guarded(std::function act); // runs `act`, asking first when there are unsaved changes bool panelOutliner = true, panelInspector = true, panelAssets = true, panelLog = true; // clip tool bool clipHasA = false, clipHasB = false; Vec3 clipA, clipB; int clipMode = 0; // 0 keep front, 1 keep back, 2 keep both int clipView = 0; // ------------------------------------------------------------ commands (editor.cpp) void log(const char* fmt, ...); void newMap(bool demo); bool openMap(const std::string& path); bool saveMap(const std::string& path); void touch(); // document changed void pushUndo(); void editBegin(); // call before applying an inspector edit (one undo entry per edit session) void undo(); void redo(); void clearSelection(); void select(const ObjRef& r, bool additive); void selectAll(); void pruneSelection(); bool selectedBrushes(std::vector& out); AABB objectBounds(const ObjRef& r); AABB selectionBounds(); Vec3 selectionCenter(); hmap::Brush* findBrush(uint32_t id) { return doc.findBrush(id); } const hmap::EntityClass* classOf(const hmap::Entity& e) const { return entdefs.find(e.classname); } AABB entityBounds(const hmap::Entity& e); Vec3 entityColor(const hmap::Entity& e) const; void deleteSelection(); void duplicateSelection(); void hideSelection(bool hide); void unhideAll(); void hollowSelection(float thickness); void convertSelectionToMesh(); void tieSelectionToEntity(const std::string& cls); void untieSelection(); void clipSelection(const Plane& p, int mode); void createPrimitive(Vec3 mn, Vec3 mx); void createEntityAt(const Vec3& origin); void applyMaterialToSelection(int material); void paintHit(const Hit& h, bool wholeObject); void flipSelection(); void importObjFile(const std::string& path); DragSnap captureSnap(); void restoreSnap(const DragSnap& s); void applyMatrix(const Mat34& m, bool lockTexture); void translateSelectedVerts(const Vec3& d); void compileNow(); void exportFbx(const std::string& path); void exportObj(const std::string& path); void exportCompiled(const std::string& path); const SceneCache& scene(); void refreshGpu(); Hit pick(const HRay& r); float snapValue(float v) const { return snapOn ? std::round(v / grid) * grid : v; } // ------------------------------------------------------------ ui (ui.cpp) void drawUI(); void drawMenu(); void drawToolbar(); void drawOutliner(); void drawInspector(); void drawAssets(); void drawLog(); void drawHelp(); void shortcuts(); void buildLayout(ImGuiID dock); bool propEditor(hmap::Entity& e, const hmap::PropDef& p); void entityInspector(hmap::Entity& e, bool brushEntity); void faceInspector(); void transformInspector(); void openModelPicker(std::function cb); bool inEdit = false; std::vector modelFiles; std::vector textureFiles; bool filesScanned = false; void scanAssets(); std::string relativeToAssets(const std::string& path) const; // ------------------------------------------------------------ viewports (viewport.cpp) void drawViewport(Viewport& v); void viewInput(Viewport& v, bool active); void toolInput3D(Viewport& v, const ImVec2& m); void toolInput2D(Viewport& v, const ImVec2& m); void renderView(Viewport& v); void drawGrid(Viewport& v); void drawScene3D(Viewport& v); void drawWire(Viewport& v); void drawEntities(Viewport& v); void drawGizmo(Viewport& v); void drawOverlay2D(Viewport& v); void focusSelection(); bool gizmoVisible() const; Vec3 gizmoCenter(); int gizmoHit(Viewport& v, const ImVec2& m); bool selectionRect(Viewport& v, float& u0, float& u1, float& v0, float& v1); }; void applyTheme(); void firstPlace(float fx, float fy, float fw, float fh); } // namespace hpp