Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 141 additions & 0 deletions data/shaders/speed_lines.frag
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2024 SuperTuxKart-Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 3
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

// speed_lines.frag
// Creates radial speed lines effect during acceleration/boost

// Speed intensity [0.0, 1.0] - controls line density and length
uniform float speed_intensity;

// Boost intensity [0.0, 1.0] - for nitro/zipper effects (adds purple tint)
uniform float boost_intensity;

// Time for animation
uniform float time;

// The color buffer
uniform sampler2D color_buffer;

// Center point (in UV coordinates) - where the kart/driver is
uniform vec2 center;

// Inner radius where lines start to fade in
uniform float inner_radius;

out vec4 FragColor;

// Pseudo-random function
float hash(vec2 p)
{
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);
}

// Generate speed line pattern
float speedLine(vec2 uv, vec2 center_point, float angle_offset)
{
vec2 dir = uv - center_point;
float dist = length(dir);

// Calculate angle from center
float angle = atan(dir.y, dir.x);

// Number of lines around the circle
float num_lines = 60.0 + speed_intensity * 40.0;

// Create angular sections
float section = floor((angle + 3.14159) / (6.28318 / num_lines));

// Random offset for each line to make them non-uniform
float rand_offset = hash(vec2(section, floor(time * 2.0)));

// Line position within its section
float line_pos = fract((angle + 3.14159) / (6.28318 / num_lines));

// Create thin lines (using smoothstep for anti-aliasing)
float line_width = 0.08 + rand_offset * 0.04;
float line = smoothstep(0.5 - line_width, 0.5, line_pos) *
smoothstep(0.5 + line_width, 0.5, line_pos);

// Radial falloff - lines are stronger at the edges, fade near center
float radial = smoothstep(inner_radius, inner_radius + 0.2, dist);

// Lines get longer/more visible at edges of screen
float edge_intensity = smoothstep(0.3, 0.8, dist);

// Animate lines moving outward
float anim_offset = fract(time * (1.0 + speed_intensity) + rand_offset);
float anim_fade = smoothstep(0.0, 0.3, anim_offset) * smoothstep(1.0, 0.7, anim_offset);

// Random visibility per line (some lines appear, some don't)
float visibility = step(0.3 - speed_intensity * 0.25, hash(vec2(section, floor(time * 3.0 + angle_offset))));

return line * radial * edge_intensity * anim_fade * visibility;
}

void main()
{
vec2 texcoords = gl_FragCoord.xy / u_screen;

// Sample the original color
vec4 original = texture(color_buffer, texcoords);

// Early exit if no speed effect
if (speed_intensity < 0.01)
{
FragColor = original;
return;
}

// Generate multiple layers of speed lines for depth
float lines = 0.0;
lines += speedLine(texcoords, center, 0.0) * 0.6;
lines += speedLine(texcoords, center, 1.0) * 0.3;
lines += speedLine(texcoords, center, 2.0) * 0.2;

// Scale by speed intensity
lines *= speed_intensity * 0.8;

// Clamp lines intensity
lines = clamp(lines, 0.0, 0.6);

// Base line color (white)
vec3 line_color = vec3(1.0, 1.0, 1.0);

// Add purple tint for boost (nitro/zipper)
// Purple: RGB(0.7, 0.4, 1.0)
vec3 boost_color = vec3(0.75, 0.5, 1.0);
line_color = mix(line_color, boost_color, boost_intensity * 0.7);

// Slight glow effect - brighten the line area
vec3 glow = line_color * lines * 1.5;

// Blend lines with original image (additive blend)
vec3 final_color = original.rgb + glow;

// Add subtle chromatic aberration near lines for extra punch
if (boost_intensity > 0.3)
{
float aberration = lines * boost_intensity * 0.003;
vec2 dir_to_center = normalize(center - texcoords);
float r = texture(color_buffer, texcoords + dir_to_center * aberration).r;
float b = texture(color_buffer, texcoords - dir_to_center * aberration).b;
final_color.r = mix(final_color.r, r, boost_intensity * 0.3);
final_color.b = mix(final_color.b, b, boost_intensity * 0.3);
}

FragColor = vec4(final_color, original.a);
}
1 change: 1 addition & 0 deletions src/graphics/abstract_renderer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ class AbstractRenderer

