first build attempt

This commit is contained in:
2026-04-23 08:05:03 -07:00
parent a75186cab6
commit f68524a6ae
125 changed files with 23271 additions and 463 deletions

245
src/graticule.cpp Normal file
View 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_);
}