Skip to content

Tutorials

This page provides step-by-step tutorials for common OMath use cases.


Tutorial 1: Basic Vector Math

Learn the fundamentals of vector operations in OMath.

Step 1: Include OMath

#include <omath/omath.hpp>
#include <iostream>

using namespace omath;

Step 2: Create Vectors

// 2D vectors
Vector2<float> v2a{3.0f, 4.0f};
Vector2<float> v2b{1.0f, 2.0f};

// 3D vectors
Vector3<float> v3a{1.0f, 2.0f, 3.0f};
Vector3<float> v3b{4.0f, 5.0f, 6.0f};

// 4D vectors (often used for homogeneous coordinates)
Vector4<float> v4{1.0f, 2.0f, 3.0f, 1.0f};

Step 3: Perform Operations

// Addition
auto sum = v3a + v3b;  // {5, 7, 9}

// Subtraction
auto diff = v3a - v3b;  // {-3, -3, -3}

// Scalar multiplication
auto scaled = v3a * 2.0f;  // {2, 4, 6}

// Dot product
float dot = v3a.dot(v3b);  // 32.0

// Cross product (3D only)
auto cross = v3a.cross(v3b);  // {-3, 6, -3}

// Length
float len = v3a.length();  // ~3.74

// Normalization (safe - returns original if length is zero)
auto normalized = v3a.normalized();

// Distance between vectors
float dist = v3a.distance_to(v3b);  // ~5.196

Step 4: Angle Calculations

if (auto angle = v3a.angle_between(v3b)) {
    std::cout << "Angle in degrees: " << angle->as_degrees() << "\n";
    std::cout << "Angle in radians: " << angle->as_radians() << "\n";
} else {
    std::cout << "Cannot compute angle (zero-length vector)\n";
}

// Check if perpendicular
if (v3a.is_perpendicular(v3b)) {
    std::cout << "Vectors are perpendicular\n";
}

Key takeaways: - All vector operations are type-safe and constexpr-friendly - Safe normalization never produces NaN - Angle calculations use std::expected for error handling


Tutorial 2: World-to-Screen Projection

Project 3D coordinates to 2D screen space for overlays and ESP.

Step 1: Choose Your Game Engine

#include <omath/omath.hpp>

// For Source Engine games (CS:GO, TF2, etc.)
using namespace omath::source_engine;

// Or for other engines:
// using namespace omath::unity_engine;
// using namespace omath::unreal_engine;
// using namespace omath::frostbite_engine;

Step 2: Set Up the Camera

using namespace omath;
using namespace omath::projection;

// Define viewport (screen dimensions)
ViewPort viewport{1920.0f, 1080.0f};

// Define field of view
auto fov = FieldOfView::from_degrees(90.0f);

// Camera position and angles
Vector3<float> camera_pos{0.0f, 0.0f, 100.0f};
ViewAngles camera_angles{
    PitchAngle::from_degrees(0.0f),
    YawAngle::from_degrees(0.0f),
    RollAngle::from_degrees(0.0f)
};

// Create camera (using Source Engine in this example)
Camera camera(
    camera_pos,
    camera_angles,
    viewport,
    fov,
    0.1f,    // near plane
    1000.0f  // far plane
);

Step 3: Project 3D Points

// 3D world position (e.g., enemy player position)
Vector3<float> enemy_pos{150.0f, 200.0f, 75.0f};

// Project to screen
if (auto screen = camera.world_to_screen(enemy_pos)) {
    std::cout << "Enemy on screen at: "
              << screen->x << ", " << screen->y << "\n";

    // Draw ESP box or marker at screen->x, screen->y
    // Note: screen coordinates are in viewport space (0-width, 0-height)
} else {
    // Enemy is not visible (behind camera or outside frustum)
    std::cout << "Enemy not visible\n";
}

Step 4: Update Camera for Each Frame

void render_frame() {
    // Read current camera data from game
    Vector3<float> new_pos = read_camera_position();
    ViewAngles new_angles = read_camera_angles();

    // Update camera
    camera.update(new_pos, new_angles);

    // Project all entities
    for (const auto& entity : entities) {
        if (auto screen = camera.world_to_screen(entity.position)) {
            draw_esp_box(screen->x, screen->y);
        }
    }
}

