first build attempt
This commit is contained in:
245
src/graticule.cpp
Normal file
245
src/graticule.cpp
Normal file
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
* 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_);
|
||||
}
|
||||
Reference in New Issue
Block a user