virtual void resetPostProcessing() {}
virtual void giveBoost(unsigned int cam_index) {}
virtual void setSpeedIntensity(unsigned int cam_index, float speed_intensity, float boost_intensity) {}

virtual void removeSkyBox() {}

Expand Down
35 changes: 35 additions & 0 deletions src/graphics/camera/camera.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -312,3 +312,38 @@ void Camera::activate(bool alsoActivateInIrrlicht)
}
} // activate

/**
* Camera Lerp FOV Helper for boost
* use setFOV().
*/
void Camera::updateDynamicFoV(float dt, float speed_ratio, bool boost_active)
{

const float max_fov_increase = 8.0f;

const float speed_threshold = 0.7f;

const float threshold_range = 0.3f;

const float lerp_speed = 4.0f;

// Factors
float speed_factor = 0.0f;

if(speed_ratio > speed_threshold) {
speed_factor =
(speed_ratio - speed_threshold) / threshold_range;
}

float total_factor = speed_factor * (boost_active ? 1.3f : 1.0f);
total_factor = std::min(total_factor, 1.5f);

float fov_increase = total_factor * DEGREE_TO_RAD * max_fov_increase;
m_target_fov = m_fov + fov_increase;

float lerp_factor = 1.0f - expf(-lerp_speed * dt);

m_current_fov = m_current_fov + (m_target_fov - m_current_fov) * lerp_factor;

m_camera->setFOV(m_current_fov);
}
11 changes: 11 additions & 0 deletions src/graphics/camera/camera.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,13 @@ class Camera : public NoCopy
/** List of all cameras. */
static std::vector<Camera*> m_all_cameras;

// Dynamic FoV variables for speed boost effect
float m_base_fov;

float m_current_fov;

float m_target_fov;

protected:
/** The camera scene node. */
scene::ICameraSceneNode *m_camera;
Expand Down Expand Up @@ -247,6 +254,10 @@ class Camera : public NoCopy
Vec3 getXYZ() { return Vec3(m_camera->getPosition()); }
// ------------------------------------------------------------------------
void setupCamera();

void updateDynamicFoV(float dt, float speed_ratio, bool boost_active);

float getCurrentFoV() const { return m_current_fov; }
}; // class Camera

#endif
Expand Down
126 changes: 124 additions & 2 deletions src/graphics/camera/camera_normal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,20 @@
#include "graphics/camera/camera_normal.hpp"

#include "audio/sfx_manager.hpp"
#include "guiengine/engine.hpp"
#include "config/stk_config.hpp"
#include "config/user_config.hpp"
#include "graphics/irr_driver.hpp"
#include "input/device_manager.hpp"
#include "input/input_manager.hpp"
#include "input/multitouch_device.hpp"
#include "modes/soccer_world.hpp"
#include "karts/kart.hpp"
#include "karts/explosion_animation.hpp"
#include "karts/kart.hpp"
#include "karts/kart_properties.hpp"
#include "karts/max_speed.hpp"
#include "karts/skidding.hpp"
#include "modes/soccer_world.hpp"
#include "tracks/track.hpp"
#include <string>
#include <vector>
Expand Down Expand Up @@ -74,9 +77,76 @@ CameraNormal::CameraNormal(Camera::CameraType type, int camera_index,
reset();
m_camera->setNearValue(1.0f);

// Initialize speed lines effect variables
m_speed_lines_intensity = 0.0f;
m_speed_lines_boost_intensity = 0.0f;
m_speed_lines_timer = 0.0f;

// Register as boost observer
MaxSpeed::addBoostObserver(this);

restart();
} // Camera

//-----------------------------------------------------------------------------
/** Destructor - unregisters from boost observer list.
*/
CameraNormal::~CameraNormal()
{
MaxSpeed::removeBoostObserver(this);
} // ~CameraNormal