Key takeaways: - Choose the engine trait that matches your target game - world_to_screen() returns std::optional - always check the result - Update camera each frame for accurate projections - Screen coordinates are in the viewport space you defined


Tutorial 3: Projectile Prediction (Aim-Bot)

Calculate where to aim to hit a moving target.

Step 1: Define Projectile Properties

#include <omath/omath.hpp>
#include <omath/projectile_prediction/proj_pred_engine_legacy.hpp>

using namespace omath;
using namespace omath::projectile_prediction;

// Define your weapon's projectile
Projectile bullet;
bullet.origin = Vector3<float>{0, 0, 0};  // Shooter position
bullet.speed = 800.0f;  // Muzzle velocity (m/s or game units/s)
bullet.gravity = Vector3<float>{0, 0, -9.81f};  // Gravity vector

Step 2: Define Target State

// Target information (enemy player)
Target enemy;
enemy.position = Vector3<float>{100, 200, 50};  // Current position
enemy.velocity = Vector3<float>{10, 5, 0};      // Current velocity

Step 3: Calculate Aim Point

// Create prediction engine
// Use AVX2 version if available for better performance:
// ProjPredEngineAVX2 engine;
ProjPredEngineLegacy engine;

// Calculate where to aim
if (auto aim_point = engine.maybe_calculate_aim_point(bullet, enemy)) {
    std::cout << "Aim at: "
              << aim_point->x << ", "
              << aim_point->y << ", "
              << aim_point->z << "\n";

    // Calculate angles to aim_point
    Vector3<float> aim_direction = (*aim_point - bullet.origin).normalized();

    // Convert to view angles (engine-specific)
    // ViewAngles angles = calculate_angles_to_direction(aim_direction);
    // set_aim_angles(angles);
} else {
    // Cannot hit target (too fast, out of range, etc.)
    std::cout << "Target cannot be hit\n";
}

Step 4: Handle Different Scenarios

// Stationary target
Target stationary;
stationary.position = Vector3<float>{100, 100, 100};
stationary.velocity = Vector3<float>{0, 0, 0};
// aim_point will equal position for stationary targets

// Fast-moving target
Target fast;
fast.position = Vector3<float>{100, 100, 100};
fast.velocity = Vector3<float>{50, 0, 0};  // Moving very fast
// May return nullopt if target is too fast

// Target at different heights
Target aerial;
aerial.position = Vector3<float>{100, 100, 200};  // High up
aerial.velocity = Vector3<float>{5, 5, -10};      // Falling
// Gravity will be factored into the calculation

Step 5: Performance Optimization

// For better performance on modern CPUs, use AVX2:
#include <omath/projectile_prediction/proj_pred_engine_avx2.hpp>

ProjPredEngineAVX2 fast_engine;  // 2-4x faster than legacy

// Use the same way as legacy engine
if (auto aim = fast_engine.maybe_calculate_aim_point(bullet, enemy)) {
    // Process aim point
}

Key takeaways: - Always check if aim point exists before using - Velocity must be in same units as position/speed - Gravity vector points down (typically negative Z or Y depending on engine) - Use AVX2 engine when possible for better performance - Returns nullopt when target is unreachable


Tutorial 4: Collision Detection

Perform ray-casting and intersection tests.

Step 1: Ray-Plane Intersection

#include <omath/omath.hpp>

using namespace omath;

// Define a ground plane (Z=0, normal pointing up)
Plane ground{
    Vector3<float>{0, 0, 0},  // Point on plane
    Vector3<float>{0, 0, 1}   // Normal vector (Z-up)
};

// Define a ray (e.g., looking downward from above)
Vector3<float> ray_origin{10, 20, 100};
Vector3<float> ray_direction{0, 0, -1};  // Pointing down

// Test intersection
if (auto hit = ground.intersects_ray(ray_origin, ray_direction)) {
    std::cout << "Hit ground at: "
              << hit->x << ", " << hit->y << ", " << hit->z << "\n";
    // Expected: (10, 20, 0)
} else {
    std::cout << "Ray does not intersect plane\n";
}

Step 2: Distance to Plane

