Files
updated-radar/src/graticule.cpp
2026-04-23 08:05:03 -07:00

246 lines
8.5 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* MIT License
* Author: Mark Allyn
*
* graticule.cpp — incandescent bearing graticule and cursor overlay.
*/
#include "graticule.h"
#include "left_panel.h" // for TextRenderer definition
#include <fstream>
#include <sstream>
#include <cmath>
#include <cstdio>
#include <cstring>
static const float PI = 3.14159265f;
// ---- Shader helpers (same pattern as phosphor.cpp) ----
static std::string gReadFile(const std::string& p) {
std::ifstream f(p);
if (!f.is_open()) { fprintf(stderr,"graticule: can't open %s\n",p.c_str()); return ""; }
std::ostringstream ss; ss << f.rdbuf(); return ss.str();
}
static GLuint gCompile(GLenum type, const std::string& src) {
const char* s = src.c_str();
GLuint sh = glCreateShader(type);
glShaderSource(sh,1,&s,nullptr); glCompileShader(sh);
GLint ok=0; glGetShaderiv(sh,GL_COMPILE_STATUS,&ok);
if(!ok){char l[1024]; glGetShaderInfoLog(sh,1024,nullptr,l); fprintf(stderr,"grat shader: %s\n",l);}
return sh;
}
static GLuint gLink(const std::string& vp, const std::string& fp) {
std::string vs=gReadFile(vp), fs=gReadFile(fp);
if(vs.empty()||fs.empty()) return 0;
GLuint v=gCompile(GL_VERTEX_SHADER,vs), f=gCompile(GL_FRAGMENT_SHADER,fs);
GLuint p=glCreateProgram(); glAttachShader(p,v); glAttachShader(p,f); glLinkProgram(p);
GLint ok=0; glGetProgramiv(p,GL_LINK_STATUS,&ok);
if(!ok){char l[1024]; glGetProgramInfoLog(p,1024,nullptr,l); fprintf(stderr,"grat prog: %s\n",l);}
glDeleteShader(v); glDeleteShader(f); return p;
}
// ----------------------------------------------------------------
static float brgToRadians(float deg, float offset) {
// Convert bearing (CW from north) to math angle (CCW from east),
// subtracting any display offset. Result in radians.
float a = 90.0f - (deg - offset);
return a * PI / 180.0f;
}
// ----------------------------------------------------------------
bool Graticule::init(const std::string& shaderDir,
TextRenderer& tr,
float cx, float cy, float radius)
{
cx_ = cx; cy_ = cy; r_ = radius;
tr_ = &tr;
prog_ = gLink(shaderDir + "graticule.vert", shaderDir + "graticule.frag");
if (!prog_) return false;
buildRingGeometry();
buildTickGeometry();
// Pre-compute label positions at GRAT_LABEL_INTERVAL_DEG steps
labels_.clear();
float labelR = r_ * GRAT_LABEL_RING_FRAC;
for (int deg = 0; deg < 360; deg += GRAT_LABEL_INTERVAL_DEG) {
float rad = brgToRadians(static_cast<float>(deg), 0.0f);
LabelPos lp;
lp.x = cx_ + labelR * std::cos(rad);
lp.y = cy_ - labelR * std::sin(rad); // screen y is inverted
snprintf(lp.text, sizeof(lp.text), "%d", deg);
labels_.push_back(lp);
}
// Cursor geometry is dynamic; create empty VAO/VBO now
glGenVertexArrays(1, &cursVAO_);
glGenBuffers(1, &cursVBO_);
glBindVertexArray(cursVAO_);
glBindBuffer(GL_ARRAY_BUFFER, cursVBO_);
glBufferData(GL_ARRAY_BUFFER, 200 * sizeof(float) * 2, nullptr, GL_DYNAMIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2*sizeof(float), nullptr);
glBindVertexArray(0);
return true;
}
void Graticule::buildRingGeometry() {
const int N = GRAT_SEGMENTS;
std::vector<float> verts;
verts.reserve((N + 1) * 2 * 2); // inner + outer ring
for (int ring = 0; ring < 2; ++ring) {
float rad = r_ * (ring == 0 ? GRAT_INNER_RING_FRAC : GRAT_OUTER_RING_FRAC);
for (int i = 0; i <= N; ++i) {
float a = 2.0f * PI * i / N;
verts.push_back(cx_ + rad * std::cos(a));
verts.push_back(cy_ - rad * std::sin(a)); // flip y for screen
}
}
ringVertCount_ = static_cast<int>(verts.size()) / 2;
glGenVertexArrays(1, &ringVAO_);
glGenBuffers(1, &ringVBO_);
glBindVertexArray(ringVAO_);
glBindBuffer(GL_ARRAY_BUFFER, ringVBO_);
glBufferData(GL_ARRAY_BUFFER, verts.size() * sizeof(float), verts.data(), GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2*sizeof(float), nullptr);
glBindVertexArray(0);
}
void Graticule::buildTickGeometry() {
std::vector<float> verts;
verts.reserve(360 * 4); // 2 verts × 2 floats per tick
float innerR = r_ * GRAT_INNER_RING_FRAC;
for (int deg = 0; deg < 360; ++deg) {
float rad = brgToRadians(static_cast<float>(deg), 0.0f);
float cR = std::cos(rad);
float sR = std::sin(rad);
// Major ticks every 10 degrees, minor otherwise
float len = (deg % 10 == 0)
? r_ * GRAT_TICK_MAJOR_FRAC
: r_ * GRAT_TICK_MINOR_FRAC;
float x0 = cx_ + innerR * cR;
float y0 = cy_ - innerR * sR;
float x1 = cx_ + (innerR - len) * cR;
float y1 = cy_ - (innerR - len) * sR;
verts.push_back(x0); verts.push_back(y0);
verts.push_back(x1); verts.push_back(y1);
}
tickVertCount_ = static_cast<int>(verts.size()) / 2;
glGenVertexArrays(1, &tickVAO_);
glGenBuffers(1, &tickVBO_);
glBindVertexArray(tickVAO_);
glBindBuffer(GL_ARRAY_BUFFER, tickVBO_);
glBufferData(GL_ARRAY_BUFFER, verts.size() * sizeof(float), verts.data(), GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2*sizeof(float), nullptr);
glBindVertexArray(0);
}
// ----------------------------------------------------------------
void Graticule::render(float viewportW, float viewportH,
float intensity, float /*bearingOffset*/) const
{
if (!prog_) return;
glUseProgram(prog_);
glUniform2f(glGetUniformLocation(prog_, "u_viewportSize"), viewportW, viewportH);
float r = GRAT_R * intensity;
float g = GRAT_G * intensity;
float b = GRAT_B * intensity;
glUniform4f(glGetUniformLocation(prog_, "u_color"), r, g, b, 1.0f);
// Inner and outer rings (LINE_STRIP segments)
glBindVertexArray(ringVAO_);
int half = ringVertCount_ / 2;
glDrawArrays(GL_LINE_STRIP, 0, half); // inner
glDrawArrays(GL_LINE_STRIP, half, half); // outer
glBindVertexArray(0);
// Tick marks
glBindVertexArray(tickVAO_);
glDrawArrays(GL_LINES, 0, tickVertCount_);
glBindVertexArray(0);
// Text labels
if (tr_) {
for (const auto& lp : labels_) {
tr_->renderText(lp.text, lp.x, lp.y,
static_cast<float>(GRATICULE_LABEL_FONT_SIZE) / 16.0f,
r, g, b,
viewportW, viewportH,
true /* centred */);
}
}
}
void Graticule::renderCursor(float viewportW, float viewportH,
float brgDeg, float rngNorm) const
{
if (!prog_ || !cursVAO_) return;
// Build cursor geometry: 10° arc + bearing crossline
std::vector<float> verts;
const float arcSpan = CURSOR_ARC_SPAN_DEG;
const float arcSteps = 20.0f;
const float cursorR = r_ * rngNorm;
// Arc
for (int i = 0; i <= (int)arcSteps; ++i) {
float a = brgDeg - arcSpan * 0.5f + arcSpan * i / arcSteps;
float rad = brgToRadians(a, 0.0f);
verts.push_back(cx_ + cursorR * std::cos(rad));
verts.push_back(cy_ - cursorR * std::sin(rad));
}
// Bearing crossline: scope centre → just beyond arc
float lineRad = brgToRadians(brgDeg, 0.0f);
float lineLen = cursorR + r_ * 0.04f; // slightly beyond arc
verts.push_back(cx_);
verts.push_back(cy_);
verts.push_back(cx_ + lineLen * std::cos(lineRad));
verts.push_back(cy_ - lineLen * std::sin(lineRad));
int nVerts = static_cast<int>(verts.size()) / 2;
int arcEnd = static_cast<int>(arcSteps) + 1;
glBindBuffer(GL_ARRAY_BUFFER, cursVBO_);
glBufferSubData(GL_ARRAY_BUFFER, 0, verts.size() * sizeof(float), verts.data());
glUseProgram(prog_);
glUniform2f(glGetUniformLocation(prog_, "u_viewportSize"), viewportW, viewportH);
glUniform4f(glGetUniformLocation(prog_, "u_color"),
CURSOR_R, CURSOR_G, CURSOR_B, 1.0f);
glBindVertexArray(cursVAO_);
glDrawArrays(GL_LINE_STRIP, 0, arcEnd); // arc
glDrawArrays(GL_LINES, arcEnd, 2); // crossline
glBindVertexArray(0);
}
Graticule::~Graticule() {
if (prog_) glDeleteProgram(prog_);
if (ringVAO_) glDeleteVertexArrays(1, &ringVAO_);
if (ringVBO_) glDeleteBuffers(1, &ringVBO_);
if (tickVAO_) glDeleteVertexArrays(1, &tickVAO_);
if (tickVBO_) glDeleteBuffers(1, &tickVBO_);
if (cursVAO_) glDeleteVertexArrays(1, &cursVAO_);
if (cursVBO_) glDeleteBuffers(1, &cursVBO_);
}