j138 draw-only: yellow triangle (enemy-heading other point, enemy, us) in the geometry overlay

This commit is contained in:
2026-09-26 15:14:48 +02:00
parent 189d990812
commit 10473fc211
2 changed files with 196 additions and 18 deletions
+68 -6
View File
@@ -12,7 +12,15 @@
## through a point that is ON the circle meets it twice — the bot itself and
## ONE OTHER point — and the OTHER point is drawn visually distinct (that is
## the one worth looking at). A tangent line's two points coincide; we draw
## what the maths gives.
## what the maths gives;
## * the TRIANGLE whose corners are the ENEMY heading line's OTHER
## intersection, the enemy, and our bot — three vertices the rest of the
## overlay already computes. Drawn YELLOW (#FFFF00), stroke width 1.5,
## with a small yellow hollow ring (r=6) on each distinct corner, so it is
## not confused with the grey circle / cyan / orange heading lines. A
## zero-length edge (a tangent enemy heading puts corner 1 on top of
## corner 2; or corner 1 on top of corner 3 when the enemy heading points
## straight at us) is SKIPPED rather than emitted as a degenerate line.
##
## Env knobs (read once at boot by ModularBot, registered in env_report.nim):
## TR_GEO_DEBUG presence/1 = on. default OFF.
@@ -98,11 +106,13 @@ proc lineCircleIntersections*(px, py, headingDeg: float,
type
GeoPrimKind* = enum
gpCircle ## the diameter circle outline
gpLineSelf ## line along OUR heading
gpLineEnemy ## line along the ENEMY heading
gpMarkSelf ## intersection that coincides with the bot (hollow ring)
gpMarkOther ## the OTHER intersection (filled, distinct)
gpCircle ## the diameter circle outline
gpLineSelf ## line along OUR heading
gpLineEnemy ## line along the ENEMY heading
gpMarkSelf ## intersection that coincides with the bot (hollow ring)
gpMarkOther ## the OTHER intersection (filled, distinct)
gpTriangleEdge ## one edge of the enemy-heading triangle
gpTriangleCorner ## a DISTINCT corner of that triangle (hollow ring)
GeoPrim* = object
kind*: GeoPrimKind
@@ -117,11 +127,17 @@ const
GeoEnemyLineColor* = "#FF8000" # orange— ENEMY heading line
GeoSelfMarkColor* = "#FFFFFF" # white — the bot itself (hollow ring)
GeoOtherMarkColor* = "#FF00FF" # magenta — the OTHER intersection (filled)
GeoTriangleColor* = "#FFFF00" # yellow — the requested triangle (edges+corner rings)
GeoCircleWidth* = 1.0
GeoLineWidth* = 1.0
GeoMarkWidth* = 1.5
GeoTriangleWidth* = 1.5
GeoSelfMarkRadius* = 4.0
GeoOtherMarkRadius* = 4.0
GeoTriangleCornerRadius* = 6.0
GeoZeroLen* = 1e-6
## Below this two triangle corners are the SAME point: the edge between
## them is degenerate (skipped) and they share a single corner ring.
GeoLineMargin* = 10.0
## Extra length (px) the heading segment extends beyond the far
## intersection; a segment of half-length 2r + margin always contains both
@@ -157,6 +173,36 @@ proc headingSegment(px, py, headingDeg, halfLen: float): tuple[x1, y1, x2, y2: f
let dy = sin(h) * halfLen
(px - dx, py - dy, px + dx, py + dy)
proc samePoint(a, b: GeomPoint): bool {.inline.} =
hypot(b.x - a.x, b.y - a.y) < GeoZeroLen
proc trianglePrims*(enemyOther, enemyPos, selfPos: GeomPoint): seq[GeoPrim] =
## DRAW-ONLY: the owner-requested triangle whose three corners are, in order,
## the ENEMY heading line's OTHER intersection with the diameter circle, the
## enemy itself, and our bot. Returns its three edges plus one hollow corner
## ring per DISTINCT corner, all in `GeoTriangleColor` (yellow, width 1.5).
##
## Degenerate handling, no new geometry is derived:
## * a zero-length edge is SKIPPED instead of drawn as a degenerate line.
## This happens when the enemy heading is TANGENT to the circle (corner 1
## coincides with corner 2) or points straight at us (corner 1 coincides
## with corner 3);
## * coincident corners share a single ring, so no zero-radius garbage.
let vs = [enemyOther, enemyPos, selfPos]
for i in 0 ..< 3:
let a = vs[i]
let b = vs[(i + 1) mod 3]
if samePoint(a, b): continue
result.add GeoPrim(kind: gpTriangleEdge, x1: a.x, y1: a.y, x2: b.x, y2: b.y,
color: GeoTriangleColor)
for i in 0 ..< 3:
var dup = false
for j in 0 ..< i:
if samePoint(vs[i], vs[j]): dup = true; break
if dup: continue
result.add GeoPrim(kind: gpTriangleCorner, cx: vs[i].x, cy: vs[i].y,
r: GeoTriangleCornerRadius, color: GeoTriangleColor)
proc collectGeoPrims*(selfX, selfY, selfHeading,
enemyX, enemyY, enemyHeading: float): seq[GeoPrim] =
## Pure: the primitives the overlay draws. Empty in the degenerate case where
@@ -194,6 +240,14 @@ proc collectGeoPrims*(selfX, selfY, selfHeading,
cy: soEnemy.other.y, r: GeoOtherMarkRadius,
color: GeoOtherMarkColor)
# The owner-requested TRIANGLE: the enemy heading's OTHER intersection,
# the enemy, and our bot. All three corners were already computed just
# above — nothing is re-derived here.
for p in trianglePrims(soEnemy.other,
GeomPoint(x: enemyX, y: enemyY),
GeomPoint(x: selfX, y: selfY)):
result.add p
# ── emit to the GUI debug-graphics buffer ────────────────────────────────────
proc emitGeoPrims*(prims: seq[GeoPrim]): int =
@@ -215,6 +269,14 @@ proc emitGeoPrims*(prims: seq[GeoPrim]): int =
of gpMarkOther:
setFillColor(p.color)
fillCircle(p.cx, p.cy, p.r)
of gpTriangleEdge:
setStrokeColor(p.color)
setStrokeWidth(GeoTriangleWidth)
drawLine(p.x1, p.y1, p.x2, p.y2)
of gpTriangleCorner:
setStrokeColor(p.color)
setStrokeWidth(GeoTriangleWidth)
drawCircle(p.cx, p.cy, p.r)
result = prims.len
proc drawGeoOverlay*(selfX, selfY, selfHeading,