// Calculate signed distance from point to plane
Vector3<float> point{10, 20, 50};
float distance = ground.distance_to_point(point);

std::cout << "Distance to ground: " << distance << "\n";
// Expected: 50.0 (50 units above ground)

// Negative distance means point is below the plane
Vector3<float> below{10, 20, -5};
float dist_below = ground.distance_to_point(below);
// Expected: -5.0

Step 3: Axis-Aligned Bounding Box

#include <omath/3d_primitives/box.hpp>

// Create a bounding box
Box bbox{
    Vector3<float>{0, 0, 0},     // Min corner
    Vector3<float>{100, 100, 100} // Max corner
};

// Test if point is inside
Vector3<float> inside{50, 50, 50};
if (bbox.contains(inside)) {
    std::cout << "Point is inside box\n";
}

Vector3<float> outside{150, 50, 50};
if (!bbox.contains(outside)) {
    std::cout << "Point is outside box\n";
}

// Box-box intersection
Box other{
    Vector3<float>{50, 50, 50},
    Vector3<float>{150, 150, 150}
};

if (bbox.intersects(other)) {
    std::cout << "Boxes overlap\n";
}

Step 4: Line Tracing

#include <omath/collision/line_tracer.hpp>

using namespace omath::collision;

// Ray-triangle intersection
Vector3<float> v0{0, 0, 0};
Vector3<float> v1{100, 0, 0};
Vector3<float> v2{0, 100, 0};

Vector3<float> ray_start{25, 25, 100};
Vector3<float> ray_dir{0, 0, -1};

LineTracer tracer;
if (auto hit = tracer.ray_triangle_intersect(ray_start, ray_dir, v0, v1, v2)) {
    std::cout << "Hit triangle at: "
              << hit->point.x << ", "
              << hit->point.y << ", "
              << hit->point.z << "\n";
    std::cout << "Hit distance: " << hit->distance << "\n";
    std::cout << "Surface normal: "
              << hit->normal.x << ", "
              << hit->normal.y << ", "
              << hit->normal.z << "\n";
}

Key takeaways: - Plane normals should be unit vectors - Ray direction should typically be normalized - Signed distance indicates which side of plane a point is on - AABB tests are very fast for broad-phase collision detection - Line tracer provides hit point, distance, and surface normal


Tutorial 5: Pattern Scanning

Search for byte patterns in memory.

Step 1: Basic Pattern Scanning

#include <omath/utility/pattern_scan.hpp>
#include <vector>

using namespace omath;

// Memory to search (e.g., from a loaded module)
std::vector<uint8_t> memory = {
    0x48, 0x8B, 0x05, 0xAA, 0xBB, 0xCC, 0xDD,
    0x48, 0x85, 0xC0, 0x74, 0x10,
    // ... more bytes
};

// Pattern with wildcards (?? = match any byte)
PatternView pattern{"48 8B 05 ?? ?? ?? ?? 48 85 C0"};

// Scan for pattern
if (auto result = pattern_scan(memory, pattern)) {
    std::cout << "Pattern found at offset: " << result->offset << "\n";

    // Extract wildcard values if needed
    // result->wildcards contains the matched bytes at ?? positions
} else {
    std::cout << "Pattern not found\n";
}

Step 2: PE File Scanning

#include <omath/utility/pe_pattern_scan.hpp>

// Scan a PE file (EXE or DLL)
PEPatternScanner scanner("game.exe");

PatternView pattern{"E8 ?? ?? ?? ?? 85 C0 75 ??"};

if (auto rva = scanner.scan_pattern(pattern)) {
    std::cout << "Pattern found at RVA: 0x"
              << std::hex << *rva << std::dec << "\n";

    // Convert RVA to absolute address if needed
    uintptr_t base_address = get_module_base("game.exe");
    uintptr_t absolute = base_address + *rva;
} else {
    std::cout << "Pattern not found in PE file\n";
}

Step 3: Multiple Patterns

// Search for multiple patterns
std::vector<PatternView> patterns{
    PatternView{"48 8B 05 ?? ?? ?? ??"},
    PatternView{"E8 ?? ?? ?? ?? 85 C0"},
    PatternView{"FF 15 ?? ?? ?? ?? 48 8B"}
};

