mirror of
https://github.com/orange-cpp/omath.git
synced 2026-02-13 07:03:25 +00:00
Add comprehensive documentation improvements
Co-authored-by: orange-cpp <59374393+orange-cpp@users.noreply.github.com>
This commit is contained in:
532
docs/best_practices.md
Normal file
532
docs/best_practices.md
Normal file
@@ -0,0 +1,532 @@
|
||||
# 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*
|
||||
Reference in New Issue
Block a user