//-----------------------------------------------------------------------------
/** IBoostObserver callback - called when any kart activates a boost.
* Triggers speed lines effect for this camera's kart.
* \param kart The kart that activated the boost.
* \param category The boost category.
* \param add_speed The speed added by the boost.
* \param duration_ticks The duration of the boost in ticks.
*/
void CameraNormal::onBoostActivated(Kart* kart, unsigned int category,
float add_speed, int duration_ticks)
{
#ifndef SERVER_ONLY
// Only trigger effects for THIS camera's kart (current player)
if (kart != m_kart) return;
if (GUIEngine::isNoGraphics()) return;

// Effect duration settings
const float EFFECT_DURATION = 2.0f; // Speed lines last 2 seconds

// Determine intensity based on boost type (strongest takes priority)
float intensity = 0.0f;
switch (category)
{
case MaxSpeed::MS_INCREASE_ZIPPER:
case MaxSpeed::MS_INCREASE_GROUND_ZIPPER:
intensity = 1.0f; // Strongest (zipper)
break;
case MaxSpeed::MS_INCREASE_NITRO:
intensity = 0.8f; // Strong (nitro)
break;
case MaxSpeed::MS_INCREASE_SKIDDING:
case MaxSpeed::MS_INCREASE_RED_SKIDDING:
case MaxSpeed::MS_INCREASE_PURPLE_SKIDDING:
intensity = 0.6f; // Medium (skid bonus)
break;
default:
// Other boost types (slipstream, rubber band, etc.) don't trigger effects
return;
}

// Only trigger if intensity would be stronger than current effect
if (intensity > m_speed_lines_intensity)
{
// Trigger speed lines effect
m_speed_lines_intensity = intensity;
m_speed_lines_boost_intensity = intensity;
m_speed_lines_timer = EFFECT_DURATION;
}
#endif
} // onBoostActivated

//-----------------------------------------------------------------------------
/** Moves the camera smoothly from the current camera position (and target)
* to the new position and target.
Expand Down Expand Up @@ -363,10 +433,62 @@ void CameraNormal::update(float dt)

if (!smoothing)
{
getCameraSettings(m_last_smooth_mode, &above_kart, &cam_angle, &side_way,
getCameraSettings(m_last_smooth_mode, &above_kart, &cam_angle, &side_way,
&distance, &smoothing, &cam_roll_angle);
moveCamera(dt, false, cam_angle, distance);
}

#ifndef SERVER_ONLY
// Update speed lines and dynamic FOV effects
// NOTE: Effect TRIGGERING is handled by onBoostActivated() observer callback
// This section only handles decay of existing effects and FOV updates
if (!GUIEngine::isNoGraphics() && m_kart != NULL)
{
const KartProperties *kp = m_kart->getKartProperties();
float max_speed = kp->getEngineMaxSpeed();
float current_speed = m_kart->getSpeed();
float speed_ratio = current_speed / max_speed;

// Effect duration for decay calculation (must match onBoostActivated)
const float EFFECT_DURATION = 2.0f;

// Decay speed lines over time
if (m_speed_lines_timer > 0.0f)
{
m_speed_lines_timer -= dt;

// Smooth decay using remaining timer ratio (ease-out curve)
float decay = std::max(0.0f, m_speed_lines_timer / EFFECT_DURATION);
float display_intensity = m_speed_lines_intensity * decay;
float display_boost = m_speed_lines_boost_intensity * decay;

irr_driver->setSpeedIntensity(getIndex(), display_intensity, display_boost);
}
else
{
// Effect has ended - reset intensities
m_speed_lines_intensity = 0.0f;
m_speed_lines_boost_intensity = 0.0f;
irr_driver->setSpeedIntensity(getIndex(), 0.0f, 0.0f);
}

// Update dynamic FOV based on speed and any active boost
// Still need to poll for active boosts (not just activations) for FOV
Kart* kart = dynamic_cast<Kart*>(m_kart);
bool any_boost_active = false;
if (kart)
{
any_boost_active =
kart->getSpeedIncreaseTicksLeft(MaxSpeed::MS_INCREASE_NITRO) > 0 ||
kart->getSpeedIncreaseTicksLeft(MaxSpeed::MS_INCREASE_ZIPPER) > 0 ||
kart->getSpeedIncreaseTicksLeft(MaxSpeed::MS_INCREASE_GROUND_ZIPPER) > 0 ||
kart->getSpeedIncreaseTicksLeft(MaxSpeed::MS_INCREASE_SKIDDING) > 0 ||
kart->getSpeedIncreaseTicksLeft(MaxSpeed::MS_INCREASE_RED_SKIDDING) > 0 ||
kart->getSpeedIncreaseTicksLeft(MaxSpeed::MS_INCREASE_PURPLE_SKIDDING) > 0;
}
updateDynamicFoV(dt, speed_ratio, any_boost_active);
}
#endif
} // update

// ----------------------------------------------------------------------------
Expand Down
Loading