|
| 1 | +' ============================================================ |
| 2 | +' trig_lut.rbas — portable trigonometry via lookup tables |
| 3 | +' |
| 4 | +' Drop-in replacement for SIN/COS in code that targets the |
| 5 | +' rgc-basic-portable subset (ugBASIC retro targets + native |
| 6 | +' rgc-basic). Pure-integer math via 360-entry sine LUT. |
| 7 | +' Identical results across every target. |
| 8 | +' |
| 9 | +' Calling convention: GOSUB-based to stay portable. ugBASIC |
| 10 | +' prefers PROCEDURE / rgc-basic uses FUNCTION; GOSUB works |
| 11 | +' everywhere with no translation needed. |
| 12 | +' |
| 13 | +' Usage: |
| 14 | +' #INCLUDE "trig_lut.rbas" |
| 15 | +' GOSUB init_trig_lut |
| 16 | +' |
| 17 | +' ' integer sine — set ISIN_DEG, GOSUB, read ISIN_RESULT |
| 18 | +' ISIN_DEG = 45 |
| 19 | +' GOSUB isin |
| 20 | +' PRINT ISIN_RESULT ' = 707 (i.e. 0.707) |
| 21 | +' |
| 22 | +' ' integer cosine — set ICOS_DEG, GOSUB, read ICOS_RESULT |
| 23 | +' ICOS_DEG = 90 |
| 24 | +' GOSUB icos |
| 25 | +' PRINT ICOS_RESULT ' = 0 |
| 26 | +' |
| 27 | +' Sprite motion idiom — smooth weaving at frame rate: |
| 28 | +' ISIN_DEG = FRAME_COUNTER * 6 ' 6° per frame, 60° per sec @ 60 fps |
| 29 | +' GOSUB isin |
| 30 | +' SPR_Y = BASE_Y + (AMP * ISIN_RESULT) / 1000 |
| 31 | +' SPRITE my_sprite AT SPR_X, SPR_Y |
| 32 | +' |
| 33 | +' Both routines return value SCALED * 1000 — so a result of 870 |
| 34 | +' means 0.870 in real terms. The ×1000 scale gives 10× the |
| 35 | +' precision of a ×100 LUT, dramatically reducing visible |
| 36 | +' clustering at peaks/troughs when scaling to small amplitudes. |
| 37 | +' Multiply your amplitude in *whole units* and divide by 1000 |
| 38 | +' to scale back: |
| 39 | +' AMP = 9 |
| 40 | +' OFFSET = 10 |
| 41 | +' ISIN_DEG = X * 18 ' 18 deg/col → 2 cycles over 40 cols |
| 42 | +' GOSUB isin |
| 43 | +' Y = OFFSET + (AMP * ISIN_RESULT) / 1000 |
| 44 | +' |
| 45 | +' Range: |
| 46 | +' ISIN_RESULT in -1000..1000 |
| 47 | +' ICOS_RESULT in -1000..1000 |
| 48 | +' ISIN_DEG / ICOS_DEG accept any signed int (wraps mod 360) |
| 49 | +' |
| 50 | +' Why a LUT instead of SIN()? |
| 51 | +' rgc-basic native uses IEEE float SIN; ugBASIC uses per- |
| 52 | +' target fixed-point. Same source code produces different |
| 53 | +' curves. Authoring the LUT explicitly gives identical |
| 54 | +' output everywhere. |
| 55 | +' |
| 56 | +' Why GOSUB instead of FUNCTION? |
| 57 | +' ugBASIC has PROCEDURE not FUNCTION; rgc-basic has |
| 58 | +' FUNCTION. GOSUB + global I/O variables is the lowest- |
| 59 | +' common-denominator pattern that works in both without a |
| 60 | +' transpiler rewrite. |
| 61 | +' ============================================================ |
| 62 | + |
| 63 | +' Storage: 360 entries, each in -100..100 → SIGNED WORD (16-bit |
| 64 | +' signed) is the safe minimum on ugBASIC. SIGNED BYTE (-128..127) |
| 65 | +' would technically fit but mixed-type arithmetic later wants WORD. |
| 66 | +DIM SIN_LUT(359) AS SIGNED WORD |
| 67 | + |
| 68 | +' I/O variables must be SIGNED WORD too — degrees can exceed 255 |
| 69 | +' (BYTE max) once you start scaling X * deg-per-col, and the result |
| 70 | +' is ±100 which fits BYTE but the multiplications don't. |
| 71 | +DIM ISIN_DEG AS SIGNED WORD |
| 72 | +DIM ISIN_D AS SIGNED WORD |
| 73 | +DIM ISIN_RESULT AS SIGNED WORD |
| 74 | +DIM ICOS_DEG AS SIGNED WORD |
| 75 | +DIM ICOS_D AS SIGNED WORD |
| 76 | +DIM ICOS_RESULT AS SIGNED WORD |
| 77 | +DIM LUT_I AS SIGNED WORD |
| 78 | + |
| 79 | +' Skip past the subroutine bodies + DATA so they don't execute |
| 80 | +' inline when the file is #INCLUDE'd at the top of a program. |
| 81 | +' Subroutines are reached only via GOSUB; falling through into them |
| 82 | +' would hit RETURN with no call stack and halt the program. |
| 83 | +GOTO trig_lut_skip |
| 84 | + |
| 85 | +' ---------- init_trig_lut ---------- |
| 86 | +' Read 360 sine values * 100 from DATA below into SIN_LUT. |
| 87 | +' Call once at startup before any ISIN / ICOS use. |
| 88 | +init_trig_lut: |
| 89 | + FOR LUT_I = 0 TO 359 |
| 90 | + READ SIN_LUT(LUT_I) |
| 91 | + NEXT LUT_I |
| 92 | + RETURN |
| 93 | + |
| 94 | +' ---------- isin ---------- |
| 95 | +' Input: ISIN_DEG (signed integer degrees) |
| 96 | +' Output: ISIN_RESULT (sine * 100, range -100..100) |
| 97 | +isin: |
| 98 | + ISIN_D = ISIN_DEG MOD 360 |
| 99 | + IF ISIN_D < 0 THEN ISIN_D = ISIN_D + 360 |
| 100 | + ISIN_RESULT = SIN_LUT(ISIN_D) |
| 101 | + RETURN |
| 102 | + |
| 103 | +' ---------- icos ---------- |
| 104 | +' Input: ICOS_DEG |
| 105 | +' Output: ICOS_RESULT |
| 106 | +' cos(x) = sin(x + 90), so reuse the table with a shift. |
| 107 | +icos: |
| 108 | + ICOS_D = (ICOS_DEG + 90) MOD 360 |
| 109 | + IF ICOS_D < 0 THEN ICOS_D = ICOS_D + 360 |
| 110 | + ICOS_RESULT = SIN_LUT(ICOS_D) |
| 111 | + RETURN |
| 112 | + |
| 113 | +' --- 360-entry sine table, value * 1000, indexed by degree -------- |
| 114 | +' Generated by: python3 -c "import math; print(','.join(str(int(round(1000*math.sin(math.radians(d))))) for d in range(360)))" |
| 115 | +' ×1000 scale gives 10× resolution vs ×100, smoothing visible |
| 116 | +' clustering when scaling to small amplitudes (sprite motion math). |
| 117 | +' Range fits SIGNED WORD (±1000 << 32767). |
| 118 | + |
| 119 | +DATA 0,17,35,52,70,87,105,122,139,156,174,191,208,225,242,259,276,292,309,326,342,358,375,391,407,423,438,454,469,485 |
| 120 | +DATA 500,515,530,545,559,574,588,602,616,629,643,656,669,682,695,707,719,731,743,755,766,777,788,799,809,819,829,839,848,857 |
| 121 | +DATA 866,875,883,891,899,906,914,921,927,934,940,946,951,956,961,966,970,974,978,982,985,988,990,993,995,996,998,999,999,1000 |
| 122 | +DATA 1000,1000,999,999,998,996,995,993,990,988,985,982,978,974,970,966,961,956,951,946,940,934,927,921,914,906,899,891,883,875 |
| 123 | +DATA 866,857,848,839,829,819,809,799,788,777,766,755,743,731,719,707,695,682,669,656,643,629,616,602,588,574,559,545,530,515 |
| 124 | +DATA 500,485,469,454,438,423,407,391,375,358,342,326,309,292,276,259,242,225,208,191,174,156,139,122,105,87,70,52,35,17 |
| 125 | +DATA 0,-17,-35,-52,-70,-87,-105,-122,-139,-156,-174,-191,-208,-225,-242,-259,-276,-292,-309,-326,-342,-358,-375,-391,-407,-423,-438,-454,-469,-485 |
| 126 | +DATA -500,-515,-530,-545,-559,-574,-588,-602,-616,-629,-643,-656,-669,-682,-695,-707,-719,-731,-743,-755,-766,-777,-788,-799,-809,-819,-829,-839,-848,-857 |
| 127 | +DATA -866,-875,-883,-891,-899,-906,-914,-921,-927,-934,-940,-946,-951,-956,-961,-966,-970,-974,-978,-982,-985,-988,-990,-993,-995,-996,-998,-999,-999,-1000 |
| 128 | +DATA -1000,-1000,-999,-999,-998,-996,-995,-993,-990,-988,-985,-982,-978,-974,-970,-966,-961,-956,-951,-946,-940,-934,-927,-921,-914,-906,-899,-891,-883,-875 |
| 129 | +DATA -866,-857,-848,-839,-829,-819,-809,-799,-788,-777,-766,-755,-743,-731,-719,-707,-695,-682,-669,-656,-643,-629,-616,-602,-588,-574,-559,-545,-530,-515 |
| 130 | +DATA -500,-485,-469,-454,-438,-423,-407,-391,-375,-358,-342,-326,-309,-292,-276,-259,-242,-225,-208,-191,-174,-156,-139,-122,-105,-87,-70,-52,-35,-17 |
| 131 | + |
| 132 | +' Skip-target: GOTO at the top of this file lands here so the |
| 133 | +' main program (the file that #INCLUDE'd this one) continues |
| 134 | +' execution past the LUT internals. |
| 135 | +trig_lut_skip: |
0 commit comments