285 lines
9.1 KiB
C++
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
|