Adding changing notifier and quickened graticule change
This commit is contained in:
39
CLAUDE.md
39
CLAUDE.md
@@ -276,6 +276,36 @@ The Raspberry Pi code will live in a separate git repository with its own CLAUDE
|
|||||||
and its own CMakeLists.txt, since it targets a different architecture (ARM) and has
|
and its own CMakeLists.txt, since it targets a different architecture (ARM) and has
|
||||||
a different toolchain and dependencies. Do not mix it into this repository hierarchy
|
a different toolchain and dependencies. Do not mix it into this repository hierarchy
|
||||||
|
|
||||||
|
|
||||||
|
NEW suggestions =======
|
||||||
|
|
||||||
|
1. The a scope bearing must not follow the rotating sweep on the ppi scope. The A scope
|
||||||
|
during the time they were popular before the ppi scope used a manual control to set the
|
||||||
|
bearing. The bearing setting uses a servo motor to rotate the dish antenna to the bearing
|
||||||
|
desired by the operator. We need to simulate that by not a scope not to follow the bearing
|
||||||
|
shown on the ppi scope. This bearing must be controled by control 7.
|
||||||
|
|
||||||
|
2. Add two more controls; Range for ppi scope cursor and Bearing for the PPI scope cursor
|
||||||
|
|
||||||
|
3. Pleae add a small window under the A scope to show the setting of the A scope bearnig.
|
||||||
|
|
||||||
|
4. Temporary: Have the bearing for a scope set to the location of one of the current targets.
|
||||||
|
|
||||||
|
5. Have the cursor on the ppi scope follow one of the targets and not move around.
|
||||||
|
|
||||||
|
New update 1
|
||||||
|
|
||||||
|
1. I notice that the height of the pulse on the A scope seems to be going up and down in sync
|
||||||
|
with the ppi scope rotating.
|
||||||
|
2. Add a range figure in the small box under the A scope
|
||||||
|
3. Have the a scope graticules change a little faster
|
||||||
|
4. it appears that the cursor range ring sometimes appears outside the boundary of the scope
|
||||||
|
|
||||||
|
New update 2
|
||||||
|
1. Add small text window above that a scope that says "Operator Manually Changing Range Graticule"
|
||||||
|
This appears only when the graticule is changing
|
||||||
|
|
||||||
|
|
||||||
NOTE on my plan for coding
|
NOTE on my plan for coding
|
||||||
|
|
||||||
1. I want to test and debug the code feature by feature.
|
1. I want to test and debug the code feature by feature.
|
||||||
@@ -305,10 +335,5 @@ Order of testing features.
|
|||||||
|
|
||||||
========================================================
|
========================================================
|
||||||
|
|
||||||
Generate code for testiong feature 1 and 2 and 3 only;
|
Generate code for testiong feature 1,2,3,4,5,6 nly;
|
||||||
1. General initialization and set up basic boundaries of the two scopes
|
|
||||||
on the screen. No features on each scope yet.
|
|
||||||
2. Edge graticule on ppi scope (Bearing ticks and numbers)
|
|
||||||
3. replaceable graticules for A scope
|
|
||||||
Do not generate any other code
|
|
||||||
Generate code the run this and hold for 10 seconds and exit
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
297
src/main.cpp
297
src/main.cpp
@@ -23,6 +23,7 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
// ─── Constants ───────────────────────────────────────────────────────────────
|
// ─── Constants ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -49,15 +50,15 @@ static constexpr float P7P_R = 0.35f;
|
|||||||
static constexpr float P7P_G = 0.88f;
|
static constexpr float P7P_G = 0.88f;
|
||||||
static constexpr float P7P_B = 0.18f;
|
static constexpr float P7P_B = 0.18f;
|
||||||
|
|
||||||
// Digits rendered into the font atlas: '0'–'9'
|
// Full printable ASCII rendered into the font atlas: ' '(32) through '~'(126)
|
||||||
static constexpr int GLYPH_FIRST = '0';
|
static constexpr int GLYPH_FIRST = ' ';
|
||||||
static constexpr int GLYPH_COUNT = 10;
|
static constexpr int GLYPH_COUNT = 95;
|
||||||
|
|
||||||
// Feature 3 timing
|
// Feature 3 timing
|
||||||
static constexpr float HOLD_SEC = 20.0f; // seconds per range setting
|
static constexpr float HOLD_SEC = 5.0f; // seconds per range setting
|
||||||
static constexpr float SLIDE_OUT_SEC = 5.0f; // slide current graticule out (up)
|
static constexpr float SLIDE_OUT_SEC = 2.0f; // slide current graticule out (up)
|
||||||
static constexpr float WAIT_SEC = 2.0f; // blank pause between graticules
|
static constexpr float WAIT_SEC = 0.5f; // blank pause between graticules
|
||||||
static constexpr float SLIDE_IN_SEC = 5.0f; // slide new graticule in (from top)
|
static constexpr float SLIDE_IN_SEC = 2.0f; // slide new graticule in (from top)
|
||||||
|
|
||||||
// Feature 4 — sweep
|
// Feature 4 — sweep
|
||||||
static constexpr float SWEEP_DEG_PS = 20.0f * 6.0f; // 20 RPM → 120 °/s
|
static constexpr float SWEEP_DEG_PS = 20.0f * 6.0f; // 20 RPM → 120 °/s
|
||||||
@@ -278,6 +279,8 @@ static void appendTextQuads(std::vector<float>& verts, const FontAtlas& fa,
|
|||||||
struct Layout {
|
struct Layout {
|
||||||
float ppiCX, ppiCY, ppiR;
|
float ppiCX, ppiCY, ppiR;
|
||||||
float asLeft, asTop, asRight, asBot;
|
float asLeft, asTop, asRight, asBot;
|
||||||
|
float brgLeft, brgTop, brgRight, brgBot; // A scope bearing display window
|
||||||
|
float notifyLeft, notifyRight, notifyTop, notifyBot; // "changing graticule" notice
|
||||||
};
|
};
|
||||||
|
|
||||||
static Layout computeLayout(float W, float H, float marginPx)
|
static Layout computeLayout(float W, float H, float marginPx)
|
||||||
@@ -286,7 +289,10 @@ static Layout computeLayout(float W, float H, float marginPx)
|
|||||||
|
|
||||||
float availW = (W - marginPx) - W * 0.5f;
|
float availW = (W - marginPx) - W * 0.5f;
|
||||||
float availH = (H - marginPx) - marginPx;
|
float availH = (H - marginPx) - marginPx;
|
||||||
L.ppiR = std::min(availW, availH) * 0.5f;
|
// The bearing graticule outer ring sits at ppiR * 1.18, so shrink ppiR so
|
||||||
|
// that outer ring — not the inner circle — lands at the margin boundary.
|
||||||
|
static constexpr float GRAT_SCALE = 1.18f;
|
||||||
|
L.ppiR = std::min(availW, availH) * 0.5f / GRAT_SCALE;
|
||||||
L.ppiCX = W * 0.5f + availW * 0.5f;
|
L.ppiCX = W * 0.5f + availW * 0.5f;
|
||||||
L.ppiCY = marginPx + availH * 0.5f;
|
L.ppiCY = marginPx + availH * 0.5f;
|
||||||
|
|
||||||
@@ -297,6 +303,22 @@ static Layout computeLayout(float W, float H, float marginPx)
|
|||||||
L.asTop = (H - asH) * 0.5f;
|
L.asTop = (H - asH) * 0.5f;
|
||||||
L.asBot = L.asTop + asH;
|
L.asBot = L.asTop + asH;
|
||||||
|
|
||||||
|
// Bearing display window — small box directly below the A scope
|
||||||
|
const float brgGap = H * 0.008f; // gap between A scope bottom and window top
|
||||||
|
const float brgH = H * 0.060f; // window height (two text lines)
|
||||||
|
L.brgLeft = L.asLeft;
|
||||||
|
L.brgRight = L.asRight;
|
||||||
|
L.brgTop = L.asBot + brgGap;
|
||||||
|
L.brgBot = L.brgTop + brgH;
|
||||||
|
|
||||||
|
// Notification window — small box directly above the A scope
|
||||||
|
const float notifyGap = H * 0.008f;
|
||||||
|
const float notifyH = H * 0.040f;
|
||||||
|
L.notifyLeft = L.asLeft;
|
||||||
|
L.notifyRight = L.asRight;
|
||||||
|
L.notifyBot = L.asTop - notifyGap;
|
||||||
|
L.notifyTop = L.notifyBot - notifyH;
|
||||||
|
|
||||||
return L;
|
return L;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -802,10 +824,13 @@ static AScopeTrace buildAScopeTrace()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Renders target return spikes on the A scope clipped to the A scope box.
|
// Renders target return spikes on the A scope clipped to the A scope box.
|
||||||
|
// The A scope antenna is pointed at a fixed bearing (ascopeBearingDeg),
|
||||||
|
// independent of the PPI sweep. Every radar pulse illuminates that bearing,
|
||||||
|
// so returns are continuous — brightness is steady, no sweep dependency.
|
||||||
static void renderAScopeTrace(AScopeTrace& at, const Layout& L,
|
static void renderAScopeTrace(AScopeTrace& at, const Layout& L,
|
||||||
FakeTarget* tgts, int nTgts,
|
FakeTarget* tgts, int nTgts,
|
||||||
float maxRangeMi, float ascopeBearingDeg,
|
float maxRangeMi, float ascopeBearingDeg,
|
||||||
float sweepAngle, float curTime, float W, float H)
|
float W, float H)
|
||||||
{
|
{
|
||||||
static const float THRESH = 3.5f;
|
static const float THRESH = 3.5f;
|
||||||
static const float PAD = 4.0f;
|
static const float PAD = 4.0f;
|
||||||
@@ -818,8 +843,6 @@ static void renderAScopeTrace(AScopeTrace& at, const Layout& L,
|
|||||||
const float sigW = gx1 - gx0;
|
const float sigW = gx1 - gx0;
|
||||||
const float sigH = (L.asTop + PAD) - gy1; // negative: upward from baseline
|
const float sigH = (L.asTop + PAD) - gy1; // negative: upward from baseline
|
||||||
|
|
||||||
const bool sweepHere = angDiff(sweepAngle, ascopeBearingDeg) < THRESH;
|
|
||||||
|
|
||||||
glEnable(GL_SCISSOR_TEST);
|
glEnable(GL_SCISSOR_TEST);
|
||||||
glScissor((GLint)L.asLeft, (GLint)(H - L.asBot),
|
glScissor((GLint)L.asLeft, (GLint)(H - L.asBot),
|
||||||
(GLint)(L.asRight - L.asLeft), (GLint)(L.asBot - L.asTop));
|
(GLint)(L.asRight - L.asLeft), (GLint)(L.asBot - L.asTop));
|
||||||
@@ -828,27 +851,22 @@ static void renderAScopeTrace(AScopeTrace& at, const Layout& L,
|
|||||||
const GLint locCol = glGetUniformLocation(at.prog, "uColor");
|
const GLint locCol = glGetUniformLocation(at.prog, "uColor");
|
||||||
|
|
||||||
for (int i = 0; i < nTgts; ++i) {
|
for (int i = 0; i < nTgts; ++i) {
|
||||||
FakeTarget& t = tgts[i];
|
const FakeTarget& t = tgts[i];
|
||||||
|
|
||||||
if (sweepHere && angDiff(ascopeBearingDeg, t.bearingDeg) < THRESH)
|
// Only show targets whose bearing falls within the A scope beam
|
||||||
at.lastActT[i] = curTime;
|
if (angDiff(ascopeBearingDeg, t.bearingDeg) >= THRESH) continue;
|
||||||
|
|
||||||
float rangeFrac = t.rangeMiles / maxRangeMi;
|
float rangeFrac = t.rangeMiles / maxRangeMi;
|
||||||
if (rangeFrac > 1.0f) continue;
|
if (rangeFrac > 1.0f) continue;
|
||||||
|
|
||||||
float dt = curTime - at.lastActT[i];
|
|
||||||
float fade = (at.lastActT[i] < 0.0f) ? 0.0f
|
|
||||||
: std::max(0.0f, 1.0f - dt / TARG_PERSIST);
|
|
||||||
if (fade < 0.02f) continue;
|
|
||||||
|
|
||||||
float coreRad, bloomRad, baseBright;
|
float coreRad, bloomRad, baseBright;
|
||||||
sizeFromApparent(apparentFt(t), L.ppiR, coreRad, bloomRad, baseBright);
|
sizeFromApparent(apparentFt(t), L.ppiR, coreRad, bloomRad, baseBright);
|
||||||
float bright = baseBright * fade;
|
|
||||||
float height = sigH * bright; // upward (sigH is negative → spike goes up)
|
float height = sigH * baseBright; // upward (sigH is negative)
|
||||||
|
|
||||||
float sx = gx0 + rangeFrac * sigW;
|
float sx = gx0 + rangeFrac * sigW;
|
||||||
float sy0 = gy1; // baseline
|
float sy0 = gy1;
|
||||||
float sy1 = gy1 + height; // peak (upward because sigH < 0)
|
float sy1 = gy1 + height;
|
||||||
|
|
||||||
float verts[6] = {
|
float verts[6] = {
|
||||||
ndcX(sx - HALF_W, W), ndcY(sy0, H),
|
ndcX(sx - HALF_W, W), ndcY(sy0, H),
|
||||||
@@ -857,7 +875,7 @@ static void renderAScopeTrace(AScopeTrace& at, const Layout& L,
|
|||||||
};
|
};
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, at.vbo);
|
glBindBuffer(GL_ARRAY_BUFFER, at.vbo);
|
||||||
glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_DYNAMIC_DRAW);
|
glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_DYNAMIC_DRAW);
|
||||||
glUniform3f(locCol, P1_R * bright, P1_G * bright, P1_B * bright);
|
glUniform3f(locCol, P1_R * baseBright, P1_G * baseBright, P1_B * baseBright);
|
||||||
glDrawArrays(GL_TRIANGLES, 0, 3);
|
glDrawArrays(GL_TRIANGLES, 0, 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1025,6 +1043,180 @@ static void renderCursor(CursorLayer& cl, const Layout& L,
|
|||||||
glBindVertexArray(0);
|
glBindVertexArray(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── A scope bearing display window ──────────────────────────────────────────
|
||||||
|
//
|
||||||
|
// Small box below the A scope showing the current A scope bearing setting
|
||||||
|
// as "BRG XXX" in incandescent colour.
|
||||||
|
// Reuses scope_bounds shader (uColor, no offset) for the box outline
|
||||||
|
// and text/text shaders for the label.
|
||||||
|
|
||||||
|
struct BearingDisplay {
|
||||||
|
GLuint boxVAO = 0, boxVBO = 0;
|
||||||
|
int boxCount = 0;
|
||||||
|
GLuint textVAO = 0, textVBO = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
static BearingDisplay buildBearingDisplay(const Layout& L, float W, float H)
|
||||||
|
{
|
||||||
|
BearingDisplay bd{};
|
||||||
|
|
||||||
|
// Static box outline (4 line segments)
|
||||||
|
std::vector<float> v;
|
||||||
|
auto ln = [&](float x1, float y1, float x2, float y2) {
|
||||||
|
v.push_back(ndcX(x1,W)); v.push_back(ndcY(y1,H));
|
||||||
|
v.push_back(ndcX(x2,W)); v.push_back(ndcY(y2,H));
|
||||||
|
};
|
||||||
|
ln(L.brgLeft, L.brgTop, L.brgRight, L.brgTop);
|
||||||
|
ln(L.brgRight, L.brgTop, L.brgRight, L.brgBot);
|
||||||
|
ln(L.brgRight, L.brgBot, L.brgLeft, L.brgBot);
|
||||||
|
ln(L.brgLeft, L.brgBot, L.brgLeft, L.brgTop);
|
||||||
|
bd.boxCount = (int)v.size() / 2;
|
||||||
|
makeLineVAO(bd.boxVAO, bd.boxVBO, v);
|
||||||
|
|
||||||
|
// Dynamic text VAO (rebuilt each frame with current bearing value)
|
||||||
|
glGenVertexArrays(1, &bd.textVAO); glGenBuffers(1, &bd.textVBO);
|
||||||
|
glBindVertexArray(bd.textVAO);
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, bd.textVBO);
|
||||||
|
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4*sizeof(float), nullptr);
|
||||||
|
glEnableVertexAttribArray(0);
|
||||||
|
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4*sizeof(float),
|
||||||
|
reinterpret_cast<void*>(2*sizeof(float)));
|
||||||
|
glEnableVertexAttribArray(1);
|
||||||
|
glBindVertexArray(0);
|
||||||
|
|
||||||
|
return bd;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void renderBearingDisplay(BearingDisplay& bd, const Layout& L,
|
||||||
|
GLuint boxProg, GLuint textProg,
|
||||||
|
const FontAtlas& fa, float bearingDeg,
|
||||||
|
float maxRangeMi, float W, float H)
|
||||||
|
{
|
||||||
|
// Box outline
|
||||||
|
glUseProgram(boxProg);
|
||||||
|
glUniform3f(glGetUniformLocation(boxProg, "uColor"),
|
||||||
|
INCAN_R, INCAN_G, INCAN_B);
|
||||||
|
glBindVertexArray(bd.boxVAO);
|
||||||
|
glDrawArrays(GL_LINES, 0, bd.boxCount);
|
||||||
|
glBindVertexArray(0);
|
||||||
|
|
||||||
|
// Two lines: "BRG 000" and "RNG 15"
|
||||||
|
int iBear = static_cast<int>(bearingDeg) % 360;
|
||||||
|
if (iBear < 0) iBear += 360;
|
||||||
|
int iRng = static_cast<int>(maxRangeMi);
|
||||||
|
char brgBuf[12], rngBuf[12];
|
||||||
|
std::snprintf(brgBuf, sizeof(brgBuf), "BRG %03d", iBear);
|
||||||
|
std::snprintf(rngBuf, sizeof(rngBuf), "RNG %2d", iRng);
|
||||||
|
|
||||||
|
const float cx = (L.brgLeft + L.brgRight) * 0.5f;
|
||||||
|
const float brgH = L.brgBot - L.brgTop;
|
||||||
|
const float cy1 = L.brgTop + brgH * 0.30f; // upper line
|
||||||
|
const float cy2 = L.brgTop + brgH * 0.70f; // lower line
|
||||||
|
|
||||||
|
std::vector<float> tv;
|
||||||
|
appendTextQuads(tv, fa, std::string(brgBuf), cx, cy1, W, H);
|
||||||
|
appendTextQuads(tv, fa, std::string(rngBuf), cx, cy2, W, H);
|
||||||
|
if (tv.empty()) return;
|
||||||
|
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, bd.textVBO);
|
||||||
|
glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(tv.size()*sizeof(float)),
|
||||||
|
tv.data(), GL_DYNAMIC_DRAW);
|
||||||
|
glUseProgram(textProg);
|
||||||
|
glUniform3f(glGetUniformLocation(textProg, "uColor"),
|
||||||
|
INCAN_R, INCAN_G, INCAN_B);
|
||||||
|
glUniform1i(glGetUniformLocation(textProg, "uTexture"), 0);
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, fa.texture);
|
||||||
|
glBindVertexArray(bd.textVAO);
|
||||||
|
glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(tv.size() / 4));
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
glBindVertexArray(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── New Update 2: "Operator Manually Changing Range Graticule" window ────────
|
||||||
|
//
|
||||||
|
// Appears above the A scope only while the graticule is animating (any phase
|
||||||
|
// other than HOLD).
|
||||||
|
|
||||||
|
struct GratNotifyWindow {
|
||||||
|
GLuint boxVAO = 0, boxVBO = 0;
|
||||||
|
int boxCount = 0;
|
||||||
|
GLuint textVAO = 0, textVBO = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
static GratNotifyWindow buildGratNotifyWindow(const Layout& L, float W, float H)
|
||||||
|
{
|
||||||
|
GratNotifyWindow gn{};
|
||||||
|
|
||||||
|
std::vector<float> v;
|
||||||
|
auto ln = [&](float x1, float y1, float x2, float y2) {
|
||||||
|
v.push_back(ndcX(x1,W)); v.push_back(ndcY(y1,H));
|
||||||
|
v.push_back(ndcX(x2,W)); v.push_back(ndcY(y2,H));
|
||||||
|
};
|
||||||
|
ln(L.notifyLeft, L.notifyTop, L.notifyRight, L.notifyTop);
|
||||||
|
ln(L.notifyRight, L.notifyTop, L.notifyRight, L.notifyBot);
|
||||||
|
ln(L.notifyRight, L.notifyBot, L.notifyLeft, L.notifyBot);
|
||||||
|
ln(L.notifyLeft, L.notifyBot, L.notifyLeft, L.notifyTop);
|
||||||
|
gn.boxCount = (int)v.size() / 2;
|
||||||
|
makeLineVAO(gn.boxVAO, gn.boxVBO, v);
|
||||||
|
|
||||||
|
glGenVertexArrays(1, &gn.textVAO); glGenBuffers(1, &gn.textVBO);
|
||||||
|
glBindVertexArray(gn.textVAO);
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, gn.textVBO);
|
||||||
|
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4*sizeof(float), nullptr);
|
||||||
|
glEnableVertexAttribArray(0);
|
||||||
|
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4*sizeof(float),
|
||||||
|
reinterpret_cast<void*>(2*sizeof(float)));
|
||||||
|
glEnableVertexAttribArray(1);
|
||||||
|
glBindVertexArray(0);
|
||||||
|
|
||||||
|
return gn;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void renderGratNotifyWindow(GratNotifyWindow& gn, const Layout& L,
|
||||||
|
GLuint boxProg, GLuint textProg,
|
||||||
|
const FontAtlas& fa, float W, float H)
|
||||||
|
{
|
||||||
|
// Box outline
|
||||||
|
glUseProgram(boxProg);
|
||||||
|
glUniform3f(glGetUniformLocation(boxProg, "uColor"),
|
||||||
|
INCAN_R, INCAN_G, INCAN_B);
|
||||||
|
glBindVertexArray(gn.boxVAO);
|
||||||
|
glDrawArrays(GL_LINES, 0, gn.boxCount);
|
||||||
|
glBindVertexArray(0);
|
||||||
|
|
||||||
|
// Text centred in box, clipped to box bounds
|
||||||
|
const float cx = (L.notifyLeft + L.notifyRight) * 0.5f;
|
||||||
|
const float cy = (L.notifyTop + L.notifyBot) * 0.5f;
|
||||||
|
|
||||||
|
std::vector<float> tv;
|
||||||
|
appendTextQuads(tv, fa, "Operator Manually Changing Range Graticule",
|
||||||
|
cx, cy, W, H);
|
||||||
|
if (tv.empty()) return;
|
||||||
|
|
||||||
|
glEnable(GL_SCISSOR_TEST);
|
||||||
|
glScissor((GLint) L.notifyLeft,
|
||||||
|
(GLint)(H - L.notifyBot),
|
||||||
|
(GLint)(L.notifyRight - L.notifyLeft),
|
||||||
|
(GLint)(L.notifyBot - L.notifyTop));
|
||||||
|
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, gn.textVBO);
|
||||||
|
glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(tv.size()*sizeof(float)),
|
||||||
|
tv.data(), GL_DYNAMIC_DRAW);
|
||||||
|
glUseProgram(textProg);
|
||||||
|
glUniform3f(glGetUniformLocation(textProg, "uColor"),
|
||||||
|
INCAN_R, INCAN_G, INCAN_B);
|
||||||
|
glUniform1i(glGetUniformLocation(textProg, "uTexture"), 0);
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, fa.texture);
|
||||||
|
glBindVertexArray(gn.textVAO);
|
||||||
|
glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(tv.size() / 4));
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
glBindVertexArray(0);
|
||||||
|
|
||||||
|
glDisable(GL_SCISSOR_TEST);
|
||||||
|
}
|
||||||
|
|
||||||
// ─── Key callback ─────────────────────────────────────────────────────────────
|
// ─── Key callback ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
static void onKey(GLFWwindow* win, int key, int /*scan*/, int action, int /*mods*/)
|
static void onKey(GLFWwindow* win, int key, int /*scan*/, int action, int /*mods*/)
|
||||||
@@ -1097,15 +1289,15 @@ int main()
|
|||||||
RingLayer rl = buildRingLayer(fa);
|
RingLayer rl = buildRingLayer(fa);
|
||||||
|
|
||||||
// Feature 5 — Four fake targets per CLAUDE.md spec:
|
// Feature 5 — Four fake targets per CLAUDE.md spec:
|
||||||
// 1. 10 mi north, 100 ft long / 20 ft beam, heading south (head-on) → apparent ~20 ft
|
// 1. 5 mi N, 100 ft / 20 ft, heading S at 1 kt (head-on) → apparent ~20 ft
|
||||||
// 2. 5 mi south, 20 ft long / 5 ft beam, heading south (stern-on) → apparent ~5 ft
|
// 2. 5 mi S, 20 ft / 5 ft, heading S at 20 kt (stern-on) → apparent ~5 ft
|
||||||
// 3. 4 mi east, 30 ft long / 10 ft beam, heading north (full side) → apparent ~30 ft
|
// 3. 6 mi E, 30 ft / 10 ft, heading N at 30 kt (full side) → apparent ~30 ft
|
||||||
// 4. 1 mi west, 100 ft long / 25 ft beam, heading south (full side) → apparent ~100 ft
|
// 4. 6 mi W, 100 ft / 25 ft, heading S at 5 kt (full side) → apparent ~100 ft
|
||||||
FakeTarget targets[4] = {
|
FakeTarget targets[4] = {
|
||||||
{ 0.0f, 10.0f, 100.0f, 20.0f, 180.0f, -999.0f }, // N – head-on, ~20 ft apparent
|
{ 0.0f, 5.0f, 100.0f, 20.0f, 180.0f, -999.0f }, // N – head-on, ~20 ft apparent
|
||||||
{ 180.0f, 5.0f, 20.0f, 5.0f, 180.0f, -999.0f }, // S – stern-on, ~5 ft apparent
|
{ 180.0f, 5.0f, 20.0f, 5.0f, 180.0f, -999.0f }, // S – stern-on, ~5 ft apparent
|
||||||
{ 90.0f, 4.0f, 30.0f, 10.0f, 0.0f, -999.0f }, // E – full side, ~30 ft apparent
|
{ 90.0f, 6.0f, 30.0f, 10.0f, 0.0f, -999.0f }, // E – full side, ~30 ft apparent
|
||||||
{ 270.0f, 1.0f, 100.0f, 25.0f, 180.0f, -999.0f }, // W – full side, ~100 ft apparent
|
{ 270.0f, 6.0f, 100.0f, 25.0f, 180.0f, -999.0f }, // W – full side, ~100 ft apparent
|
||||||
};
|
};
|
||||||
TargetLayer tl = buildTargetLayer();
|
TargetLayer tl = buildTargetLayer();
|
||||||
|
|
||||||
@@ -1115,6 +1307,13 @@ int main()
|
|||||||
// Feature 6 — PPI cursor
|
// Feature 6 — PPI cursor
|
||||||
CursorLayer cl = buildCursorLayer();
|
CursorLayer cl = buildCursorLayer();
|
||||||
|
|
||||||
|
// Bearing display window (new suggestion #3)
|
||||||
|
// Reuse scope_bounds program for the box, text program for label
|
||||||
|
BearingDisplay bd = buildBearingDisplay(layout, W, H);
|
||||||
|
|
||||||
|
// New Update 2 — graticule change notification window
|
||||||
|
GratNotifyWindow gn = buildGratNotifyWindow(layout, W, H);
|
||||||
|
|
||||||
const float ppiR = layout.ppiR;
|
const float ppiR = layout.ppiR;
|
||||||
|
|
||||||
// NDC height of A scope box — used for slide animation
|
// NDC height of A scope box — used for slide animation
|
||||||
@@ -1127,8 +1326,10 @@ int main()
|
|||||||
GratPhase gratPhase = GratPhase::HOLD;
|
GratPhase gratPhase = GratPhase::HOLD;
|
||||||
float phaseTimer = 0.0f;
|
float phaseTimer = 0.0f;
|
||||||
|
|
||||||
// A scope bearing (° true, CW from N)
|
// A scope bearing (° true, CW from N).
|
||||||
const float ascopeBearingDeg = 90.0f;
|
// New suggestion #4: temporarily locked to targets[0] bearing (north, 0°).
|
||||||
|
// Will be driven by Control 7 once hardware is available.
|
||||||
|
float ascopeBearingDeg = targets[0].bearingDeg; // 0.0° — north
|
||||||
|
|
||||||
float sweepAngle = 0.0f;
|
float sweepAngle = 0.0f;
|
||||||
float prevTime = static_cast<float>(glfwGetTime());
|
float prevTime = static_cast<float>(glfwGetTime());
|
||||||
@@ -1236,10 +1437,14 @@ int main()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── New Update 2: notification window above A scope ───────────────────
|
||||||
|
if (gratPhase != GratPhase::HOLD)
|
||||||
|
renderGratNotifyWindow(gn, layout, sb.prog, bg.textProg, fa, W, H);
|
||||||
|
|
||||||
// ── A scope signal trace ──────────────────────────────────────────────
|
// ── A scope signal trace ──────────────────────────────────────────────
|
||||||
renderAScopeTrace(at, layout, targets, 4,
|
renderAScopeTrace(at, layout, targets, 4,
|
||||||
RANGE_CONFIGS[curRange].maxMiles, ascopeBearingDeg,
|
RANGE_CONFIGS[curRange].maxMiles, ascopeBearingDeg,
|
||||||
sweepAngle, now, W, H);
|
W, H);
|
||||||
|
|
||||||
// ── Feature 4: PPI range rings ────────────────────────────────────────
|
// ── Feature 4: PPI range rings ────────────────────────────────────────
|
||||||
renderRingLayer(rl, layout, curRange, sweepAngle, now, W, H, fa);
|
renderRingLayer(rl, layout, curRange, sweepAngle, now, W, H, fa);
|
||||||
@@ -1249,12 +1454,22 @@ int main()
|
|||||||
RANGE_CONFIGS[curRange].maxMiles, ppiR,
|
RANGE_CONFIGS[curRange].maxMiles, ppiR,
|
||||||
sweepAngle, now, W, H);
|
sweepAngle, now, W, H);
|
||||||
|
|
||||||
// ── Feature 6: PPI cursor (slow auto-animation for test) ──────────────
|
// ── Feature 6: PPI cursor locked to targets[2] (east, 6 mi) ──────────
|
||||||
// Range oscillates between ~15 % and ~85 % over 12 s period.
|
// New suggestion #5: cursor follows one target; not free-roaming.
|
||||||
// Bearing rotates slowly at 18 °/s (one full revolution per 20 s).
|
// Only draw when the target is within the current range setting.
|
||||||
float cursorRangeFrac = 0.15f + 0.70f * (std::sin(now * (2.0f*PI/12.0f)) * 0.5f + 0.5f);
|
{
|
||||||
float cursorBearingDeg = std::fmod(now * 18.0f, 360.0f);
|
const float cursorRangeFrac = targets[2].rangeMiles
|
||||||
|
/ RANGE_CONFIGS[curRange].maxMiles;
|
||||||
|
const float cursorBearingDeg = targets[2].bearingDeg;
|
||||||
|
if (cursorRangeFrac <= 1.0f)
|
||||||
renderCursor(cl, layout, cursorRangeFrac, cursorBearingDeg, W, H);
|
renderCursor(cl, layout, cursorRangeFrac, cursorBearingDeg, W, H);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Bearing display window (new suggestion #3) ────────────────────────
|
||||||
|
renderBearingDisplay(bd, layout,
|
||||||
|
sb.prog, bg.textProg,
|
||||||
|
fa, ascopeBearingDeg,
|
||||||
|
RANGE_CONFIGS[curRange].maxMiles, W, H);
|
||||||
|
|
||||||
glfwSwapBuffers(win);
|
glfwSwapBuffers(win);
|
||||||
glfwPollEvents();
|
glfwPollEvents();
|
||||||
@@ -1287,6 +1502,10 @@ int main()
|
|||||||
glDeleteProgram(at.prog);
|
glDeleteProgram(at.prog);
|
||||||
glDeleteVertexArrays(1, &cl.vao); glDeleteBuffers(1, &cl.vbo);
|
glDeleteVertexArrays(1, &cl.vao); glDeleteBuffers(1, &cl.vbo);
|
||||||
glDeleteProgram(cl.prog);
|
glDeleteProgram(cl.prog);
|
||||||
|
glDeleteVertexArrays(1, &bd.boxVAO); glDeleteBuffers(1, &bd.boxVBO);
|
||||||
|
glDeleteVertexArrays(1, &bd.textVAO); glDeleteBuffers(1, &bd.textVBO);
|
||||||
|
glDeleteVertexArrays(1, &gn.boxVAO); glDeleteBuffers(1, &gn.boxVBO);
|
||||||
|
glDeleteVertexArrays(1, &gn.textVAO); glDeleteBuffers(1, &gn.textVBO);
|
||||||
|
|
||||||
glfwDestroyWindow(win);
|
glfwDestroyWindow(win);
|
||||||
glfwTerminate();
|
glfwTerminate();
|
||||||
|
|||||||
Reference in New Issue
Block a user