From 9212b1f51f86ab06779926ebd7cf49f8ab025b0a Mon Sep 17 00:00:00 2001 From: Orange Date: Fri, 31 Oct 2025 16:25:56 +0300 Subject: [PATCH] Documents pattern scanning API Adds comprehensive documentation for the pattern scanning API. Details parsing behavior, complexity, and usage examples. Includes troubleshooting tips and minimal test sketches. Clarifies edge-case handling and implementation notes. --- docs/3d_primitives/box.md | 118 +++++++++++++++++++++ docs/3d_primitives/plane.md | 98 ++++++++++++++++++ docs/utility/color.md | 190 ++++++++++++++++++++++++++++++++++ docs/utility/pattern_scan.md | 194 +++++++++++++++++++++++++++++++++++ 4 files changed, 600 insertions(+) create mode 100644 docs/3d_primitives/box.md create mode 100644 docs/3d_primitives/plane.md create mode 100644 docs/utility/color.md create mode 100644 docs/utility/pattern_scan.md diff --git a/docs/3d_primitives/box.md b/docs/3d_primitives/box.md new file mode 100644 index 0000000..84e392d --- /dev/null +++ b/docs/3d_primitives/box.md @@ -0,0 +1,118 @@ +# `omath::primitives::create_box` — Build an oriented box as 12 triangles + +> Header: your project’s `primitives/box.hpp` (declares `create_box`) +> Namespace: `omath::primitives` +> Depends on: `omath::Triangle>`, `omath::Vector3` + +```cpp +[[nodiscard]] +std::array>, 12> +create_box(const Vector3& top, + const Vector3& bottom, + const Vector3& dir_forward, + const Vector3& dir_right, + float ratio = 4.f) noexcept; +``` + +--- + +## What it does + +Constructs a **rectangular cuboid (“box”)** oriented in 3D space and returns its surface as **12 triangles** (2 per face × 6 faces). The box’s central axis runs from `bottom` → `top`. The **up** direction is inferred from that segment; the **forward** and **right** directions define the box’s orientation around that axis. + +The lateral half-extents are derived from the axis length and `ratio`: + +> Let `H = |top - bottom|`. Lateral half-size ≈ `H / ratio` along both `dir_forward` and `dir_right` +> (i.e., the cross-section is a square of side `2H/ratio`). + +> **Note:** This describes the intended behavior from the interface. If you rely on a different sizing rule, document it next to your implementation. + +--- + +## Parameters + +* `top` + Center of the **top face**. + +* `bottom` + Center of the **bottom face**. + +* `dir_forward` + A direction that orients the box around its up axis. Should be **non-zero** and **not collinear** with `top - bottom`. + +* `dir_right` + A direction roughly orthogonal to both `dir_forward` and `top - bottom`. Used to fully fix orientation. + +* `ratio` (default `4.0f`) + Controls thickness relative to height. Larger values → thinner box. + With the default rule above, half-extent = `|top-bottom|/ratio`. + +--- + +## Return value + +`std::array>, 12>` — the six faces of the box, triangulated. +Winding is intended to be **outward-facing** (right-handed coordinates). Do not rely on a specific **face ordering**; treat the array as opaque unless your implementation guarantees an order. + +--- + +## Expected math & robustness + +* Define `u = normalize(top - bottom)`. +* Re-orthonormalize the basis to avoid skew: + + ```cpp + f = normalize(dir_forward - u * u.dot(dir_forward)); // drop any up component + r = normalize(u.cross(f)); // right-handed basis + // (Optionally recompute f = r.cross(u) for orthogonality) + ``` +* Half-extents: `h = length(top - bottom) / ratio; hf = h * f; hr = h * r`. +* Corners (top): `t±r±f = top ± hr ± hf`; (bottom): `b±r±f = bottom ± hr ± hf`. +* Triangulate each face with consistent CCW winding when viewed from outside. + +--- + +## Example + +```cpp +using omath::Vector3; +using omath::Triangle; +using omath::primitives::create_box; + +// Axis from bottom to top (height 2) +Vector3 bottom{0, 0, 0}; +Vector3 top {0, 2, 0}; + +// Orientation around the axis +Vector3 forward{0, 0, 1}; +Vector3 right {1, 0, 0}; + +// Ratio 4 → lateral half-size = height/4 = 0.5 +auto tris = create_box(top, bottom, forward, right, 4.0f); + +// Use the triangles (normals, rendering, collision, etc.) +for (const auto& tri : tris) { + auto n = tri.calculate_normal(); + (void)n; +} +``` + +--- + +## Usage notes & pitfalls + +* **Degenerate axis**: If `top == bottom`, the box is undefined (zero height). Guard against this. +* **Directions**: Provide **non-zero**, **reasonably orthogonal** `dir_forward`/`dir_right`. A robust implementation should project/normalize internally, but callers should still pass sensible inputs. +* **Winding**: If your renderer or collision expects a specific winding, verify with a unit test and flip vertex order per face if necessary. +* **Thickness policy**: This doc assumes both lateral half-extents equal `|top-bottom|/ratio`. If your implementation diverges (e.g., separate forward/right ratios), document it. + +--- + +## See also + +* `omath::Triangle` (vertex utilities: normals, centroid, etc.) +* `omath::Vector3` (geometry operations used by the construction) + +--- + +*Last updated: 31 Oct 2025* diff --git a/docs/3d_primitives/plane.md b/docs/3d_primitives/plane.md new file mode 100644 index 0000000..c4619a3 --- /dev/null +++ b/docs/3d_primitives/plane.md @@ -0,0 +1,98 @@ +# `omath::primitives::create_plane` — Build an oriented quad (2 triangles) + +> Header: your project’s `primitives/plane.hpp` +> Namespace: `omath::primitives` +> Depends on: `omath::Triangle>`, `omath::Vector3` + +```cpp +[[nodiscard]] +std::array>, 2> +create_plane(const Vector3& vertex_a, + const Vector3& vertex_b, + const Vector3& direction, + float size) noexcept; +``` + +--- + +## What it does + +Creates a **rectangle (quad)** in 3D oriented by the edge **A→B** and a second in-plane **direction**. The quad is returned as **two triangles** suitable for rendering or collision. + +* Edge axis: `e = vertex_b - vertex_a` +* Width axis: “direction”, **projected to be perpendicular to `e`** so the quad is planar and well-formed. +* Normal (by right-hand rule): `n ∝ e × width`. + +> **Sizing convention** +> Typical construction uses **half-width = `size`** along the (normalized, orthogonalized) *direction*, i.e. the total width is `2*size`. +> If your implementation interprets `size` as full width, adjust your expectations accordingly. + +--- + +## Parameters + +* `vertex_a`, `vertex_b` — two adjacent quad vertices defining the **long edge** of the plane. +* `direction` — a vector indicating the **cross-edge direction** within the plane (does not need to be orthogonal or normalized). +* `size` — **half-width** of the quad along the (processed) `direction`. + +--- + +## Return + +`std::array>, 2>` — the quad triangulated (consistent CCW winding, outward normal per `e × width`). + +--- + +## Robust construction (expected math) + +1. `e = vertex_b - vertex_a` +2. Make `d` perpendicular to `e`: + + ``` + d = direction - e * (e.dot(direction) / e.length_sqr()); + if (d.length_sqr() == 0) pick an arbitrary perpendicular to e + d = d.normalized(); + ``` +3. Offsets: `w = d * size` +4. Four corners: + + ``` + A0 = vertex_a - w; A1 = vertex_a + w; + B0 = vertex_b - w; B1 = vertex_b + w; + ``` +5. Triangles (CCW when viewed from +normal): + + ``` + T0 = Triangle{ A0, A1, B1 } + T1 = Triangle{ A0, B1, B0 } + ``` + +--- + +## Example + +```cpp +using omath::Vector3; +using omath::Triangle; +using omath::primitives::create_plane; + +Vector3 a{ -1, 0, -1 }; // edge start +Vector3 b{ 1, 0, -1 }; // edge end +Vector3 dir{ 0, 0, 1 }; // cross-edge direction within the plane (roughly +Z) +float half_width = 2.0f; + +auto quad = create_plane(a, b, dir, half_width); + +// e.g., compute normals +for (const auto& tri : quad) { + auto n = tri.calculate_normal(); (void)n; +} +``` + +--- + +## Notes & edge cases + +* **Degenerate edge**: if `vertex_a == vertex_b`, the plane is undefined. +* **Collinearity**: if `direction` is parallel to `vertex_b - vertex_a`, the function must choose an alternate perpendicular; expect a fallback. +* **Winding**: If your renderer expects a specific face order, verify and swap the two vertices in each triangle as needed. diff --git a/docs/utility/color.md b/docs/utility/color.md new file mode 100644 index 0000000..e4b864b --- /dev/null +++ b/docs/utility/color.md @@ -0,0 +1,190 @@ +# `omath::Color` — RGBA color with HSV helpers (C++20/23) + +> Header: your project’s `color.hpp` +> Namespace: `omath` +> Inherits: `Vector4` (`x=r`, `y=g`, `z=b`, `w=a`) +> Depends on: ``, `Vector4`, optionally ImGui (`OMATH_IMGUI_INTEGRATION`) +> Formatting: provides `std::formatter` + +`Color` is a tiny RGBA utility on top of `Vector4`. It offers sRGB-style channel construction, HSV↔RGB conversion, in-place HSV setters, linear blending, and string/formatter helpers. + +--- + +## Quick start + +```cpp +#include "color.hpp" +using omath::Color; + +// RGBA in [0,1] (r,g,b clamped to [0,1] on construction) +Color c{0.2f, 0.4f, 0.8f, 0.5f}; + +// From 8-bit channels +auto red = Color::from_rgba(255, 0, 0, 255); +auto green = Color::from_rgba(0, 255, 0, 160); + +// From HSV (h ∈ [0,1], s ∈ [0,1], v ∈ [0,1]) +auto cyan = Color::from_hsv(0.5f, 1.0f, 1.0f); // a = 1 + +// Read/modify via HSV +auto hsv = cyan.to_hsv(); // hue ∈ [0,1], saturation ∈ [0,1], value ∈ [0,1] +cyan.set_value(0.6f); // converts back to RGB (alpha becomes 1) + +// Blend linearly (lerp) +auto mid = red.blend(green, 0.5f); + +// Printable (0–255 per channel) +std::string s = std::format("{}", mid); // "[r:128, g:128, b:0, a:207]" for example +``` + +--- + +## Data model + +* Inherits `Vector4`: + + * `x` = **red**, `y` = **green**, `z` = **blue**, `w` = **alpha**. +* Construction clamps **RGB** to `[0,1]` (via `Vector4::clamp(0,1)`), **alpha is not clamped** by that call (see notes). + +--- + +## Construction & factories + +```cpp +// RGBA in [0,1] (RGB clamped to [0,1]; alpha untouched by clamp) +constexpr Color(float r, float g, float b, float a) noexcept; + +// Default +constexpr Color() noexcept; + +// From 8-bit RGBA (0–255) → normalized to [0,1] +constexpr static Color from_rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) noexcept; + +// From HSV where hue ∈ [0,1], saturation ∈ [0,1], value ∈ [0,1] +struct Hsv { float hue{}, saturation{}, value{}; }; + +constexpr static Color from_hsv(float hue, float saturation, float value) noexcept; +constexpr static Color from_hsv(const Hsv& hsv) noexcept; // delegates to the above + +// Construct from a Vector4 (RGB clamped, alpha not clamped) +constexpr explicit Color(const Vector4& vec) noexcept; +``` + +**HSV details** + +* `from_hsv(h, s, v)`: `h` is **normalized** (`[0,1]`); it is clamped, then mapped to the 6 hue sectors; **alpha = 1.0**. +* `to_hsv()`: returns `Hsv{h,s,v}` with **`h ∈ [0,1]`** (internally computes degrees and divides by 360), `s,v ∈ [0,1]`. + +--- + +## Mutators + +```cpp +constexpr void set_hue(float h) noexcept; // h ∈ [0,1] recommended +constexpr void set_saturation(float s) noexcept; // s ∈ [0,1] +constexpr void set_value(float v) noexcept; // v ∈ [0,1] + +// Linear blend: (1-ratio)*this + ratio*other, ratio clamped to [0,1] +constexpr Color blend(const Color& other, float ratio) const noexcept; +``` + +> ⚠️ **Alpha reset on HSV setters:** each `set_*` converts HSV→RGB using `from_hsv(...)`, which **sets alpha to 1.0** (overwriting previous `w`). If you need to preserve alpha: +> +> ```cpp +> float a = col.w; +> col.set_value(0.5f); +> col.w = a; +> ``` + +--- + +## Constants + +```cpp +static constexpr Color red(); // (1,0,0,1) +static constexpr Color green(); // (0,1,0,1) +static constexpr Color blue(); // (0,0,1,1) +``` + +--- + +## String & formatting + +```cpp +// "[r:R, g:G, b:B, a:A]" with each channel shown as 0–255 integer +std::string to_string() const noexcept; +std::wstring to_wstring() const noexcept; +std::u8string to_u8string() const noexcept; + +// Formatter forwards to the above (char/wchar_t/char8_t) +template<> struct std::formatter; +``` + +--- + +## ImGui (optional) + +```cpp +#ifdef OMATH_IMGUI_INTEGRATION +ImColor to_im_color() const noexcept; // constructs from Vector4's to_im_vec4() +#endif +``` + +Ensure `` is included somewhere before this header when the macro is enabled. + +--- + +## Notes & caveats + +* **Alpha clamping:** `Vector4::clamp(min,max)` (called by `Color` ctors) clamps **x,y,z** only in the provided `Vector4` implementation; `w` is **left unchanged**. If you require strict `[0,1]` alpha, clamp it yourself: + + ```cpp + col.w = std::clamp(col.w, 0.0f, 1.0f); + ``` +* **HSV range:** The API consistently uses **normalized hue** (`[0,1]`). Convert degrees ↔ normalized as `h_norm = h_deg / 360.f`. +* **Blend space:** `blend` is a **linear** interpolation in RGBA; it is not perceptually uniform. + +--- + +## API summary + +```cpp +struct Hsv { float hue{}, saturation{}, value{}; }; + +class Color final : public Vector4 { +public: + constexpr Color(float r, float g, float b, float a) noexcept; + constexpr Color() noexcept; + constexpr explicit Color(const Vector4& vec) noexcept; + + static constexpr Color from_rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) noexcept; + static constexpr Color from_hsv(float hue, float saturation, float value) noexcept; + static constexpr Color from_hsv(const Hsv& hsv) noexcept; + + constexpr Hsv to_hsv() const noexcept; + + constexpr void set_hue(float h) noexcept; + constexpr void set_saturation(float s) noexcept; + constexpr void set_value(float v) noexcept; + + constexpr Color blend(const Color& other, float ratio) const noexcept; + + static constexpr Color red(); + static constexpr Color green(); + static constexpr Color blue(); + +#ifdef OMATH_IMGUI_INTEGRATION + ImColor to_im_color() const noexcept; +#endif + + std::string to_string() const noexcept; + std::wstring to_wstring() const noexcept; + std::u8string to_u8string() const noexcept; +}; + +// formatter provided +``` + +--- + +*Last updated: 31 Oct 2025* diff --git a/docs/utility/pattern_scan.md b/docs/utility/pattern_scan.md new file mode 100644 index 0000000..c1bc883 --- /dev/null +++ b/docs/utility/pattern_scan.md @@ -0,0 +1,194 @@ +# `omath::PatternScanner` — Fast byte-pattern search with wildcards + +> Header: your project’s `pattern_scanner.hpp` +> Namespace: `omath` +> Core API: `scan_for_pattern(...)` (span or iterators) +> Errors: `PatternScanError::INVALID_PATTERN_STRING` (from `parse_pattern`) +> C++: uses `std::span`, `std::expected`, `std::byte` + +`PatternScanner` scans a contiguous byte range for a **hex pattern** that may include **wildcards**. It returns an iterator to the **first match** or the end iterator if not found (or if the pattern string is invalid). + +--- + +## Quick start + +```cpp +#include "pattern_scanner.hpp" +using omath::PatternScanner; + +std::vector buf = /* ... bytes ... */; +std::span s{buf.data(), buf.size()}; + +// Example pattern: "48 8B ?? ?? 89" (hex bytes with '?' as any byte) +auto it = PatternScanner::scan_for_pattern(s, "48 8B ?? ?? 89"); +if (it != s.end()) { + // Found at offset: + auto offset = static_cast(it - s.begin()); +} +``` + +Or with iterators: + +```cpp +auto it = PatternScanner::scan_for_pattern(buf.begin(), buf.end(), "DE AD BE EF"); +if (it != buf.end()) { /* ... */ } +``` + +--- + +## Pattern string grammar + +The pattern string is parsed into a sequence of byte **tokens**: + +* **Hex byte**: two hexadecimal digits form one byte. + Examples: `90`, `4F`, `00`, `ff` +* **Wildcard byte**: `?` or `??` matches **any single byte**. +* **Separators**: any ASCII whitespace (space, tab, newline) is **ignored** and may be used to group tokens. + +> ✔️ Valid: `"48 8B ?? 05 00"`, `"90 90 90"`, `"??"`, `"00??FF"` (no spaces) +> ❌ Invalid: odd number of hex digits in a token, non-hex characters (besides `?` and whitespace) + +If the string cannot be parsed into a clean sequence of tokens, `parse_pattern()` returns `std::unexpected(PatternScanError::INVALID_PATTERN_STRING)`, and the public scan function returns **end**. + +--- + +## API + +```cpp +namespace omath { + +enum class PatternScanError { INVALID_PATTERN_STRING }; + +class PatternScanner final { +public: + // Contiguous range (span) overload + [[nodiscard]] + static std::span::iterator + scan_for_pattern(const std::span& range, + const std::string_view& pattern); + + // Deleted rvalue-span overload (prevents dangling) + static std::span::iterator + scan_for_pattern(std::span&&, + const std::string_view&) = delete; + + // Iterator overload + template + requires std::input_or_output_iterator> + static IteratorType + scan_for_pattern(const IteratorType& begin, + const IteratorType& end, + const std::string_view& pattern); +private: + [[nodiscard]] + static std::expected>, PatternScanError> + parse_pattern(const std::string_view& pattern_string); +}; + +} // namespace omath +``` + +### Return value + +* On success: iterator to the **first** matching position. +* On failure / not found / invalid pattern: **`end`**. + +--- + +## Complexity + +Let `N` be the number of bytes in the range and `M` the number of **pattern tokens**. + +* Time: **O(N × M)** (simple sliding window with early break). +* Space: **O(M)** for the parsed pattern vector. + +--- + +## Examples + +### Find a function prologue + +```cpp +// x86-64: push rbp; mov rbp, rsp +auto it = PatternScanner::scan_for_pattern(s, "55 48 89 E5"); +if (it != s.end()) { + // ... process +} +``` + +### Skip variable bytes with wildcards + +```cpp +// mov rax, ; call +auto it = PatternScanner::scan_for_pattern(s, "48 B8 ?? ?? ?? ?? ?? ?? ?? ?? E8 ?? ?? ?? ??"); +``` + +### Iterator-based scan (subrange) + +```cpp +auto sub_begin = buf.begin() + 1024; +auto sub_end = buf.begin() + 4096; +auto it = PatternScanner::scan_for_pattern(sub_begin, sub_end, "DE AD ?? BE EF"); +``` + +--- + +## Behavior & edge cases + +* **Empty or too-short**: If the effective pattern token count `M` is `0` or `M > N`, the function returns `end`. +* **Wildcards**: Each `?`/`??` token matches **exactly one** byte. +* **No exceptions**: Invalid pattern → parsed as `unexpected`, public API returns `end`. +* **No ownership**: The span overload takes `const std::span&` and returns a **mutable iterator** into that span. You must ensure the underlying memory stays alive. The rvalue-span overload is **deleted** to prevent dangling. + +--- + +## Notes & caveats (implementation-sensitive) + +* Although the iterator overload is constrained with `std::input_or_output_iterator`, the implementation uses `*(begin + i + j)` and arithmetic on iterators. In practice this means you should pass **random-access / contiguous iterators** (e.g., from `std::vector` or `std::span`). Using non-random-access iterators would be ill-formed. +* The inner matching loop compares a parsed token (`std::optional`) to the candidate byte; `std::nullopt` is treated as a **wildcard** (always matches). +* **Implementation note:** the outer scan currently derives its scan bound from the **pattern string length**; conceptually it should use the **number of parsed tokens** (hex/wildcard bytes). If you plan to accept spaces (or other non-byte characters) in the pattern string, ensure the scan window uses the parsed token count to avoid false negatives near the end of the buffer. + +--- + +## Testing hooks + +The class befriends several unit tests: + +``` +unit_test_pattern_scan_read_test_Test +unit_test_pattern_scan_corner_case_1_Test +unit_test_pattern_scan_corner_case_2_Test +unit_test_pattern_scan_corner_case_3_Test +unit_test_pattern_scan_corner_case_4_Test +``` + +Use these to validate parsing correctness, wildcard handling, and boundary conditions. + +--- + +## Troubleshooting + +* **Always returns end**: verify the pattern string is valid hex/wildcards and that you’re scanning the intended subrange. Try a simpler pattern (e.g., a single known byte) to sanity-check. +* **Crashes or compile errors with iterator overload**: use iterators that support random access (e.g., from `std::vector`), or prefer the `std::span` overload. +* **Ambiguous wildcards**: this scanner treats `?` and `??` as **byte-wide** wildcards (not per-nibble). If you need nibble-level masks, extend `parse_pattern` to support patterns like `A?`/`?F` with bitmask matching. + +--- + +## Minimal unit test sketch + +```cpp +TEST(pattern, basic) { + std::array data{ + std::byte{0x48}, std::byte{0x8B}, std::byte{0x01}, std::byte{0x02}, + std::byte{0x89}, std::byte{0x50}, std::byte{0x90}, std::byte{0xCC} + }; + std::span s{data.data(), data.size()}; + auto it = omath::PatternScanner::scan_for_pattern(s, "48 8B ?? ?? 89"); + ASSERT_NE(it, s.end()); + EXPECT_EQ(static_cast(it - s.begin()), 0U); +} +``` + +--- + +*Last updated: 31 Oct 2025*