for (size_t i = 0; i < patterns.size(); ++i) {
    if (auto result = pattern_scan(memory, patterns[i])) {
        std::cout << "Pattern " << i << " found at: "
                  << result->offset << "\n";
    }
}

Step 4: Pattern with Masks

// Alternative: use mask-based patterns
// Pattern: bytes to match
std::vector<uint8_t> pattern_bytes{0x48, 0x8B, 0x05, 0x00, 0x00, 0x00, 0x00};

// Mask: 'x' = must match, '?' = wildcard
std::string mask{"xxx????"};

// Custom scan function
auto scan_with_mask = [&](const std::vector<uint8_t>& data) {
    for (size_t i = 0; i < data.size() - pattern_bytes.size(); ++i) {
        bool match = true;
        for (size_t j = 0; j < pattern_bytes.size(); ++j) {
            if (mask[j] == 'x' && data[i + j] != pattern_bytes[j]) {
                match = false;
                break;
            }
        }
        if (match) return i;
    }
    return size_t(-1);
};

Key takeaways: - Use ?? in pattern strings for wildcards - PE scanner works with files and modules - Pattern scanning is useful for finding functions, vtables, or data - Always validate found addresses before use - Patterns may have multiple matches - consider context


Tutorial 6: Angles and View Angles

Work with game camera angles properly.

Step 1: Understanding Angle Types

#include <omath/omath.hpp>

using namespace omath;

// Generic angle with custom range
auto angle1 = Angle<float, 0.0f, 360.0f>::from_degrees(45.0f);
auto angle2 = Angle<float, -180.0f, 180.0f>::from_degrees(270.0f);

// Specialized camera angles
auto pitch = PitchAngle::from_degrees(-10.0f);  // Looking down
auto yaw = YawAngle::from_degrees(90.0f);       // Looking right
auto roll = RollAngle::from_degrees(0.0f);      // No tilt

Step 2: Angle Conversions

// Create from degrees
auto deg_angle = PitchAngle::from_degrees(45.0f);

// Get as radians
float radians = deg_angle.as_radians();
std::cout << "45° = " << radians << " radians\n";

// Get as degrees
float degrees = deg_angle.as_degrees();
std::cout << "Value: " << degrees << "°\n";

Step 3: View Angles (Camera)

// Pitch: vertical rotation (-89° to 89°)
// Yaw: horizontal rotation (-180° to 180°)
// Roll: camera tilt (-180° to 180°)

ViewAngles camera_angles{
    PitchAngle::from_degrees(-15.0f),  // Looking slightly down
    YawAngle::from_degrees(45.0f),     // Facing northeast
    RollAngle::from_degrees(0.0f)      // No tilt
};

// Access individual components
float pitch_val = camera_angles.pitch.as_degrees();
float yaw_val = camera_angles.yaw.as_degrees();
float roll_val = camera_angles.roll.as_degrees();

Step 4: Calculating Look-At Angles

using namespace omath::source_engine;  // Or your game's engine

Vector3<float> camera_pos{0, 0, 100};
Vector3<float> target_pos{100, 100, 100};

// Calculate angles to look at target
ViewAngles look_at = CameraTrait::calc_look_at_angle(camera_pos, target_pos);

std::cout << "Pitch: " << look_at.pitch.as_degrees() << "°\n";
std::cout << "Yaw: " << look_at.yaw.as_degrees() << "°\n";
std::cout << "Roll: " << look_at.roll.as_degrees() << "°\n";

Step 5: Angle Arithmetic

// Angles support arithmetic with automatic normalization
auto angle1 = YawAngle::from_degrees(170.0f);
auto angle2 = YawAngle::from_degrees(20.0f);

// Addition (wraps around)
auto sum = angle1 + angle2;  // 190° → normalized to -170°

// Subtraction
auto diff = angle2 - angle1;  // -150°

// Scaling
auto scaled = angle1 * 2.0f;

Key takeaways: - Use specialized angle types for camera angles (PitchAngle, YawAngle, RollAngle) - Angles automatically normalize to their valid ranges - Each game engine may have different angle conventions - Use engine traits to calculate look-at angles correctly


Next Steps

Now that you've completed these tutorials, explore:


Last updated: 1 Nov 2025