246 lines
8.5 KiB
C++
246 lines
8.5 KiB
C++
/*
|
||
* 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_);
|
||
}
|