Add project files.
This commit is contained in:
commit
a9ae435321
52 files changed
+65378
No files matched your search
@@ -0,0 +1,359 @@
|
||||
// hmap/hmap.h - Hammer++ map library. One include gives you everything:
|
||||
// * the editable map model (brushes = BSP/CSG solids, mesh objects, entities, props)
|
||||
// * geometry helpers and primitives
|
||||
// * compile(): CSG + BSP collision tree + render batches (+ optional baked vertex lighting)
|
||||
// * the .hmap binary format (load/save, source and/or compiled data)
|
||||
// * FBX (binary 7.4) and OBJ export
|
||||
//
|
||||
// Conventions: right-handed, +Y up, forward = -Z. Triangles are CCW seen from the front.
|
||||
// UVs use image space (v grows downwards). Euler angles are (pitch, yaw, roll) in degrees.
|
||||
#pragma once
|
||||
#include "hmath.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace hmap {
|
||||
|
||||
constexpr uint32_t kFormatVersion = 1;
|
||||
|
||||
struct Status {
|
||||
bool ok = true;
|
||||
std::string message;
|
||||
explicit operator bool() const { return ok; }
|
||||
static Status Ok() { return Status{}; }
|
||||
static Status Fail(std::string m) { Status s; s.ok = false; s.message = std::move(m); return s; }
|
||||
};
|
||||
|
||||
// ------------------------------------------------------------------ flags
|
||||
enum Contents : uint32_t {
|
||||
CONTENTS_SOLID = 1, // blocks movement + light, clips other brushes' hidden faces
|
||||
CONTENTS_WATER = 2, // volume, rendered, does not block
|
||||
CONTENTS_CLIP = 4, // invisible collision
|
||||
CONTENTS_TRIGGER = 8, // invisible, non-blocking volume
|
||||
CONTENTS_SKY = 16
|
||||
};
|
||||
enum ObjectFlags : uint32_t {
|
||||
OBJ_HIDDEN = 1, // editor visibility only (still compiled)
|
||||
OBJ_NOCOLLIDE = 2 // meshes: do not collide / cast baked shadows
|
||||
};
|
||||
enum FaceFlags : uint32_t { FACE_NODRAW = 1 };
|
||||
|
||||
// ------------------------------------------------------------------ model
|
||||
struct UVInfo { // planar projection along the dominant normal axis
|
||||
Vec2 offset{0, 0}; // world units
|
||||
Vec2 scale{1, 1}; // 1 = one texture repeat per Material::tileSize
|
||||
float rotation = 0; // degrees
|
||||
};
|
||||
|
||||
struct Material {
|
||||
std::string name; // unique key, e.g. "dev/orange"
|
||||
std::string texture; // path relative to the asset root, may be empty
|
||||
Vec2 tileSize{64, 64}; // world units per texture repeat
|
||||
Vec3 color{1, 1, 1}; // tint
|
||||
uint32_t flags = 0;
|
||||
};
|
||||
|
||||
struct Face { // one side of a brush
|
||||
Plane plane;
|
||||
int32_t material = 0;
|
||||
UVInfo uv;
|
||||
uint32_t flags = 0;
|
||||
};
|
||||
|
||||
// A convex solid defined as the intersection of half-spaces (Hammer/Quake style).
|
||||
struct Brush {
|
||||
uint32_t id = 0;
|
||||
uint32_t owner = 0; // 0 = world, otherwise id of the brush entity that owns it
|
||||
uint32_t contents = CONTENTS_SOLID;
|
||||
uint32_t flags = 0;
|
||||
std::vector<Face> faces;
|
||||
};
|
||||
|
||||
struct MeshPoly { // convex polygon of a mesh object
|
||||
std::vector<uint32_t> idx; // CCW from the front
|
||||
std::vector<Vec2> uv; // empty = auto-project with `proj`
|
||||
int32_t material = 0;
|
||||
uint32_t smooth = 0; // 0 = flat shaded; same non-zero id = smooth together
|
||||
UVInfo proj;
|
||||
};
|
||||
|
||||
// Free-form polygon mesh ("detail" geometry): arbitrary topology, not part of the BSP tree.
|
||||
struct MeshObject {
|
||||
uint32_t id = 0;
|
||||
uint32_t owner = 0;
|
||||
uint32_t flags = 0;
|
||||
std::string name;
|
||||
std::vector<Vec3> verts;
|
||||
std::vector<MeshPoly> polys;
|
||||
};
|
||||
|
||||
struct KeyValue { std::string key, value; };
|
||||
|
||||
struct Entity { // point entities and props; brush entities own brushes via Brush::owner
|
||||
uint32_t id = 0;
|
||||
uint32_t flags = 0;
|
||||
std::string classname;
|
||||
Vec3 origin;
|
||||
Vec3 angles; // pitch, yaw, roll (deg)
|
||||
Vec3 scale{1, 1, 1};
|
||||
std::vector<KeyValue> props;
|
||||
|
||||
const std::string* find(const std::string& key) const;
|
||||
std::string get(const std::string& key, const std::string& def = "") const;
|
||||
float getFloat(const std::string& key, float def) const;
|
||||
int getInt(const std::string& key, int def) const;
|
||||
bool getBool(const std::string& key, bool def) const;
|
||||
Vec3 getVec3(const std::string& key, const Vec3& def) const;
|
||||
Vec3 getColor(const std::string& key, const Vec3& def) const; // "255 128 0" -> 0..1
|
||||
void set(const std::string& key, const std::string& value);
|
||||
void erase(const std::string& key);
|
||||
Mat34 transform() const; // translate * rotate * scale
|
||||
Vec3 forward() const;
|
||||
};
|
||||
|
||||
struct Settings {
|
||||
Vec3 ambient{0.16f, 0.18f, 0.22f};
|
||||
float gridSize = 16;
|
||||
float unitsPerMeter = 64;
|
||||
};
|
||||
|
||||
struct Map {
|
||||
std::string name = "untitled";
|
||||
Settings settings;
|
||||
std::vector<Material> materials;
|
||||
std::vector<Brush> brushes;
|
||||
std::vector<MeshObject> meshes;
|
||||
std::vector<Entity> entities;
|
||||
uint32_t nextId = 1;
|
||||
|
||||
uint32_t newId() { return nextId++; }
|
||||
int findMaterial(const std::string& name) const;
|
||||
int addMaterial(const std::string& name, const std::string& texture = ""); // dedupes by name
|
||||
Brush* findBrush(uint32_t id);
|
||||
MeshObject* findMesh(uint32_t id);
|
||||
Entity* findEntity(uint32_t id);
|
||||
const Brush* findBrush(uint32_t id) const;
|
||||
const MeshObject* findMesh(uint32_t id) const;
|
||||
const Entity* findEntity(uint32_t id) const;
|
||||
};
|
||||
|
||||
// ------------------------------------------------------------------ geometry
|
||||
using Polygon = std::vector<Vec3>;
|
||||
|
||||
struct BrushGeometry {
|
||||
std::vector<Polygon> polys; // parallel to Brush::faces; empty polygon = redundant face
|
||||
AABB bounds;
|
||||
std::vector<Vec3> points; // all polygon vertices
|
||||
bool valid = false;
|
||||
};
|
||||
|
||||
BrushGeometry buildBrushGeometry(const Brush& b);
|
||||
AABB brushBounds(const Brush& b);
|
||||
AABB meshBounds(const MeshObject& m);
|
||||
Vec3 polyNormal(const Polygon& p);
|
||||
|
||||
// Raycasts (direction need not be normalised; t is in units of d). Return nearest hit.
|
||||
bool rayBrush(const Brush& b, const Vec3& o, const Vec3& d, float& t, int& face);
|
||||
bool rayMesh(const MeshObject& m, const Vec3& o, const Vec3& d, float& t, int& poly);
|
||||
|
||||
// Texture mapping shared by the editor and the compiler.
|
||||
void uvAxes(const Vec3& normal, const UVInfo& uv, Vec3& u, Vec3& v);
|
||||
Vec2 computeUV(const Vec3& p, const Vec3& normal, const UVInfo& uv, const Vec2& tileSize);
|
||||
|
||||
// Brush edits
|
||||
void translateBrush(Brush& b, const Vec3& t, bool lockTexture = true);
|
||||
void transformBrush(Brush& b, const Mat34& m);
|
||||
void transformMesh(MeshObject& m, const Mat34& xf);
|
||||
std::vector<Brush> hollowBrush(const Brush& b, float thickness);
|
||||
bool clipBrush(const Brush& b, const Plane& p, Brush* front, Brush* back); // false if nothing valid
|
||||
MeshObject brushToMesh(const Brush& b);
|
||||
|
||||
// Mesh edits
|
||||
void flipMesh(MeshObject& m);
|
||||
void noiseMesh(MeshObject& m, float amplitude, float frequency, uint32_t seed);
|
||||
bool extrudePoly(MeshObject& m, int poly, float dist);
|
||||
Status importOBJ(const std::string& path, Map& map, MeshObject& out);
|
||||
|
||||
// Brush primitives (axis-aligned bounds; cylinders/cones are vertical)
|
||||
Brush makeBox(const Vec3& mn, const Vec3& mx, int material = 0);
|
||||
Brush makeWedge(const Vec3& mn, const Vec3& mx, int material = 0); // ramp rising towards +X
|
||||
Brush makeCylinder(const Vec3& mn, const Vec3& mx, int segments, int material = 0);
|
||||
Brush makeCone(const Vec3& mn, const Vec3& mx, int segments, int material = 0);
|
||||
std::vector<Brush> makeStairs(const Vec3& mn, const Vec3& mx, int steps, int material = 0); // rises towards +X
|
||||
|
||||
// Mesh primitives
|
||||
MeshObject meshBox(const Vec3& mn, const Vec3& mx, int material = 0);
|
||||
MeshObject meshCylinder(const Vec3& mn, const Vec3& mx, int segments, int material = 0);
|
||||
MeshObject meshCone(const Vec3& mn, const Vec3& mx, int segments, int material = 0);
|
||||
MeshObject meshSphere(const Vec3& mn, const Vec3& mx, int rings, int segments, int material = 0);
|
||||
MeshObject meshTorus(const Vec3& mn, const Vec3& mx, int segments, int sides, int material = 0);
|
||||
MeshObject meshGrid(const Vec3& mn, const Vec3& mx, int segX, int segZ, int material = 0); // terrain patch
|
||||
|
||||
// Starter content (used by the editor's "New map" and by the sample tools)
|
||||
void addDefaultMaterials(Map& map); // dev/* materials; texture-less, tinted
|
||||
Map makeDemoMap(); // lit room with brushes, meshes, entities, a door and a trigger
|
||||
|
||||
// ------------------------------------------------------------------ lights
|
||||
enum class LightType : uint8_t { Point = 0, Spot = 1, Sun = 2 };
|
||||
struct Light {
|
||||
LightType type = LightType::Point;
|
||||
Vec3 pos;
|
||||
Vec3 dir{0, -1, 0}; // direction the light travels (spot + sun)
|
||||
Vec3 color{1, 1, 1};
|
||||
float intensity = 1;
|
||||
float radius = 512;
|
||||
float innerDeg = 25, outerDeg = 40;
|
||||
uint32_t entityId = 0;
|
||||
};
|
||||
// light / light_spot / light_environment entities -> Light list
|
||||
std::vector<Light> collectLights(const Map& map);
|
||||
|
||||
// ------------------------------------------------------------------ compiled data
|
||||
struct Vertex {
|
||||
Vec3 pos;
|
||||
Vec3 normal;
|
||||
Vec2 uv;
|
||||
uint32_t color = 0xFFFFFFFFu; // 0xAABBGGRR (little-endian RGBA bytes); baked lighting goes here
|
||||
};
|
||||
enum class GeomSource : uint8_t { Bsp = 0, Mesh = 1 };
|
||||
struct Batch { // one draw call: a model + material + source combination
|
||||
int32_t material = 0;
|
||||
GeomSource source = GeomSource::Bsp;
|
||||
int32_t model = 0; // 0 = world, >0 = brush entity sub-model
|
||||
std::vector<Vertex> vertices;
|
||||
std::vector<uint32_t> indices; // triangle list
|
||||
AABB bounds;
|
||||
};
|
||||
|
||||
struct CollisionBrush {
|
||||
uint32_t contents = 0;
|
||||
uint32_t sourceId = 0; // Brush::id
|
||||
std::vector<Plane> planes;
|
||||
AABB bounds;
|
||||
};
|
||||
struct TraceResult {
|
||||
bool hit = false;
|
||||
float fraction = 1;
|
||||
Vec3 point, normal;
|
||||
uint32_t contents = 0;
|
||||
uint32_t objectId = 0; // Brush::id or MeshObject::id
|
||||
bool startSolid = false;
|
||||
};
|
||||
|
||||
// Brush-based BSP tree (Quake 3 style): nodes split on brush-face planes, leaves hold brush lists.
|
||||
struct BspNode { int32_t plane = 0; int32_t child[2] = {0, 0}; }; // [0]=front [1]=back, <0 => ~leaf
|
||||
struct BspLeaf { uint32_t firstBrush = 0, brushCount = 0; };
|
||||
struct Bsp {
|
||||
std::vector<Plane> planes;
|
||||
std::vector<BspNode> nodes;
|
||||
std::vector<BspLeaf> leaves;
|
||||
std::vector<uint32_t> leafBrushes;
|
||||
std::vector<CollisionBrush> brushes;
|
||||
int32_t root = -1; // node index, or ~leaf
|
||||
AABB bounds;
|
||||
uint32_t depth = 0;
|
||||
|
||||
void build(std::vector<CollisionBrush> brushes);
|
||||
int findLeaf(const Vec3& p) const;
|
||||
uint32_t pointContents(const Vec3& p) const;
|
||||
TraceResult trace(const Vec3& a, const Vec3& b, uint32_t mask = CONTENTS_SOLID) const;
|
||||
};
|
||||
|
||||
struct CompiledModel { // model 0 = world, others = brush entities (doors, triggers...)
|
||||
uint32_t entityId = 0;
|
||||
AABB bounds;
|
||||
std::vector<CollisionBrush> brushes; // sub-models: linear brush list
|
||||
};
|
||||
struct CollisionMesh {
|
||||
uint32_t objectId = 0;
|
||||
AABB bounds;
|
||||
std::vector<Vec3> tris; // 3 per triangle
|
||||
};
|
||||
struct CompileStats {
|
||||
uint32_t brushes = 0, meshes = 0, faces = 0, triangles = 0, vertices = 0;
|
||||
uint32_t bspNodes = 0, bspLeaves = 0, bspDepth = 0, lights = 0;
|
||||
double ms = 0;
|
||||
};
|
||||
|
||||
struct CompiledMap {
|
||||
std::vector<Material> materials;
|
||||
std::vector<Batch> batches;
|
||||
std::vector<CompiledModel> models;
|
||||
Bsp bsp; // world solids only
|
||||
std::vector<CollisionMesh> collMeshes;
|
||||
std::vector<Light> lights;
|
||||
std::vector<Entity> entities;
|
||||
Vec3 ambient{0.2f, 0.2f, 0.2f};
|
||||
float unitsPerMeter = 64;
|
||||
AABB bounds;
|
||||
CompileStats stats;
|
||||
bool baked = false;
|
||||
|
||||
// Nearest hit against the BSP (solid brushes) and solid mesh objects.
|
||||
TraceResult trace(const Vec3& a, const Vec3& b, uint32_t mask = CONTENTS_SOLID) const;
|
||||
uint32_t pointContents(const Vec3& p) const { return bsp.pointContents(p); }
|
||||
};
|
||||
|
||||
struct CompileOptions {
|
||||
bool csg = true; // remove brush faces hidden inside other solids
|
||||
bool bsp = true; // build collision BSP
|
||||
bool bakeLighting = false; // tessellate + bake vertex colours
|
||||
float lightEdge = 32; // max triangle edge while baking (smaller = finer light)
|
||||
bool shadows = true;
|
||||
int threads = 0; // 0 = hardware concurrency
|
||||
};
|
||||
CompiledMap compile(const Map& map, const CompileOptions& opt = {});
|
||||
// Raw per-object render batches without CSG (fast; used by the editor viewport).
|
||||
void buildEditBatches(const Map& map, std::vector<Batch>& out);
|
||||
// Bakes per-vertex lighting (ambient + point/spot/sun with BSP-traced shadows).
|
||||
void bakeLighting(CompiledMap& cm, float maxEdge = 32, bool shadows = true, int threads = 0);
|
||||
|
||||
// ------------------------------------------------------------------ file format (.hmap)
|
||||
// Chunked little-endian binary. Chunks: META MATS BRSH MESH ENTS CMPL. Unknown chunks are skipped.
|
||||
Status saveMap(const std::string& path, const Map& map, const CompiledMap* compiled = nullptr);
|
||||
Status loadMap(const std::string& path, Map& out, CompiledMap* compiled = nullptr);
|
||||
// Runtime-oriented: writes only materials + entities + compiled geometry (no editable source).
|
||||
Status saveCompiled(const std::string& path, const CompiledMap& cm);
|
||||
// Loads compiled data; if the file only has source chunks it compiles on the fly.
|
||||
Status loadCompiled(const std::string& path, CompiledMap& out);
|
||||
std::vector<uint8_t> saveMapToMemory(const Map& map, const CompiledMap* compiled = nullptr);
|
||||
Status loadMapFromMemory(const uint8_t* data, size_t size, Map& out, CompiledMap* compiled = nullptr);
|
||||
|
||||
// ------------------------------------------------------------------ export
|
||||
struct FbxOptions {
|
||||
float scale = 1.0f; // multiplies positions
|
||||
bool entities = true; // export entities/props as Null nodes with custom properties
|
||||
bool vertexColors = true; // write baked lighting as a colour layer
|
||||
bool includeSubModels = true;
|
||||
};
|
||||
std::vector<uint8_t> exportFBXToMemory(const CompiledMap& cm, const FbxOptions& opt = {});
|
||||
Status exportFBX(const std::string& path, const CompiledMap& cm, const FbxOptions& opt = {});
|
||||
Status exportOBJ(const std::string& path, const CompiledMap& cm, float scale = 1.0f);
|
||||
|
||||
// ------------------------------------------------------------------ entity definitions
|
||||
struct PropDef {
|
||||
std::string key, type, label, def, choices; // type: string int float bool color255 vec3 choice model material target
|
||||
};
|
||||
struct EntityClass {
|
||||
std::string name, label;
|
||||
bool brush = false; // brush entity (func_*, trigger_*)
|
||||
Vec3 color{1, 1, 1}; // editor colour 0..1
|
||||
Vec3 half{8, 8, 8}; // editor box half extents
|
||||
bool directional = false; // shows an angles arrow
|
||||
bool hasModel = false; // displays the "model" key
|
||||
uint32_t brushContents = CONTENTS_SOLID;
|
||||
std::vector<PropDef> props;
|
||||
};
|
||||
class EntityRegistry {
|
||||
public:
|
||||
std::vector<EntityClass> classes;
|
||||
const EntityClass* find(const std::string& name) const;
|
||||
// Mini-FGD text format, see assets/entities.def. Later definitions override earlier ones.
|
||||
Status parse(const std::string& text);
|
||||
void applyDefaults(Entity& e) const; // fills missing keys with class defaults
|
||||
static EntityRegistry builtin();
|
||||
};
|
||||
|
||||
} // namespace hmap
|
||||
@@ -0,0 +1,84 @@
|
||||
/* hmap/hmap_c.h - plain C API over libhmap for engines, scripting languages and FFI.
|
||||
*
|
||||
* 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);
|
||||
* // upload b.vertices / b.indices, bind texture hmap_material_texture(m, b.material)
|
||||
* }
|
||||
* hmap_trace t; hmap_trace_segment(m, from, to, HMAP_CONTENTS_SOLID, &t);
|
||||
* hmap_free(m);
|
||||
*
|
||||
* Units: map units, +Y up, forward -Z. Pointers stay valid until hmap_free().
|
||||
*/
|
||||
#ifndef HMAP_C_H
|
||||
#define HMAP_C_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define HMAP_CONTENTS_SOLID 1u
|
||||
#define HMAP_CONTENTS_WATER 2u
|
||||
#define HMAP_CONTENTS_CLIP 4u
|
||||
#define HMAP_CONTENTS_TRIGGER 8u
|
||||
#define HMAP_CONTENTS_SKY 16u
|
||||
|
||||
typedef struct hmap_map hmap_map;
|
||||
|
||||
typedef struct {
|
||||
float pos[3];
|
||||
float normal[3];
|
||||
float uv[2];
|
||||
uint32_t color; /* 0xAABBGGRR, baked vertex light */
|
||||
} hmap_vertex;
|
||||
|
||||
typedef struct {
|
||||
int32_t material;
|
||||
int32_t model; /* 0 = world, >0 = brush-entity sub-model */
|
||||
int32_t is_mesh; /* 0 = BSP/brush geometry, 1 = mesh object */
|
||||
uint32_t vertex_count;
|
||||
const hmap_vertex* vertices;
|
||||
uint32_t index_count; /* triangle list */
|
||||
const uint32_t* indices;
|
||||
} hmap_batch;
|
||||
|
||||
typedef struct {
|
||||
int hit;
|
||||
float fraction;
|
||||
float point[3];
|
||||
float normal[3];
|
||||
uint32_t contents;
|
||||
uint32_t object_id;
|
||||
int start_solid;
|
||||
} hmap_trace;
|
||||
|
||||
/* Loads a .hmap file (compiled data is used when present, otherwise the source is compiled). */
|
||||
hmap_map* hmap_load(const char* path, char* err, size_t err_len);
|
||||
void hmap_free(hmap_map* m);
|
||||
|
||||
uint32_t hmap_batch_count(const hmap_map* m);
|
||||
int hmap_get_batch(const hmap_map* m, uint32_t index, hmap_batch* out);
|
||||
|
||||
uint32_t hmap_material_count(const hmap_map* m);
|
||||
const char* hmap_material_name(const hmap_map* m, uint32_t index);
|
||||
const char* hmap_material_texture(const hmap_map* m, uint32_t index);
|
||||
|
||||
uint32_t hmap_entity_count(const hmap_map* m);
|
||||
const char* hmap_entity_classname(const hmap_map* m, uint32_t index);
|
||||
void hmap_entity_origin(const hmap_map* m, uint32_t index, float out[3]);
|
||||
void hmap_entity_angles(const hmap_map* m, uint32_t index, float out[3]); /* pitch, yaw, roll degrees */
|
||||
const char* hmap_entity_get(const hmap_map* m, uint32_t index, const char* key); /* NULL if missing */
|
||||
|
||||
uint32_t hmap_point_contents(const hmap_map* m, const float p[3]);
|
||||
int hmap_trace_segment(const hmap_map* m, const float from[3], const float to[3], uint32_t mask, hmap_trace* out);
|
||||
|
||||
int hmap_export_fbx(const hmap_map* m, const char* path); /* 1 on success */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* HMAP_C_H */
|
||||
@@ -0,0 +1,226 @@
|
||||
// hmap/hmath.h - tiny dependency-free math types used by libhmap.
|
||||
// Conventions: right-handed, +Y up, forward = -Z, column vectors (p' = M * p + t).
|
||||
#pragma once
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
|
||||
namespace hmap {
|
||||
|
||||
constexpr float kPi = 3.14159265358979323846f;
|
||||
constexpr float kDeg2Rad = kPi / 180.0f;
|
||||
constexpr float kRad2Deg = 180.0f / kPi;
|
||||
constexpr float kPlaneEps = 0.01f; // world-unit tolerance used by plane classification
|
||||
|
||||
struct Vec2 {
|
||||
float x = 0, y = 0;
|
||||
constexpr Vec2() = default;
|
||||
constexpr Vec2(float x_, float y_) : x(x_), y(y_) {}
|
||||
Vec2 operator+(const Vec2& o) const { return {x + o.x, y + o.y}; }
|
||||
Vec2 operator-(const Vec2& o) const { return {x - o.x, y - o.y}; }
|
||||
Vec2 operator*(float s) const { return {x * s, y * s}; }
|
||||
};
|
||||
|
||||
struct Vec3 {
|
||||
float x = 0, y = 0, z = 0;
|
||||
constexpr Vec3() = default;
|
||||
constexpr Vec3(float x_, float y_, float z_) : x(x_), y(y_), z(z_) {}
|
||||
Vec3 operator+(const Vec3& o) const { return {x + o.x, y + o.y, z + o.z}; }
|
||||
Vec3 operator-(const Vec3& o) const { return {x - o.x, y - o.y, z - o.z}; }
|
||||
Vec3 operator*(float s) const { return {x * s, y * s, z * s}; }
|
||||
Vec3 operator/(float s) const { return {x / s, y / s, z / s}; }
|
||||
Vec3 operator-() const { return {-x, -y, -z}; }
|
||||
Vec3& operator+=(const Vec3& o) { x += o.x; y += o.y; z += o.z; return *this; }
|
||||
Vec3& operator-=(const Vec3& o) { x -= o.x; y -= o.y; z -= o.z; return *this; }
|
||||
Vec3& operator*=(float s) { x *= s; y *= s; z *= s; return *this; }
|
||||
float operator[](int i) const { return i == 0 ? x : (i == 1 ? y : z); }
|
||||
float& operator[](int i) { return i == 0 ? x : (i == 1 ? y : z); }
|
||||
bool operator==(const Vec3& o) const { return x == o.x && y == o.y && z == o.z; }
|
||||
bool operator!=(const Vec3& o) const { return !(*this == o); }
|
||||
};
|
||||
|
||||
inline Vec3 operator*(float s, const Vec3& v) { return v * s; }
|
||||
inline float dot(const Vec3& a, const Vec3& b) { return a.x * b.x + a.y * b.y + a.z * b.z; }
|
||||
inline Vec3 cross(const Vec3& a, const Vec3& b) {
|
||||
return {a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x};
|
||||
}
|
||||
inline float lengthSq(const Vec3& v) { return dot(v, v); }
|
||||
inline float length(const Vec3& v) { return std::sqrt(dot(v, v)); }
|
||||
inline Vec3 normalize(const Vec3& v) {
|
||||
float l = length(v);
|
||||
return l > 1e-12f ? v / l : Vec3(0, 0, 0);
|
||||
}
|
||||
inline Vec3 vmin(const Vec3& a, const Vec3& b) { return {std::min(a.x, b.x), std::min(a.y, b.y), std::min(a.z, b.z)}; }
|
||||
inline Vec3 vmax(const Vec3& a, const Vec3& b) { return {std::max(a.x, b.x), std::max(a.y, b.y), std::max(a.z, b.z)}; }
|
||||
inline Vec3 lerp(const Vec3& a, const Vec3& b, float t) { return a + (b - a) * t; }
|
||||
inline float clampf(float v, float lo, float hi) { return v < lo ? lo : (v > hi ? hi : v); }
|
||||
|
||||
// Plane: dot(n, p) == d. Solids are on the BACK side (dot(n,p) < d); n points out of the solid.
|
||||
struct Plane {
|
||||
Vec3 n{0, 1, 0};
|
||||
float d = 0;
|
||||
float dist(const Vec3& p) const { return dot(n, p) - d; }
|
||||
static Plane fromPoints(const Vec3& a, const Vec3& b, const Vec3& c) {
|
||||
Vec3 n = normalize(cross(b - a, c - a));
|
||||
return Plane{n, dot(n, a)};
|
||||
}
|
||||
static Plane fromNormalPoint(const Vec3& n, const Vec3& p) {
|
||||
Vec3 nn = normalize(n);
|
||||
return Plane{nn, dot(nn, p)};
|
||||
}
|
||||
};
|
||||
|
||||
struct AABB {
|
||||
Vec3 mn{1e30f, 1e30f, 1e30f}, mx{-1e30f, -1e30f, -1e30f};
|
||||
bool empty() const { return mn.x > mx.x; }
|
||||
void add(const Vec3& p) { mn = vmin(mn, p); mx = vmax(mx, p); }
|
||||
void add(const AABB& b) { if (!b.empty()) { add(b.mn); add(b.mx); } }
|
||||
Vec3 center() const { return (mn + mx) * 0.5f; }
|
||||
Vec3 size() const { return mx - mn; }
|
||||
bool overlaps(const AABB& o, float eps = 0) const {
|
||||
return !(o.mn.x > mx.x + eps || o.mx.x < mn.x - eps || o.mn.y > mx.y + eps ||
|
||||
o.mx.y < mn.y - eps || o.mn.z > mx.z + eps || o.mx.z < mn.z - eps);
|
||||
}
|
||||
bool contains(const Vec3& p) const {
|
||||
return p.x >= mn.x && p.x <= mx.x && p.y >= mn.y && p.y <= mx.y && p.z >= mn.z && p.z <= mx.z;
|
||||
}
|
||||
AABB expanded(float e) const { AABB r = *this; r.mn = mn - Vec3(e, e, e); r.mx = mx + Vec3(e, e, e); return r; }
|
||||
};
|
||||
|
||||
// Slab test. Returns true when the ray hits; tNear may be negative when the origin is inside.
|
||||
inline bool rayAABB(const Vec3& o, const Vec3& d, const AABB& b, float& tNear, float& tFar) {
|
||||
float t0 = -1e30f, t1 = 1e30f;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
if (std::fabs(d[i]) < 1e-9f) {
|
||||
if (o[i] < b.mn[i] || o[i] > b.mx[i]) return false;
|
||||
} else {
|
||||
float inv = 1.0f / d[i];
|
||||
float a = (b.mn[i] - o[i]) * inv, c = (b.mx[i] - o[i]) * inv;
|
||||
if (a > c) std::swap(a, c);
|
||||
t0 = std::max(t0, a);
|
||||
t1 = std::min(t1, c);
|
||||
if (t0 > t1) return false;
|
||||
}
|
||||
}
|
||||
tNear = t0;
|
||||
tFar = t1;
|
||||
return t1 >= 0;
|
||||
}
|
||||
|
||||
// 3x4 affine matrix.
|
||||
struct Mat34 {
|
||||
float m[3][4] = {{1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 1, 0}};
|
||||
|
||||
static Mat34 identity() { return Mat34(); }
|
||||
static Mat34 translate(const Vec3& t) {
|
||||
Mat34 r;
|
||||
r.m[0][3] = t.x; r.m[1][3] = t.y; r.m[2][3] = t.z;
|
||||
return r;
|
||||
}
|
||||
static Mat34 scale(const Vec3& s) {
|
||||
Mat34 r;
|
||||
r.m[0][0] = s.x; r.m[1][1] = s.y; r.m[2][2] = s.z;
|
||||
return r;
|
||||
}
|
||||
static Mat34 rotate(const Vec3& axis, float deg) { // Rodrigues, right-handed
|
||||
Vec3 a = normalize(axis);
|
||||
float c = std::cos(deg * kDeg2Rad), s = std::sin(deg * kDeg2Rad), t = 1 - c;
|
||||
Mat34 r;
|
||||
r.m[0][0] = t * a.x * a.x + c; r.m[0][1] = t * a.x * a.y - s * a.z; r.m[0][2] = t * a.x * a.z + s * a.y;
|
||||
r.m[1][0] = t * a.x * a.y + s * a.z; r.m[1][1] = t * a.y * a.y + c; r.m[1][2] = t * a.y * a.z - s * a.x;
|
||||
r.m[2][0] = t * a.x * a.z - s * a.y; r.m[2][1] = t * a.y * a.z + s * a.x; r.m[2][2] = t * a.z * a.z + c;
|
||||
return r;
|
||||
}
|
||||
// angles = (pitch, yaw, roll) degrees. R = Ry(yaw) * Rx(pitch) * Rz(roll). Forward (-Z) with
|
||||
// yaw 0 / pitch 0 stays -Z; +pitch looks up; +yaw turns left (counter-clockwise from above).
|
||||
static Mat34 fromEuler(const Vec3& a) {
|
||||
return rotate({0, 1, 0}, a.y) * rotate({1, 0, 0}, a.x) * rotate({0, 0, 1}, a.z);
|
||||
}
|
||||
Vec3 toEuler() const { // inverse of fromEuler
|
||||
float sp = -m[1][2];
|
||||
float pitch = std::asin(clampf(sp, -1.0f, 1.0f));
|
||||
float yaw, roll;
|
||||
if (std::fabs(sp) < 0.99999f) {
|
||||
yaw = std::atan2(m[0][2], m[2][2]);
|
||||
roll = std::atan2(m[1][0], m[1][1]);
|
||||
} else {
|
||||
yaw = std::atan2(-m[2][0], m[0][0]);
|
||||
roll = 0;
|
||||
}
|
||||
return {pitch * kRad2Deg, yaw * kRad2Deg, roll * kRad2Deg};
|
||||
}
|
||||
// Euler angles for the FBX default XYZ order (R = Rz * Ry * Rx), degrees.
|
||||
Vec3 toEulerXYZ() const {
|
||||
float sb = -m[2][0];
|
||||
float b = std::asin(clampf(sb, -1.0f, 1.0f));
|
||||
float a, c;
|
||||
if (std::fabs(sb) < 0.99999f) {
|
||||
a = std::atan2(m[2][1], m[2][2]);
|
||||
c = std::atan2(m[1][0], m[0][0]);
|
||||
} else {
|
||||
a = std::atan2(-m[1][2], m[1][1]);
|
||||
c = 0;
|
||||
}
|
||||
return {a * kRad2Deg, b * kRad2Deg, c * kRad2Deg};
|
||||
}
|
||||
Vec3 point(const Vec3& p) const {
|
||||
return {m[0][0] * p.x + m[0][1] * p.y + m[0][2] * p.z + m[0][3],
|
||||
m[1][0] * p.x + m[1][1] * p.y + m[1][2] * p.z + m[1][3],
|
||||
m[2][0] * p.x + m[2][1] * p.y + m[2][2] * p.z + m[2][3]};
|
||||
}
|
||||
Vec3 vector(const Vec3& v) const {
|
||||
return {m[0][0] * v.x + m[0][1] * v.y + m[0][2] * v.z,
|
||||
m[1][0] * v.x + m[1][1] * v.y + m[1][2] * v.z,
|
||||
m[2][0] * v.x + m[2][1] * v.y + m[2][2] * v.z};
|
||||
}
|
||||
Vec3 translation() const { return {m[0][3], m[1][3], m[2][3]}; }
|
||||
// this * b : applies b first, then this.
|
||||
Mat34 operator*(const Mat34& b) const {
|
||||
Mat34 r;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
for (int j = 0; j < 3; ++j)
|
||||
r.m[i][j] = m[i][0] * b.m[0][j] + m[i][1] * b.m[1][j] + m[i][2] * b.m[2][j];
|
||||
r.m[i][3] = m[i][0] * b.m[0][3] + m[i][1] * b.m[1][3] + m[i][2] * b.m[2][3] + m[i][3];
|
||||
}
|
||||
return r;
|
||||
}
|
||||
float det3() const {
|
||||
return m[0][0] * (m[1][1] * m[2][2] - m[1][2] * m[2][1]) -
|
||||
m[0][1] * (m[1][0] * m[2][2] - m[1][2] * m[2][0]) +
|
||||
m[0][2] * (m[1][0] * m[2][1] - m[1][1] * m[2][0]);
|
||||
}
|
||||
bool inverse(Mat34& out) const {
|
||||
float det = det3();
|
||||
if (std::fabs(det) < 1e-12f) return false;
|
||||
float id = 1.0f / det;
|
||||
Mat34 r;
|
||||
r.m[0][0] = (m[1][1] * m[2][2] - m[1][2] * m[2][1]) * id;
|
||||
r.m[0][1] = (m[0][2] * m[2][1] - m[0][1] * m[2][2]) * id;
|
||||
r.m[0][2] = (m[0][1] * m[1][2] - m[0][2] * m[1][1]) * id;
|
||||
r.m[1][0] = (m[1][2] * m[2][0] - m[1][0] * m[2][2]) * id;
|
||||
r.m[1][1] = (m[0][0] * m[2][2] - m[0][2] * m[2][0]) * id;
|
||||
r.m[1][2] = (m[0][2] * m[1][0] - m[0][0] * m[1][2]) * id;
|
||||
r.m[2][0] = (m[1][0] * m[2][1] - m[1][1] * m[2][0]) * id;
|
||||
r.m[2][1] = (m[0][1] * m[2][0] - m[0][0] * m[2][1]) * id;
|
||||
r.m[2][2] = (m[0][0] * m[1][1] - m[0][1] * m[1][0]) * id;
|
||||
Vec3 t = translation();
|
||||
Vec3 it = r.vector(t);
|
||||
r.m[0][3] = -it.x; r.m[1][3] = -it.y; r.m[2][3] = -it.z;
|
||||
out = r;
|
||||
return true;
|
||||
}
|
||||
// Transforms a plane by this affine map (normal uses the inverse transpose).
|
||||
Plane transformPlane(const Plane& pl) const {
|
||||
Mat34 inv;
|
||||
if (!inverse(inv)) return pl;
|
||||
Vec3 n(inv.m[0][0] * pl.n.x + inv.m[1][0] * pl.n.y + inv.m[2][0] * pl.n.z,
|
||||
inv.m[0][1] * pl.n.x + inv.m[1][1] * pl.n.y + inv.m[2][1] * pl.n.z,
|
||||
inv.m[0][2] * pl.n.x + inv.m[1][2] * pl.n.y + inv.m[2][2] * pl.n.z);
|
||||
float d = pl.d + dot(n, translation());
|
||||
float l = length(n);
|
||||
if (l < 1e-12f) return pl;
|
||||
return Plane{n / l, d / l};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace hmap
|
||||
Reference in new issue
Block a user