mirror of
https://github.com/orange-cpp/omath.git
synced 2026-02-13 15:03:27 +00:00
533 lines
11 KiB
Markdown
533 lines
11 KiB
Markdown
# Best Practices
|
|
|
|
Guidelines for using OMath effectively and avoiding common pitfalls.
|
|
|
|
---
|
|
|
|
## Code Organization
|
|
|
|
### Use Type Aliases
|
|
|
|
Define clear type aliases for commonly used types:
|
|
|
|
```cpp
|
|
// Good: Clear and concise
|
|
using Vec3f = omath::Vector3<float>;
|
|
using Vec2f = omath::Vector2<float>;
|
|
using Mat4 = omath::Mat4X4;
|
|
|
|
Vec3f position{1.0f, 2.0f, 3.0f};
|
|
```
|
|
|
|
```cpp
|
|
// Avoid: Verbose and repetitive
|
|
omath::Vector3<float> position{1.0f, 2.0f, 3.0f};
|
|
omath::Vector3<float> velocity{0.0f, 0.0f, 0.0f};
|
|
```
|
|
|
|
### Namespace Usage
|
|
|
|
Be selective with `using namespace`:
|
|
|
|
```cpp
|
|
// Good: Specific namespace for your engine
|
|
using namespace omath::source_engine;
|
|
|
|
// Good: Import specific types
|
|
using omath::Vector3;
|
|
using omath::Vector2;
|
|
|
|
// Avoid: Too broad
|
|
using namespace omath; // Imports everything
|
|
```
|
|
|
|
### Include What You Use
|
|
|
|
```cpp
|
|
// Good: Include specific headers
|
|
#include <omath/linear_algebra/vector3.hpp>
|
|
#include <omath/projection/camera.hpp>
|
|
|
|
// Okay for development
|
|
#include <omath/omath.hpp>
|
|
|
|
// Production: Include only what you need
|
|
// to reduce compile times
|
|
```
|
|
|
|
---
|
|
|
|
## Error Handling
|
|
|
|
### Always Check Optional Results
|
|
|
|
```cpp
|
|
// Good: Check before using
|
|
if (auto screen = camera.world_to_screen(world_pos)) {
|
|
draw_at(screen->x, screen->y);
|
|
} else {
|
|
// Handle point not visible
|
|
}
|
|
|
|
// Bad: Unchecked access can crash
|
|
auto screen = camera.world_to_screen(world_pos);
|
|
draw_at(screen->x, screen->y); // Undefined behavior if nullopt!
|
|
```
|
|
|
|
### Handle Expected Errors
|
|
|
|
```cpp
|
|
// Good: Handle error case
|
|
if (auto angle = v1.angle_between(v2)) {
|
|
use_angle(*angle);
|
|
} else {
|
|
switch (angle.error()) {
|
|
case Vector3Error::IMPOSSIBLE_BETWEEN_ANGLE:
|
|
// Handle zero-length vector
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Bad: Assume success
|
|
auto angle = v1.angle_between(v2);
|
|
use_angle(*angle); // Throws if error!
|
|
```
|
|
|
|
### Validate Inputs
|
|
|
|
```cpp
|
|
// Good: Validate before expensive operations
|
|
bool is_valid_projectile(const Projectile& proj) {
|
|
return proj.speed > 0.0f &&
|
|
std::isfinite(proj.speed) &&
|
|
std::isfinite(proj.origin.length());
|
|
}
|
|
|
|
if (is_valid_projectile(proj) && is_valid_target(target)) {
|
|
auto aim = engine.maybe_calculate_aim_point(proj, target);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Performance
|
|
|
|
### Use constexpr When Possible
|
|
|
|
```cpp
|
|
// Good: Computed at compile time
|
|
constexpr Vector3<float> gravity{0.0f, 0.0f, -9.81f};
|
|
constexpr float max_range = 1000.0f;
|
|
constexpr float max_range_sq = max_range * max_range;
|
|
|
|
// Use in runtime calculations
|
|
if (position.length_sqr() < max_range_sq) {
|
|
// ...
|
|
}
|
|
```
|
|
|
|
### Prefer Squared Distance
|
|
|
|
```cpp
|
|
// Good: Avoids expensive sqrt
|
|
constexpr float max_dist_sq = 100.0f * 100.0f;
|
|
for (const auto& entity : entities) {
|
|
if (entity.pos.distance_to_sqr(player_pos) < max_dist_sq) {
|
|
// Process nearby entity
|
|
}
|
|
}
|
|
|
|
// Avoid: Unnecessary sqrt calls
|
|
constexpr float max_dist = 100.0f;
|
|
for (const auto& entity : entities) {
|
|
if (entity.pos.distance_to(player_pos) < max_dist) {
|
|
// More expensive
|
|
}
|
|
}
|
|
```
|
|
|
|
### Cache Expensive Calculations
|
|
|
|
```cpp
|
|
// Good: Update camera once per frame
|
|
void update_frame() {
|
|
camera.update(current_position, current_angles);
|
|
|
|
// All projections use cached matrices
|
|
for (const auto& entity : entities) {
|
|
if (auto screen = camera.world_to_screen(entity.pos)) {
|
|
draw_entity(screen->x, screen->y);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Bad: Camera recreated each call
|
|
for (const auto& entity : entities) {
|
|
Camera cam(pos, angles, viewport, fov, near, far); // Expensive!
|
|
auto screen = cam.world_to_screen(entity.pos);
|
|
}
|
|
```
|
|
|
|
### Choose the Right Engine
|
|
|
|
```cpp
|
|
// Good: Use AVX2 when available
|
|
#ifdef __AVX2__
|
|
using Engine = ProjPredEngineAVX2;
|
|
#else
|
|
using Engine = ProjPredEngineLegacy;
|
|
#endif
|
|
|
|
Engine prediction_engine;
|
|
|
|
// Or runtime detection
|
|
Engine* create_best_engine() {
|
|
if (cpu_supports_avx2()) {
|
|
return new ProjPredEngineAVX2();
|
|
}
|
|
return new ProjPredEngineLegacy();
|
|
}
|
|
```
|
|
|
|
### Minimize Allocations
|
|
|
|
```cpp
|
|
// Good: Reuse vectors
|
|
std::vector<Vector3<float>> positions;
|
|
positions.reserve(expected_count);
|
|
|
|
// In loop
|
|
positions.clear(); // Doesn't deallocate
|
|
for (...) {
|
|
positions.push_back(compute_position());
|
|
}
|
|
|
|
// Bad: Allocate every time
|
|
for (...) {
|
|
std::vector<Vector3<float>> positions; // Allocates each iteration
|
|
// ...
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Type Safety
|
|
|
|
### Use Strong Angle Types
|
|
|
|
```cpp
|
|
// Good: Type-safe angles
|
|
PitchAngle pitch = PitchAngle::from_degrees(45.0f);
|
|
YawAngle yaw = YawAngle::from_degrees(90.0f);
|
|
ViewAngles angles{pitch, yaw, RollAngle::from_degrees(0.0f)};
|
|
|
|
// Bad: Raw floats lose safety
|
|
float pitch = 45.0f; // No range checking
|
|
float yaw = 90.0f; // Can go out of bounds
|
|
```
|
|
|
|
### Match Engine Types
|
|
|
|
```cpp
|
|
// Good: Use matching types from same engine
|
|
using namespace omath::source_engine;
|
|
Camera camera = /* ... */;
|
|
ViewAngles angles = /* ... */;
|
|
|
|
// Bad: Mixing engine types
|
|
using UnityCamera = omath::unity_engine::Camera;
|
|
using SourceAngles = omath::source_engine::ViewAngles;
|
|
UnityCamera camera{pos, SourceAngles{}, ...}; // May cause issues!
|
|
```
|
|
|
|
### Template Type Parameters
|
|
|
|
```cpp
|
|
// Good: Explicit and clear
|
|
Vector3<float> position;
|
|
Vector3<double> high_precision_pos;
|
|
|
|
// Okay: Use default float
|
|
Vector3<> position; // Defaults to float
|
|
|
|
// Avoid: Mixing types unintentionally
|
|
Vector3<float> a;
|
|
Vector3<double> b;
|
|
auto result = a + b; // Type mismatch!
|
|
```
|
|
|
|
---
|
|
|
|
## Testing & Validation
|
|
|
|
### Test Edge Cases
|
|
|
|
```cpp
|
|
void test_projection() {
|
|
Camera camera = setup_camera();
|
|
|
|
// Test normal case
|
|
assert(camera.world_to_screen({100, 100, 100}).has_value());
|
|
|
|
// Test edge cases
|
|
assert(!camera.world_to_screen({0, 0, -100}).has_value()); // Behind
|
|
assert(!camera.world_to_screen({1e10, 0, 0}).has_value()); // Too far
|
|
|
|
// Test boundaries
|
|
Vector3<float> at_near{0, 0, camera.near_plane() + 0.1f};
|
|
assert(camera.world_to_screen(at_near).has_value());
|
|
}
|
|
```
|
|
|
|
### Validate Assumptions
|
|
|
|
```cpp
|
|
void validate_game_data() {
|
|
// Validate FOV
|
|
float fov = read_game_fov();
|
|
assert(fov > 1.0f && fov < 179.0f);
|
|
|
|
// Validate positions
|
|
Vector3<float> pos = read_player_position();
|
|
assert(std::isfinite(pos.x));
|
|
assert(std::isfinite(pos.y));
|
|
assert(std::isfinite(pos.z));
|
|
|
|
// Validate viewport
|
|
ViewPort vp = read_viewport();
|
|
assert(vp.width > 0 && vp.height > 0);
|
|
}
|
|
```
|
|
|
|
### Use Assertions
|
|
|
|
```cpp
|
|
// Good: Catch errors early in development
|
|
void shoot_projectile(const Projectile& proj) {
|
|
assert(proj.speed > 0.0f && "Projectile speed must be positive");
|
|
assert(std::isfinite(proj.origin.length()) && "Invalid projectile origin");
|
|
|
|
// Continue with logic
|
|
}
|
|
|
|
// Add debug-only checks
|
|
#ifndef NDEBUG
|
|
if (!is_valid_input(data)) {
|
|
std::cerr << "Warning: Invalid input detected\n";
|
|
}
|
|
#endif
|
|
```
|
|
|
|
---
|
|
|
|
## Memory & Resources
|
|
|
|
### RAII for Resources
|
|
|
|
```cpp
|
|
// Good: Automatic cleanup
|
|
class GameOverlay {
|
|
Camera camera_;
|
|
std::vector<Entity> entities_;
|
|
|
|
public:
|
|
GameOverlay(/* ... */) : camera_(/* ... */) {
|
|
entities_.reserve(1000);
|
|
}
|
|
|
|
// Resources cleaned up automatically
|
|
~GameOverlay() = default;
|
|
};
|
|
```
|
|
|
|
### Avoid Unnecessary Copies
|
|
|
|
```cpp
|
|
// Good: Pass by const reference
|
|
void draw_entities(const std::vector<Vector3<float>>& positions) {
|
|
for (const auto& pos : positions) {
|
|
// Process position
|
|
}
|
|
}
|
|
|
|
// Bad: Copies entire vector
|
|
void draw_entities(std::vector<Vector3<float>> positions) {
|
|
// Expensive copy!
|
|
}
|
|
|
|
// Good: Move when transferring ownership
|
|
std::vector<Vector3<float>> compute_positions();
|
|
auto positions = compute_positions(); // Move, not copy
|
|
```
|
|
|
|
### Use Structured Bindings
|
|
|
|
```cpp
|
|
// Good: Clear and concise
|
|
if (auto [success, screen_pos] = try_project(world_pos); success) {
|
|
draw_at(screen_pos.x, screen_pos.y);
|
|
}
|
|
|
|
// Good: Decompose results
|
|
auto [x, y, z] = position.as_tuple();
|
|
```
|
|
|
|
---
|
|
|
|
## Documentation
|
|
|
|
### Document Assumptions
|
|
|
|
```cpp
|
|
// Good: Clear documentation
|
|
/**
|
|
* Projects world position to screen space.
|
|
*
|
|
* @param world_pos Position in world coordinates (meters)
|
|
* @return Screen position if visible, nullopt if behind camera or out of view
|
|
*
|
|
* @note Assumes camera.update() was called this frame
|
|
* @note Screen coordinates are in viewport space [0, width] x [0, height]
|
|
*/
|
|
std::optional<Vector2<float>> project(const Vector3<float>& world_pos);
|
|
```
|
|
|
|
### Explain Non-Obvious Code
|
|
|
|
```cpp
|
|
// Good: Explain the math
|
|
// Use squared distance to avoid expensive sqrt
|
|
// max_range = 100.0 → max_range_sq = 10000.0
|
|
constexpr float max_range_sq = 100.0f * 100.0f;
|
|
if (dist_sq < max_range_sq) {
|
|
// Entity is in range
|
|
}
|
|
|
|
// Explain engine-specific quirks
|
|
// Source Engine uses Z-up coordinates, but angles are in degrees
|
|
// Pitch: [-89, 89], Yaw: [-180, 180], Roll: [-180, 180]
|
|
ViewAngles angles{pitch, yaw, roll};
|
|
```
|
|
|
|
---
|
|
|
|
## Debugging
|
|
|
|
### Add Debug Visualization
|
|
|
|
```cpp
|
|
#ifndef NDEBUG
|
|
void debug_draw_projection() {
|
|
// Draw camera frustum
|
|
draw_frustum(camera);
|
|
|
|
// Draw world axes
|
|
draw_line({0,0,0}, {100,0,0}, Color::Red); // X
|
|
draw_line({0,0,0}, {0,100,0}, Color::Green); // Y
|
|
draw_line({0,0,0}, {0,0,100}, Color::Blue); // Z
|
|
|
|
// Draw projected points
|
|
for (const auto& entity : entities) {
|
|
if (auto screen = camera.world_to_screen(entity.pos)) {
|
|
draw_cross(screen->x, screen->y);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
```
|
|
|
|
### Log Important Values
|
|
|
|
```cpp
|
|
void debug_projection_failure(const Vector3<float>& pos) {
|
|
std::cerr << "Projection failed for position: "
|
|
<< pos.x << ", " << pos.y << ", " << pos.z << "\n";
|
|
|
|
auto view_matrix = camera.get_view_matrix();
|
|
std::cerr << "View matrix:\n";
|
|
// Print matrix...
|
|
|
|
std::cerr << "Camera position: "
|
|
<< camera.position().x << ", "
|
|
<< camera.position().y << ", "
|
|
<< camera.position().z << "\n";
|
|
}
|
|
```
|
|
|
|
### Use Debug Builds
|
|
|
|
```cmake
|
|
# CMakeLists.txt
|
|
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
|
target_compile_definitions(your_target PRIVATE
|
|
DEBUG_PROJECTION=1
|
|
VALIDATE_INPUTS=1
|
|
)
|
|
endif()
|
|
```
|
|
|
|
```cpp
|
|
#ifdef DEBUG_PROJECTION
|
|
std::cout << "Projecting: " << world_pos << "\n";
|
|
#endif
|
|
|
|
#ifdef VALIDATE_INPUTS
|
|
assert(std::isfinite(world_pos.length()));
|
|
#endif
|
|
```
|
|
|
|
---
|
|
|
|
## Platform Considerations
|
|
|
|
### Cross-Platform Code
|
|
|
|
```cpp
|
|
// Good: Platform-agnostic
|
|
constexpr float PI = 3.14159265359f;
|
|
|
|
// Avoid: Platform-specific
|
|
#ifdef _WIN32
|
|
// Windows-only code
|
|
#endif
|
|
```
|
|
|
|
### Handle Different Compilers
|
|
|
|
```cpp
|
|
// Good: Compiler-agnostic
|
|
#if defined(_MSC_VER)
|
|
// MSVC-specific
|
|
#elif defined(__GNUC__)
|
|
// GCC/Clang-specific
|
|
#endif
|
|
|
|
// Use OMath's built-in compatibility
|
|
// It handles compiler differences automatically
|
|
```
|
|
|
|
---
|
|
|
|
## Summary
|
|
|
|
**Key principles:**
|
|
1. **Safety first**: Always check optional/expected results
|
|
2. **Performance matters**: Use constexpr, avoid allocations, cache results
|
|
3. **Type safety**: Use strong types, match engine types
|
|
4. **Clear code**: Use aliases, document assumptions, explain non-obvious logic
|
|
5. **Test thoroughly**: Validate inputs, test edge cases, add assertions
|
|
6. **Debug effectively**: Add visualization, log values, use debug builds
|
|
|
|
---
|
|
|
|
## See Also
|
|
|
|
- [Troubleshooting Guide](troubleshooting.md)
|
|
- [FAQ](faq.md)
|
|
- [API Overview](api_overview.md)
|
|
- [Tutorials](tutorials.md)
|
|
|
|
---
|
|
|
|
*Last updated: 1 Nov 2025*
|