Skip to content

Compiled track view - #6

Open
rremedio wants to merge 12 commits into
paulhoad:masterfrom
rremedio:ccline-integrated-visualizer
Open

Compiled track view#6
rremedio wants to merge 12 commits into
paulhoad:masterfrom
rremedio:ccline-integrated-visualizer

Conversation

@rremedio

@rremedio rremedio commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Adds a new Show Compiled Track view (View menu + toolbar "C" button) that draws GP2's exact compiled track geometry and racing line (attempting to be bit-exact), updated live as cc-line sectors are edited. No pitlane/kerbs/objects.

EDIT: I don't advise using this view until your track is a closed loop/circuit. It distorts the track geometry the same way GP2 does when the loop is not closed, making it hard for you to work with the track data.

Drawing engine

Tries the same math GP2 when compiling the track to the 3D world
New standalone C module gp2cc.c / gp2cc.h / gp2cc_tables.c — a bit-exact port of GP2's geometry compiler + cc-line (UACalcBestLine) and helpers, validated to the byte against GP2Lap dumps of the geometry:

  • geometry: heading, startAngle, road widths, side vectors
  • exact integer position warp (Bresenham gap distribution, sub_76D39)
  • cc-line bestLine / angle18

t_Sinus + t_ArithTab1 are embedded in gp2cc_tables.c, so no GP2.EXE is needed at runtime.

All the tracks I've tested so far work correctly. Example:
Original view:
image

New view:
image

@rremedio
rremedio marked this pull request as ready for review June 10, 2026 13:56
@mydeveloperday

Copy link
Copy Markdown
Collaborator

you need to rebase and add files to CMakeLists.txt

@mydeveloperday

Copy link
Copy Markdown
Collaborator

This is great, but I have a number of things...

  1. did you forget to add an icon on the toolbar,
  2. why does it need to be "C" code why can it not be changed to C++
  3. is this copied from somewhere does it need permission from someone to copy this in here?
  4. could the cos and atan tables be just computed values? when you show the compiled track you hide the track why is that?
  5. add to CMakeLists.txt

Comment thread TrackEditorScrollView.cpp Outdated
track->drawTrack(display, TRUE);
track->drawCCLines(display);
} else if (track->showComputed) {
// bit-exact compiled-track + cc-line view (replaces the normal draws)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why replace... and not just overlay

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes I think since you can show and hide the track too, overlaying makes much more sense

Comment thread GPTrack.cpp Outdated
// heading orientation. (If the shape comes out mirrored/wrong-scale, adjust
// S / the axis pairing here.)
// ---------------------------------------------------------------------------
void GPTrack::drawComputed(Display *g)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should have 2 options, draw computed track and draw computed CC line, both independent of any other drawing.. I want to understand the algorithim of the CC line, this is brute force reverse engineering of GP2.exe (I never actually did that...) as such my editor was always relatively clean of their original code. Don't get me wrong this is great.. and really solves what is a 20 year+ old problem that I was never able to solve.

I can see some substiles that caused me to be wrong.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand. And I think I jumped the gun a little bit. Since it is basically just math I just used the "ported" code. But there was a better approach and I will update it.

It was a mix of trying to make sense of data dumped from memory and then looking into RE for help. But in the end all the info ended up coming from RE anyway.

I think this not being solved before is a mix of the integer math (floating-point carries a lot of error when you compound a whole track) and the warping effect which is very hard to figure out by just looking at the data, especially if you are comparing against floating-point math.

@rremedio

rremedio commented Jun 11, 2026

Copy link
Copy Markdown
Contributor Author

This is great, but I have a number of things...

  1. did you forget to add an icon on the toolbar,
  2. why does it need to be "C" code why can it not be changed to C++
  3. is this copied from somewhere does it need permission from someone to copy this in here?
  4. could the cos and atan tables be just computed values? when you show the compiled track you hide the track why is that?
  5. add to CMakeLists.txt

Hi!

  1. I'm just lousy at graphics so I used a "C" (for "compiled") instead of an actual image. I'm gonna update it.
  2. I just worked a lot with the C code until getting it right and always using it to check the result in the editor when it finally worked I just pushed it without thinking about converting it. Converting it should be trivial and makes much more sense
  3. It is all based off asm/reverse engineering work from someone else who gave me permission to use it I have not asked for permission to use it here, specifically. I will do that. More on that on your separated comment
  4. About the tables: I need to re-check because I could never get it right until I used the exact values.
    4b. I did not think about overlaying. Replacing the track makes it impossible to work when you are creating a new track because of the geometry warping it does, so I went with the extra view
  5. Yes, no excuses here

Comment thread gp2cc.c Outdated
Comment on lines +21 to +41
return 0;
}

