From 01bb187aba7e2bae45b86b305594fdbaa0cd6dfe Mon Sep 17 00:00:00 2001 From: Miloslav Ciz Date: Sun, 29 Jun 2025 23:48:01 +0200 Subject: [PATCH] Add ribbon mod --- mods/ribbon.diff | 176 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 mods/ribbon.diff diff --git a/mods/ribbon.diff b/mods/ribbon.diff new file mode 100644 index 0000000..06a3a4e --- /dev/null +++ b/mods/ribbon.diff @@ -0,0 +1,176 @@ +Licar mod that adds a simple visual effect, a kind of ribbon light trail. + +diff --git a/game.h b/game.h +index b1db32c..e9db9be 100644 +--- a/game.h ++++ b/game.h +@@ -335,6 +335,7 @@ struct + uint32_t frame; ///< Current frame number. + uint32_t nextRenderFrameTime; ///< At which frame to render next frame. + uint32_t nextRacingTickTime; ///< When to simulate next physics tick. ++ uint32_t nextRibbonTime; + uint8_t cameraMode; + uint8_t musicOn; + uint8_t keyStates[LCR_KEYS_TOTAL]; /**< Assures unchanging key states +@@ -1102,6 +1103,7 @@ void LCR_gameInit(int argc, const char **argv) + LCR_game.musicOn = LCR_SETTING_MUSIC; + LCR_game.nextRenderFrameTime = 0; + LCR_game.nextRacingTickTime = 0; ++ LCR_game.nextRibbonTime = 0; + LCR_game.cameraMode = LCR_CAMERA_MODE_DRIVE; + LCR_currentMap.blockCount = 0; // means no map loaded + +@@ -1771,6 +1773,9 @@ uint8_t LCR_gameStep(uint32_t time) + + uint32_t events = paused ? 0 : LCR_racingStep(input); + ++ if (LCR_racing.tick % LCR_SETTING_RIBBON_INTERVAL == 0) ++ LCR_rendererRibbonUpdate(); ++ + #if LCR_SETTING_PARTICLES + LCR_rendererSetParticles(0); + +diff --git a/renderer.h b/renderer.h +index 735644e..6941398 100644 +--- a/renderer.h ++++ b/renderer.h +@@ -153,6 +153,9 @@ struct + #if LCR_SETTING_PARTICLES + uint_fast16_t particleColor; ///< 0x0000 means no particles active. + #endif ++ ++ int16_t ribbonPoints[LCR_SETTING_RIBBON_SEGMENTS * 3]; ++ uint8_t ribbonLastSegment; + } LCR_renderer; + + /** +@@ -200,6 +203,23 @@ void LCR_rendererSetGhostTransform(LCR_GameUnit position[3], + _LCR_rendererSetModelTransform(LCR_renderer.ghostModel,position,rotation); + } + ++void LCR_rendererRibbonUpdate(void) ++{ ++ LCR_renderer.ribbonLastSegment = ++ (LCR_renderer.ribbonLastSegment + 1) % LCR_SETTING_RIBBON_SEGMENTS; ++ ++ unsigned int n = 3 * LCR_renderer.ribbonLastSegment; ++ ++ LCR_renderer.ribbonPoints[n] = ++ LCR_renderer.carModel->transform.translation.x / 2; ++ n++; ++ LCR_renderer.ribbonPoints[n] = ++ LCR_renderer.carModel->transform.translation.y / 2; ++ n++; ++ LCR_renderer.ribbonPoints[n] = ++ LCR_renderer.carModel->transform.translation.z / 2; ++} ++ + void LCR_rendererSetCarVisibility(uint8_t visible) + { + LCR_renderer.carModel->config.visible = visible; +@@ -2154,6 +2174,59 @@ void LCR_rendererSetWheelState(LCR_GameUnit rotation, LCR_GameUnit steer) + #endif + } + ++void LCR_rendererDrawRibbon(void) ++{ ++ S3L_Vec4 p, r; ++ int16_t *s = // this will go backwards ++ LCR_renderer.ribbonPoints + 3 * LCR_renderer.ribbonLastSegment + 2; ++ int16_t prev[3]; // previous values for interpolation: screen x, y and size ++ ++ p.x = LCR_renderer.carModel->transform.translation.x; ++ p.y = LCR_renderer.carModel->transform.translation.y; ++ p.z = LCR_renderer.carModel->transform.translation.z; ++ p.w = (LCR_SETTING_RIBBON_SIZE * LCR_SETTING_RESOLUTION_X) / 100; ++ ++ S3L_project3DPointToScreen(p,LCR_renderer.scene.camera,&r); ++ ++ prev[0] = r.x; ++ prev[1] = r.y; ++ prev[2] = r.w; ++ ++ for (int i = 0; i < LCR_SETTING_RIBBON_SEGMENTS; ++i) ++ { ++ p.z = *s * 2; s--; ++ p.y = *s * 2; s--; ++ p.x = *s * 2; s--; ++ p.w = (LCR_SETTING_RIBBON_SIZE * LCR_SETTING_RESOLUTION_X) / 100; ++ ++ // reducing size towards the tail: ++ p.w = (p.w * (LCR_SETTING_RIBBON_SEGMENTS - 1 - i)) ++ / LCR_SETTING_RIBBON_SEGMENTS; ++ ++ if (s <= LCR_renderer.ribbonPoints) // wrap to the end ++ s = LCR_renderer.ribbonPoints + (LCR_SETTING_RIBBON_SEGMENTS - 1) * 3 + 2; ++ ++ S3L_project3DPointToScreen(p,LCR_renderer.scene.camera,&r); ++ ++ for (int j = 0; j < LCR_SETTING_RIBBON_SUBDIV; ++j) ++ { ++ int w = prev[2] + (j * (r.w - prev[2])) / LCR_SETTING_RIBBON_SUBDIV; ++ int x = prev[0] + (j * (r.x - prev[0])) / LCR_SETTING_RIBBON_SUBDIV ++ - w / 2; ++ int y = prev[1] + (j * (r.y - prev[1])) / LCR_SETTING_RIBBON_SUBDIV ++ - w / 2; ++ ++ if (w > 0 && x > 0 && y > 0 && ++ x < LCR_EFFECTIVE_RESOLUTION_X && y < LCR_EFFECTIVE_RESOLUTION_Y) ++ LCR_rendererDrawRect(x,y,w,w,LCR_SETTING_RIBBON_COLOR,1); ++ } ++ ++ prev[0] = r.x; ++ prev[1] = r.y; ++ prev[2] = r.w; ++ } ++} ++ + void LCR_rendererDraw3D(void) + { + LCR_LOG2("rendering frame (start)"); +@@ -2304,6 +2377,8 @@ void LCR_rendererDraw3D(void) + + #endif + ++ LCR_rendererDrawRibbon(); ++ + LCR_LOG2("3D rendering (end)"); + LCR_LOG2("rendering frame (end)"); + } +diff --git a/settings.h b/settings.h +index 332df45..47e27f8 100644 +--- a/settings.h ++++ b/settings.h +@@ -200,6 +200,32 @@ + #define LCR_SETTING_COUNTDOWN_MS 2200 + #endif + ++#ifndef LCR_SETTING_RIBBON_SEGMENTS ++ /** Ribbon mod: how many points to spawn. */ ++ #define LCR_SETTING_RIBBON_SEGMENTS 10 ++#endif ++ ++#ifndef LCR_SETTING_RIBBON_SUBDIV ++ /** Ribbon mod: number of splats to draw per segment, it's good to keep this a ++ power of 2. */ ++ #define LCR_SETTING_RIBBON_SUBDIV 16 ++#endif ++ ++#ifndef LCR_SETTING_RIBBON_INTERVAL ++ /** Ribbon mod: how often to spawn a new ribbon point (in physics ticks). */ ++ #define LCR_SETTING_RIBBON_INTERVAL 4 ++#endif ++ ++#ifndef LCR_SETTING_RIBBON_SIZE ++ /** Ribbon mod: ribbon splat size (in percents of screen width). */ ++ #define LCR_SETTING_RIBBON_SIZE 6 ++#endif ++ ++#ifndef LCR_SETTING_RIBBON_COLOR ++ /** Ribbon mod: ribbon color. */ ++ #define LCR_SETTING_RIBBON_COLOR 0xffe6 ++#endif ++ + #ifndef LCR_SETTING_MAP_CHUNK_RELOAD_INTERVAL + /** Interval in rendering frames of reloading map chunks, should ideally be + kept a power of two, can't be 0. */