Files
2026-10-25 03:29:04 -03:00

285 lines
9.1 KiB
C++

// Hammer++ editor: shared types. The editor is a thin UI layer over libhmap.
#pragma once
#include <hmap/hmap.h>
#include <cstdint>
#include <functional>
#include <string>
#include <unordered_map>
#include <vector>
#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<ObjRef> objs;
std::vector<int> faces; // faces (brush) / polygons (mesh) of the primary object
std::vector<int> 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<hmap::Brush> brushes;
std::vector<hmap::MeshObject> meshes;
std::vector<hmap::Entity> 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<hmap::BrushGeometry> brushGeo; // parallel to Map::brushes
std::vector<AABB> meshBounds; // parallel to Map::meshes
};
class FileDialog {
public:
using Callback = std::function<void(const std::string&)>;
void open(const std::string& title, const std::string& startDir, std::vector<std::string> exts, bool save, Callback cb,
const std::string& defaultName = "");
void draw();
private:
std::string title_, dir_, name_;
std::vector<std::string> 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<hmap::Map> 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<hmap::Light> lights;
uint64_t lightsVersion = 0;
FileDialog fileDialog;
std::vector<std::string> logLines;
bool showDemo = false, resetLayout = false, showHelp = false, wantQuit = false;
std::function<void()> pendingAction;
bool askDiscard = false;
void guarded(std::function<void()> 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<hmap::Brush*>& 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<void(const std::string&)> cb);
bool inEdit = false;
std::vector<std::string> modelFiles;
std::vector<std::string> 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