static inline int s16(int v) { v &= 0xFFFF; return (v & 0x8000) ? v - 0x10000 : v; }

/* GetSinusVal (0x104B9): cos, interpolated. */
int gp2cc_gv(int ax) {
ax = s16(ax);
if (ax < 0) ax = (-ax) & 0xFFFF;
int frac = ax & 7;
int widx = ((ax >> 2) & 0xFFFE) >> 1;
int base = COS[widx], nxt = COS[widx + 1];
int d = s16(nxt - base);
return s16(base + ((d * frac) >> 3));
}
/* raw (non-interpolated) cosine for the position step (InitTrackSegs 0x79A9F). */
static int scos(int a) {
a = s16(a);
if (a < 0) a = -a;
return COS[((a >> 2) & 0xFFFE) >> 1];
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe all of this can be replaced with

/* Convert to signed 16-bit: keep low 16 bits, then sign-extend into int.
 * Examples: 0x7FFF->32767, 0x8000->-32768, 0xFFFF->-1. */
static inline int s16(int v) {
    const int low16 = v & 0xFFFF;
    return (low16 >= 0x8000) ? (low16 - 0x10000) : low16;
}

/* raw (non-interpolated) cosine for the position step (InitTrackSegs 0x79A9F). */
int scos(int a) {
    /* GP2 angles use 65536 units per full turn; output is cosine scaled to 0x4000. */
    a = s16(a);
    double angle = a * (2.0 * M_PI / 65536.0);
    return (int)round(cos(angle) * 16384.0);
}

Then there is no need for the tables at all (I don't like pulling hex code from the GP2.exe binary)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm... maybe not..

@rremedio rremedio Jun 11, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll have to run it in the editor tonight to check how much drift it would create, if any. Small deviations compounded produce catastrophic results in tracks like Hockenheim. My best guess is this for scos:

/* the game samples its cosine table every 8 units with NO interpolation, so the
   * angle must be floored to the table grid via (a>>3) before the cosine — taking
   * cos() of the exact angle over-resolves and diverges on ~7/8 of angles. */
int scos(int a) {
      a = s16(a);
      if (a < 0) a = -a;                 /* cosine is even */
      const int idx = a >> 3;            /* floor to the 8-unit table grid (NOT exact angle) */
      return (int)lround(16384.0 * cos(idx * (M_PI / 4096.0)));
  }

ATAN1 can be easily replaced with round(atan(i/2048) · 65536/2π) (also checking this tonight)

rremedio added 11 commits June 15, 2026 11:29
Add a toggleable editor view (View menu + toolbar "C" button) that draws
GP2's exact compiled track geometry and cc-line (racing line), with no
pitlane/kerbs/objects. When active it recompiles from the live track on
each repaint, so it updates as cc-line sectors are edited, and mirrors the
other cc-line views: a small perpendicular tick where each cc-line sector
starts, plus a highlight on the selected sector.

Drawing goes through a new standalone C module (gp2cc.c/.h/gp2cc_tables.c):
a bit-exact port of GP2's geometry compiler + cc-line (UACalcBestLine) and
its helpers, validated to the byte against GP2Lap dumps on F1CT01/02a/03/
09/11/16:
  - geometry: heading, startAngle, road widths, side vectors
  - exact integer position warp (Bresenham gap distribution, sub_76D39)
  - cc-line bestLine / angle18

t_Sinus and t_ArithTab1 are embedded in gp2cc_tables.c so no GP2.EXE is
needed at runtime.
Make the existing "Track Section Numbers" and "Show Finish Line" toggles
work in the Show Compiled Track view:
- showFinishLine: thick line across the road at segment 0 + checkered flag.
- showTrackNumbers: a perpendicular divider across the road at each track
  section boundary + the section index drawn mid-section.

Section->segment mapping uses cumulative TrackSection::getLength() (= the
geometry-command word gp2cc consumes), so the markers land bit-exactly on
the compiled road. GPTrack.cpp only; no gp2cc change.
…ed view

Rewrites GP2's geometry compilation as the geometric process it actually is — a
fixed-point "turtle" with named steps (turn, advance one fixed chord, midpoint
half-step, ramp width, compute side vectors, close the loop) — instead of a
transcription of the disassembly. Every fixed-point op is a named helper whose
comment derives the constant (0x6488 = round(pi/2*2^14); the half-step = midpoint
integration; the warp = DDA error diffusion; the chord = a fixed 128-unit step).
Same exact integers: validated BYTE-IDENTICAL to the original gp2cc compiler on
F1CT01/02a/03/09/11/16 (every field) and the cc-line on it is bit-exact.

The cosine it uses comes from gp2cos.hpp: the table expressed as its formula
(round(16384*cos(i*pi/4096)) + the one documented COS[1992]=703 quirk), generated
at compile time and static_assert'd against GP2's extracted bytes. If a toolchain
struggles with the constexpr generation, define GP2GEOM_EMBEDDED_COS to use the
existing table (bit-identical); MSVC also gets a raised /constexpr:steps budget.

New files only — the original gp2cc.c (incl. the chaotic cc-line kernel, kept
as-is) is untouched. drawComputed now calls gp2geom::compileGeometry; the cc-line
still runs through the existing kernel.

Note: ATAN1's formula header (gp2atan.hpp) is proven but not wired in yet — it
belongs with a later cc-line-kernel cleanup. CMakeLists isn't on this branch
(predates the upstream CMake merge); when this lands on dev, add gp2cc.c,
gp2cc_tables.c and gp2geom.cpp to its SOURCES.
Ports GP2's racing-line kernel (UACalcBestLine + helpers: reproject, build_centre,
nudge, q30 trig, the fixed-iteration integer sqrts, atan2) to C++, fed by the
formula-generated tables (gp2cos.hpp cosine + gp2atan.hpp arctan). It is a faithful
transcription of the chaotic fixed-point math, not a re-derivation — validated
BIT-IDENTICAL to the original gp2cc_compute_ccline (bestLine, angle18, cmdStartSeg,
numCmds) on F1CT01/02a/03/09/11/16.

drawComputed now runs entirely on the C++/formula path: gp2geom::compileGeometry
for the geometry and gp2geom::computeCcLine for the racing line. gp2cc.c/.h/_tables.c
stay compiled (fallback, the shared structs, and gp2cc_gv which the view's drawing
still uses) until the new files are tested in the editor.

gp2atan.hpp now wired in (was sandbox-only). vcxproj builds gp2ccline.cpp
(stdcpp17, NotUsing PCH, raised /constexpr:steps). View drawing unchanged.
Scrap the standalone compiled-track view (which replaced the normal view). The
two compiled features are now independent, toggleable overlays drawn on top of
the normal editor view:
 - Compiled Track overlay (showComputed, reuses ID_VIEW_COMPILED): bit-exact
   road edges + per-section dividers, BLUE.
 - Compiled CC-Line overlay (new showComputedCCLine / ID_VIEW_COMPILED_CCLINE):
   bit-exact racing line + per-sector start ticks + selected-sector highlight,
   ORANGE / bright YELLOW.
Both compile once per repaint (buildCompiledOverlay) and share the result.

Fix the selected-sector off-by-one: CCLineSection[c] starts at cmdSeg[c+1]
(cmdSeg[0] is the seed/start command = the editor's CCLineStart header, not a
user sector), so selecting sector N now highlights N.

drawComputed() is split into buildCompiledOverlay + drawCompiledTrack +
drawCompiledCcLine; OnDraw's standalone branch is removed and the overlays are
appended to the normal-view branch. Menu: "Show Compiled Track" -> "Compiled
Track Overlay" + new "Compiled CC-Line Overlay". Toolbar: ID_VIEW_COMPILED button
re-tinted blue (track-tool icon); new ID_VIEW_COMPILED_CCLINE button (ccline-tool
icon, orange via repurposed palette idx 5). gp2cc.c untouched. View-only.
Replace the compiled-track and compiled-CC-line toolbar icons with
recolored copies of the existing Show Track / Show CC Line glyphs
(track in blue, CC line in orange) and add status-bar/tooltip strings
for both buttons.
Add gp2cc.c, gp2cc_tables.c, gp2ccline.cpp and gp2geom.cpp to the
CMake SOURCES list so the CMake build matches the .vcxproj.
An earlier edit had re-saved the file as UTF-8 and mangled the three
non-ASCII characters (the two (c) copyright signs and the e-umlaut in
'Michael') into U+FFFD. Restore the original ISO-8859-1 bytes so those
lines match upstream byte-for-byte.
The C++ modules gp2geom (geometry) and gp2ccline (cc-line) are the live
compilers now; the old C kernel was dead except for one cosine helper
(gp2cc_gv) used to draw the overlay. Move that into gp2geom::gv (the
interpolated GetSinusVal, bit-identical over all 65536 angles, verified
against the old table) and delete both C files.

gp2cc.h is trimmed to just the shared output structs (gp2cc_track /
gp2cc_ccgeo). gp2geom now always uses the build-time formula cosine table
(gp2cos.hpp); the GP2GEOM_EMBEDDED_COS fallback is dropped since its table
source is gone. Removed from the .vcxproj and CMakeLists SOURCES.
Replace the GP2 sub_/address/field references in the cc-line solver's
comments with plain descriptions of what each step does, rename the
register-named 'ebp' local to 'step', and name the 0xCBB0C constant
kInvPiQ16 (round(2^16/pi)) with a derivation. Pure comment/identifier
changes — the fixed-point math is unchanged (still bit-exact).
The cosine/atan tables are generated from their formulas at compile time;
gp2cos_reference.hpp and gp2atan_reference.hpp existed only to hold GP2's
extracted bytes for a static_assert that proved the generated tables matched.
Remove both headers and the matchesReference/atanMatchesReference guards (and
their .vcxproj entries) so no extracted GP2 data ships in the editor.

In their place, each table header now warns that the entries come from
compile-time float rounding: a different compiler/std-lib could round a
.5-boundary entry the other way, and a drifting compiled track or cc-line is
the symptom to watch for. The single load-bearing boundary (cos index 1992)
is still pinned by a one-line spot-check static_assert. Verified the generated
tables still match GP2's bytes bit-for-bit on the current toolchain.
@rremedio
rremedio force-pushed the ccline-integrated-visualizer branch from 6e7350d to b17df67 Compare June 15, 2026 21:12
@rremedio

Copy link
Copy Markdown
Contributor Author

Hey, @mydeveloperday. I've updated the PR with a lot of changes.

Both the "compiled" track and cc-line geometry now have their own overlays over the normal view (their extra view was scrapped).

The atan and scos lookup tables were replaced with formulas, but I had to add a single magic number to make the scos formula correct. That single number (the compiler rounding something like 703.504 to 704 instead of 703) reduced the accuracy of Hockenheim cc-line geometry from 100% to 29% if missing.

I tried to port the code to C++ and make it semantically make sense but I have to be honest, trying to simplify or make it accurate without all the bitwise operations led me nowhere. So let me know if the current code is acceptable. I undertand it if it is not.

@mydeveloperday mydeveloperday left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Go for it..

buildCompiledOverlay drew widthL/widthR with the perpendicular sign opposite to the normal view's getLeftSide (angle-90) / getRightSide (angle+90), so a Track Width Change Right (0xB5) command moved the on-screen LEFT edge and vice-versa. Flip the edge signs so widthL uses -pv (left) and widthR uses +pv (right), matching the normal view and the game.
@rremedio
rremedio force-pushed the ccline-integrated-visualizer branch 2 times, most recently from e66f103 to f5816f9 Compare June 29, 2026 03:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants