/* * MIT License * Author: Mark Allyn * * graticule.h — incandescent bearing graticule for PPI scopes. * * Renders: * - Inner and outer bearing rings (circles) * - One tick per degree; major ticks every 10 degrees * - Text labels every GRAT_LABEL_INTERVAL_DEG degrees (15°) * * Also renders the yellow cursor arc + crossline when * renderCursor() is called. * * All geometry is in screen pixels (top-left origin); * the graticule.vert shader converts to NDC. */ #pragma once #include #include #include #include "settings.h" // Forward-declared so graticule.h doesn't pull in FreeType. class TextRenderer; class Graticule { public: Graticule() = default; ~Graticule(); // Call after GL context is current. // cx/cy: scope centre in screen pixels (top-left origin). bool init(const std::string& shaderDir, TextRenderer& tr, float cx, float cy, float radius); // Render the incandescent bearing graticule. // intensity: 0-1 scales the graticule brightness. // bearingOffset: degrees to rotate the display (Head-up mode). void render(float viewportW, float viewportH, float intensity, float bearingOffset = 0.0f) const; // Render the yellow cursor overlay. // brgDeg: cursor bearing (degrees true) // rngNorm: cursor range, normalised 0-1 (1 = scope edge) void renderCursor(float viewportW, float viewportH, float brgDeg, float rngNorm) const; private: void buildRingGeometry(); void buildTickGeometry(); GLuint prog_ = 0; GLuint ringVAO_ = 0, ringVBO_ = 0; GLuint tickVAO_ = 0, tickVBO_ = 0; GLuint cursVAO_ = 0, cursVBO_ = 0; int ringVertCount_ = 0; int tickVertCount_ = 0; // Cursor geometry is rebuilt each frame (small, dynamic) float cx_ = 0.f, cy_ = 0.f, r_ = 1.f; TextRenderer* tr_ = nullptr; // Pre-computed label screen positions (one per 15-degree step) struct LabelPos { float x, y; char text[8]; }; std::vector labels_; };