diff --git a/data/shaders/speed_streaks.frag b/data/shaders/speed_streaks.frag new file mode 100644 index 00000000000..9d5719a8e03 --- /dev/null +++ b/data/shaders/speed_streaks.frag @@ -0,0 +1,136 @@ +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2026 the SuperTuxKart team +// Copyright (C) 2026 sbilliet +// +// 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_streaks.frag + +uniform float u_intensity; // 0.0 to 1.0, how strong the effect is (derived from the kart's speed excess) +uniform float u_radial_scroll_offset; // Integrated uv offset: encodes speed/time/direction. +uniform float u_time; // Raw world->getTime() specifically needed for color change over time. + +uniform sampler2D noise_texture; + +out vec4 FragColor; + +const float WISP_DENSITY = 0.60; // Density <=> how many wisps should survive the density smoothstep. +const float WISP_SOFTNESS = 0.30; // Cutoff for noise range, determines how soft the wisps appear. +const float WISP_LENGTH = 0.10; // How much should we stretch the UV space radially. +const float WISP_AMOUNT = 6.0; // Amount of discrete sectors at the reference aspect ratio below. +const float WISP_REFERENCE_ASPECT = 1.77; // Aspect ratio the wisp amount is tuned for. +const float WISP_RIPPLE = 0.003; // Strength of the distortion applied to the noise_texture.y coordinates. +const float WISP_ACTIVATION = 0.05; // Threshold boost level at which the effect starts being fully visible. + +const float RADIAL_MASK_SOFTNESS = 1.0; +const float RADIAL_MASK_SIZE_MIN = 0.75; +const float RADIAL_MASK_SIZE_MAX = 0.35; + +const float COLOR_CHANGE_SPEED = 8.0; // At what speed should the color change clockwise over time. +const float COLOR_SATURATION_MIN = 0.1; +const float COLOR_SATURATION_MAX = 1.0; + +const float PI = 3.14159; + +void main() { + // -- COORDS SETUP -- // + // Normalize UVs [0, 1] + vec2 uv = gl_FragCoord.xy / u_screen; + + // Centre UVs [-0.5f, 0.5f] + uv = uv - 0.5f; + + // Scale X coords to convert rectangular screen UVs into a square 1:1 space. + float aspect_ratio = u_screen.x / u_screen.y; + uv.x *= aspect_ratio; + + // Convert Cartesian coords to Polar coords (angle, distance & unit direction) + // Calculate the angle (-PI to PI), distance around/from the center point and construct direction vector. + float polar_angle_radians = atan(uv.y, uv.x); + float polar_distance = length(uv); + vec2 polar_unit_vector = vec2(cos(polar_angle_radians), sin(polar_angle_radians)); + + // -- ASPECT RATIO CORRECTION -- // + // Do a ray (unit_vector from before) box (screen extents) intersection to find the normalized polar_distance + // to ensure UVs are mapped correctly to the whole screen. + + // Define the half-extents of the screen boundaries. + // And the normalized ray vector (i.e. guard against division by zero) + vec2 box_extents = vec2(0.5 * aspect_ratio, 0.5); + vec2 ray_direction = max(abs(polar_unit_vector), vec2(0.0001)); + + // t = distance / direction + vec2 distances = box_extents / ray_direction; + float border_distance = min(distances.x, distances.y); + + // Normalized polar coords. + float polar_distance_normalized = polar_distance / border_distance; + + // Dynamically increase sector count on wider screens to keep streak density even + float aspect_scale = sqrt(max(1.0, aspect_ratio / WISP_REFERENCE_ASPECT)); + float sectors = max(0.5 * WISP_AMOUNT, round(0.5 * WISP_AMOUNT * aspect_scale)); + float sector_index = polar_angle_radians / PI * sectors; + + // Radial coordinate (y portion of noise_texture UVs) used for scrolling the texture outwards. + float base_radial_scroll = (polar_distance_normalized * WISP_LENGTH) - u_radial_scroll_offset; + + // -- NOISE SAMPLE(s) -- // + // Wisp noise distortion, one ripple per wisp sector. + float radial_ripple = sin(2.0 * PI * sector_index) * WISP_RIPPLE; + float distorted_radial_scroll = base_radial_scroll + radial_ripple; + + // First layer of simplex noise (base noise layer) + vec2 noise_uv_primary = vec2(sector_index, distorted_radial_scroll); + float noise_sample_primary = texture(noise_texture, noise_uv_primary).r; + + // Second layer of simplex noise + // Scale and shift coords at different frequencies to create parallax between noise layers. + vec2 secondary_noise_modifiers = vec2(1.5, 1.350); + vec2 noise_uv_secondary = noise_uv_primary * secondary_noise_modifiers; + + float noise_sample_secondary = texture(noise_texture, noise_uv_secondary).r; + + // Intersection of both noise layers, final noise data. + float wisp_noise_mask = min(noise_sample_primary, noise_sample_secondary); + + // Threshold wisp mask using density parameters (e.g. discard range of noise frequencies) + wisp_noise_mask = smoothstep(WISP_DENSITY, WISP_DENSITY + WISP_SOFTNESS, wisp_noise_mask); + + // -- RADIAL MASK -- // + float radial_t = polar_distance_normalized; + float radial_mask_size = mix(RADIAL_MASK_SIZE_MIN, RADIAL_MASK_SIZE_MAX, u_intensity); + float radial_mask = smoothstep(radial_mask_size, radial_mask_size + RADIAL_MASK_SOFTNESS, radial_t); + wisp_noise_mask = wisp_noise_mask * radial_mask; + + // -- ACTIVATION FADE -- // + // Threshold the visibility of the effect at low boost levels and smoothly fade in at target edge. + float wisp_activation_fade = smoothstep(0.0, WISP_ACTIVATION, u_intensity); + wisp_noise_mask = wisp_noise_mask * wisp_activation_fade; + + //-------------// + // -- COLOR -- // + //-------------// + // Inigo Quilez cosine palette technique to generate smooth procedural gradient based on input factor. + float color_change_offset = u_time * COLOR_CHANGE_SPEED; // Rotate/Offset through color clockwise over time. + float base_hue_shift = sector_index / (2.0 * sectors) + color_change_offset; + float color_saturation = mix(COLOR_SATURATION_MIN, COLOR_SATURATION_MAX, u_intensity); + + vec3 spectral_hue = 0.5 + 0.5 * cos(6.28318 * (base_hue_shift + vec3(0.0, 0.333, 0.667))); + vec3 palette_output_color = mix(vec3(1.0), spectral_hue, color_saturation); + + // -- FINAL OUTPUT -- / + FragColor = vec4(palette_output_color * wisp_noise_mask, wisp_noise_mask); +} diff --git a/src/config/user_config.hpp b/src/config/user_config.hpp index 7cf2b9de8f6..72ef2cc9408 100644 --- a/src/config/user_config.hpp +++ b/src/config/user_config.hpp @@ -693,6 +693,9 @@ namespace UserConfigParams PARAM_PREFIX BoolUserConfigParam m_dof PARAM_DEFAULT(BoolUserConfigParam(false, "enable_dof", &m_video_group, "Enable Depth of Field")); + PARAM_PREFIX BoolUserConfigParam m_speed_streaks + PARAM_DEFAULT(BoolUserConfigParam(false, "enable_speed_streaks", + &m_video_group, "Enable Speed Streaks")); PARAM_PREFIX BoolUserConfigParam m_old_driver_popup PARAM_DEFAULT(BoolUserConfigParam(true, "old_driver_popup", &m_video_group, "Determines if popup message about too old drivers should be displayed.")); diff --git a/src/graphics/irr_driver.cpp b/src/graphics/irr_driver.cpp index 6b47a3c3b02..a0b73e53397 100644 --- a/src/graphics/irr_driver.cpp +++ b/src/graphics/irr_driver.cpp @@ -161,6 +161,7 @@ static const char* m_perf_query_phase[Q_LAST] = "Bloom", "Tonemap", "Motion Blur", + "Speed Streaks", "Lightning", "MLAA", "GUI", diff --git a/src/graphics/post_processing.cpp b/src/graphics/post_processing.cpp index dbda493d04f..eb729429554 100644 --- a/src/graphics/post_processing.cpp +++ b/src/graphics/post_processing.cpp @@ -35,8 +35,10 @@ #include "graphics/sp/sp_dynamic_draw_call.hpp" #include "graphics/sp/sp_shader.hpp" #include "graphics/sp/sp_uniform_assigner.hpp" +#include "graphics/speed_streaks.hpp" #include "io/file_manager.hpp" #include "karts/abstract_kart.hpp" +#include "karts/kart.hpp" #include "karts/kart_model.hpp" #include "modes/world.hpp" #include "physics/physics.hpp" @@ -542,6 +544,23 @@ class MotionBlurShader : public TextureShader +{ +public: + SpeedStreaksShader() + { + loadProgram(OBJECT, GL_VERTEX_SHADER, "screenquad.vert", + GL_FRAGMENT_SHADER, "speed_streaks.frag"); + assignUniforms("u_intensity", "u_radial_scroll_offset", "u_time"); + assignSamplerNames(0, "noise_texture", ST_BILINEAR_FILTERED); + } //SpeedStreaksShader + void render(GLuint noise_texture, float intensity, float radial_scroll_offset, float time) + { + setTextureUnits(noise_texture); + drawFullScreenEffect(intensity, radial_scroll_offset, time); + } // render +}; // SpeedStreaksShader + // ============================================================================ class GodFadeShader : public TextureShader { @@ -690,9 +709,24 @@ PostProcessing::PostProcessing() STKTexManager::getInstance()->addTexture(m_areamap); areamap->drop(); + // Load the simplex noise texture + const std::string noiseTexturePath = file_manager->getAsset( + FileManager::AssetType::TEXTURE, + "stk_noise_simplex.png"); + + //TODO: implement single_channel path, currently loads rgba even though noise_texture is single channel only. + m_noise_texture = GE::createTexture(noiseTexturePath); + if (!m_noise_texture || m_noise_texture->getTextureHandler() == 0) + { + Log::fatal("postprocessing", "Failed to load %s", noiseTexturePath.c_str()); + return; + } + STKTexManager::getInstance()->addTexture(m_noise_texture); + // For preloading shaders MotionBlurShader::getInstance(); LightningShader::getInstance(); + SpeedStreaksShader::getInstance(); } // PostProcessing // ---------------------------------------------------------------------------- @@ -923,6 +957,52 @@ void PostProcessing::renderMotionBlur(const FrameBuffer &in_fbo, MotionBlurShader::getInstance()->render(in_fbo, boost_time, depth_stencil_texture); } // renderMotionBlur +// ---------------------------------------------------------------------------- +void PostProcessing::renderSpeedStreaks() +{ + World* world = World::getWorld(); + Camera* cam = Camera::getActiveCamera(); + const Kart* kart = dynamic_cast(cam->getKart()); + SpeedStreaks* speed_streaks = kart ? kart->getSpeedStreaks() : nullptr; + + const Camera::CameraType cam_type = cam->getType(); + const Camera::Mode cam_mode = cam->getMode(); + + const bool is_valid_cam_mode = cam_mode == Camera::CM_NORMAL || + cam_mode == Camera::CM_REVERSE || + cam_mode == Camera::CM_LEADER_MODE || + cam_mode == Camera::CM_FALLING; + + float radial_scroll_offset = 0.0f; + float intensity = 0.0f; + + // Determine if we should display speed streaks based on camera type/mode + if (speed_streaks && + cam_type == Camera::CM_TYPE_NORMAL && + is_valid_cam_mode) + { + radial_scroll_offset = speed_streaks->getRadialScrollOffset(); + intensity = speed_streaks->getBoostIntensity(); + + // Change on screen direction of speedstreaks based on active camera mode + auto direction = SpeedStreaks::Direction::FORWARD; + if (cam_mode == Camera::CM_REVERSE || + cam_mode == Camera::CM_LEADER_MODE) + { + direction = SpeedStreaks::Direction::REVERSE; + } + speed_streaks->setDirection(direction); + } + + glEnable(GL_BLEND); + glBlendEquation(GL_FUNC_ADD); + glBlendFunc(GL_ONE, GL_ONE); + + SpeedStreaksShader::getInstance()->render(m_noise_texture->getTextureHandler(), intensity, radial_scroll_offset, world->getTime()); + + glDisable(GL_BLEND); +} + // ---------------------------------------------------------------------------- void PostProcessing::renderDoF(const FrameBuffer &framebuffer, GLuint color_texture, GLuint depth_stencil_texture) { @@ -1211,6 +1291,18 @@ FrameBuffer *PostProcessing::render(scene::ICameraSceneNode * const camnode, in_fbo = &rtts->getFBO(FBO_RGBA_1); out_fbo = &rtts->getFBO(FBO_RGBA_2); + { + PROFILER_PUSH_CPU_MARKER("- Speed streaks", 0xFF, 0x00, 0x00); + ScopedGPUTimer Timer(irr_driver->getGPUTimer(Q_SPEEDSTREAKS)); + if (isRace && World::getWorld() && UserConfigParams::m_speed_streaks) + { + in_fbo->bind(); + renderSpeedStreaks(); + // additive blend, specifically no ping/pong std::swap + } + PROFILER_POP_CPU_MARKER(); + } + { PROFILER_PUSH_CPU_MARKER("- Motion blur", 0xFF, 0x00, 0x00); ScopedGPUTimer Timer(irr_driver->getGPUTimer(Q_MOTIONBLUR)); diff --git a/src/graphics/post_processing.hpp b/src/graphics/post_processing.hpp index 82d796705bf..b4c3c8e949d 100644 --- a/src/graphics/post_processing.hpp +++ b/src/graphics/post_processing.hpp @@ -47,7 +47,7 @@ class PostProcessing std::vector m_boost_time; video::ITexture* m_areamap; - + video::ITexture* m_noise_texture; public: PostProcessing(); @@ -87,6 +87,9 @@ class PostProcessing void renderMotionBlur(const FrameBuffer &in_fbo, FrameBuffer &out_fbo, GLuint depth_stencil_texture); + + void renderSpeedStreaks(); + void renderGlow(const FrameBuffer& quarter_framebuffer) const; void renderLightning(core::vector3df intensity); diff --git a/src/graphics/speed_streaks.cpp b/src/graphics/speed_streaks.cpp new file mode 100644 index 00000000000..9c8c2890235 --- /dev/null +++ b/src/graphics/speed_streaks.cpp @@ -0,0 +1,78 @@ +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2026 the SuperTuxKart-Team +// Copyright (C) 2026 sbilliet +// +// 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. + +#include "graphics/speed_streaks.hpp" +#include "karts/kart.hpp" + +#include + +using namespace irr; + +SpeedStreaks::SpeedStreaks(const Kart& kart) + : m_kart(kart), + m_filtered_boost_intensity(0.0f), + m_radial_scroll_offset(0.0f), + m_direction(Direction::FORWARD) +{ +} + +// Calculate the boost intensity value. +// See comments on s_boost_intensity_lower_ratio and s_boost_intensity_upper_ratio for parameters. +float SpeedStreaks::calcBoostIntensity(float lower_bound_ratio, float upper_bound_ratio) const +{ + assert(lower_bound_ratio >= 0.0f && upper_bound_ratio > lower_bound_ratio); + float speed_ratio = m_kart.getCurrentAdditionalSpeedRatio(); + + const float result = (speed_ratio - lower_bound_ratio) / (upper_bound_ratio - lower_bound_ratio); + return core::clamp(result, 0.0f, 1.0f); +} + +// Set the direction in which the streaks should move on screen (inwards/outwards). +void SpeedStreaks::setDirection(Direction dir) +{ + m_direction = dir; +} + +void SpeedStreaks::reset() +{ + m_filtered_boost_intensity = 0.0f; + m_radial_scroll_offset = 0.0f; + m_direction = Direction::FORWARD; +} + +void SpeedStreaks::update(float dt) +{ + // Filter the kart's boost intensity to avoid abrupt visual changes. + // - Uses the last level and smoothly approaches the new value over time. + // - The speed at which it does this is dictated by s_boost_smoothing_rate. + const float interpolant = 1.0f - std::exp(-s_boost_intensity_smoothing_rate * dt); //exponential decay + m_filtered_boost_intensity = core::lerp( + m_filtered_boost_intensity, + calcBoostIntensity(s_boost_intensity_lower_ratio, s_boost_intensity_upper_ratio), + interpolant + ); + + // Accumulate UV scroll offset over time. + // Accumulated on CPU to ensure a continuous offset over time; + // Passing per-frame speed(dictated by intensity) and time values directly + // to the shader would produce discontinuous UV scrolling. + // + // Encodes speed/time/ and direction together in a single uniform to be passed to the shader. + const float streak_speed = s_streaks_speed_min + s_streaks_speed_max * m_filtered_boost_intensity; + m_radial_scroll_offset += dt * streak_speed * m_direction; +} diff --git a/src/graphics/speed_streaks.hpp b/src/graphics/speed_streaks.hpp new file mode 100644 index 00000000000..50bf5077ec6 --- /dev/null +++ b/src/graphics/speed_streaks.hpp @@ -0,0 +1,76 @@ +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2026 the SuperTuxKart-Team +// Copyright (C) 2026 sbilliet +// +// 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. + +#ifndef HEADER_SPEED_STREAKS_HPP +#define HEADER_SPEED_STREAKS_HPP + +class Kart; +class SpeedStreaks +{ +public: + // Direction in which the streaks should move on screen + // Tied to which camera is active, forward or reversing camera. + enum Direction{ + REVERSE = -1, + FORWARD = 1, + }; + + SpeedStreaks(const Kart& kart); + + void reset(); + void update(float dt); + + // Set the direction in which the streaks should move on screen (inwards/outwards). + void setDirection(Direction direction); + + float getBoostIntensity() const { return m_filtered_boost_intensity; } + float getRadialScrollOffset() const { return m_radial_scroll_offset; } + +private: + // Calculate the boost intensity value. + // See comments on s_boost_intensity_lower_ratio and s_boost_intensity_upper_ratio for parameters. + float calcBoostIntensity(float lower_bound_ratio = 0.0f, float upper_bound_ratio = 1.0f) const; + + // Reference to the kart to which these speed streak parameters belong. + const Kart& m_kart; + + // Smoothened kart boost intensity (lerped from current to the target each frame). + float m_filtered_boost_intensity; + + // Integrated uv offset: encodes speed/time/direction. + float m_radial_scroll_offset; + + // Speed streak direction (move inwards/outwards), based on camera facing. + Direction m_direction; + + // How fast/slow should the boost intensity interpolation settle on the target. + constexpr static float s_boost_intensity_smoothing_rate = 8.0f; + + // Speed excess (as a fraction of the generic engine max speed) at which the + // streak intensity starts rising: 0.0 = the kart's own unboosted top speed. + constexpr static float s_boost_intensity_lower_ratio = 0.2f; + // Speed excess (as a fraction of the generic engine max speed) at which the + // streak intensity reaches 1.0f. + constexpr static float s_boost_intensity_upper_ratio = 0.8f; + + // How fast/slow should the streaks move across the screen at low and full intensity. + constexpr static float s_streaks_speed_min = 0.3f; + constexpr static float s_streaks_speed_max = 0.6f; +}; + +#endif diff --git a/src/karts/abstract_kart.cpp b/src/karts/abstract_kart.cpp index b83f2473fb3..e82504a99b2 100644 --- a/src/karts/abstract_kart.cpp +++ b/src/karts/abstract_kart.cpp @@ -276,3 +276,4 @@ void AbstractKart::makeKartRest() body->proceedToTransform(t); setTrans(t); } // makeKartRest + diff --git a/src/karts/abstract_kart.hpp b/src/karts/abstract_kart.hpp index dafd3c70a6d..dbfa213b781 100644 --- a/src/karts/abstract_kart.hpp +++ b/src/karts/abstract_kart.hpp @@ -310,7 +310,16 @@ class AbstractKart : public Moveable * category. * \param category Which category to report on. */ virtual int getSpeedIncreaseTicksLeft(unsigned int category) const = 0; - + // ------------------------------------------------------------------------ + /** Returns the speed amount above the kart's own unboosted max speed. + */ + virtual float getCurrentAdditionalSpeed() const = 0; + // ------------------------------------------------------------------------ + /** Returns the ratio of the speed amount above the kart's own unboosted max speed + * relative to the generic engine max speed. + */ + virtual float getCurrentAdditionalSpeedRatio() const = 0; + // ------------------------------------------------------------------------ /** Sets the kart AI boost state. * Not pure abstract, since there is no need to implement this e.g. in Ghost. diff --git a/src/karts/kart.cpp b/src/karts/kart.cpp index 4408cd0e856..7e7324a3f76 100644 --- a/src/karts/kart.cpp +++ b/src/karts/kart.cpp @@ -38,6 +38,7 @@ #include "graphics/shadow.hpp" #include "graphics/skid_marks.hpp" #include "graphics/slip_stream.hpp" +#include "graphics/speed_streaks.hpp" #include "graphics/stk_text_billboard.hpp" #include "graphics/stars.hpp" #include "guiengine/scalable_font.hpp" @@ -444,6 +445,10 @@ void Kart::reset() { m_skidmarks->reset(); } + if (m_speedstreaks) + { + m_speedstreaks->reset(); + } #endif Vec3 front(0, 0, getKartLength()*0.5f); @@ -506,6 +511,29 @@ float Kart::getCurrentMaxSpeed() const { return m_max_speed->getCurrentMaxSpeed(); } // getCurrentMaxSpeed + +// ------------------------------------------------------------------------ +/** Returns the speed amount above the kart's own unboosted max speed. + */ +float Kart::getCurrentAdditionalSpeed() const +{ + const float speed = getSpeed(); + const float current_engine_max_speed = m_kart_properties->getEngineMaxSpeed(); + return core::max_(speed - current_engine_max_speed, 0.0f); +} // getCurrentAdditionalSpeed + +// ------------------------------------------------------------------------ +/** Returns the ratio of the speed amount above the kart's own unboosted max speed + * relative to the generic engine max speed. + */ +float Kart::getCurrentAdditionalSpeedRatio() const +{ + const float additional_speed_amount = getCurrentAdditionalSpeed(); + const float absolute_engine_max_speed = m_kart_properties->getEngineGenericMaxSpeed(); + + return additional_speed_amount / absolute_engine_max_speed; +} // getCurrentAdditionalSpeedRatio + // ----------------------------------------------------------------------------- int Kart::getSpeedIncreaseTicksLeft(unsigned int category) const { @@ -3031,12 +3059,15 @@ void Kart::loadData(RaceManager::KartType type, bool is_animated_model) #ifndef SERVER_ONLY m_skidmarks = nullptr; m_shadow = nullptr; + m_speedstreaks = nullptr; if (!GUIEngine::isNoGraphics() && m_kart_properties->getSkidEnabled()) { m_skidmarks.reset(new SkidMarks(*this)); } + m_speedstreaks.reset(new SpeedStreaks(*this)); + if (!GUIEngine::isNoGraphics() && (!CVS->isGLSL() || !CVS->isShadowEnabled()) && m_kart_properties ->getShadowMaterial()->getSamplerPath(0) != "unicolor_white") @@ -3375,18 +3406,24 @@ void Kart::updateGraphics(float dt) static video::SColor green(255, 61, 87, 23); #ifndef SERVER_ONLY - // draw skidmarks if relevant (we force pink skidmarks on when hitting - // a bubblegum) - if (World::getWorld()->getPhase() != - WorldStatus::IN_GAME_MENU_PHASE && - m_kart_properties->getSkidEnabled() && m_skidmarks) - { - m_skidmarks->update(dt, - m_bubblegum_ticks > 0, - (m_bubblegum_ticks > 0 - ? (m_has_caught_nolok_bubblegum ? &green - : &pink) - : NULL)); + if (World::getWorld()->getPhase() != WorldStatus::IN_GAME_MENU_PHASE) + { + // draw skidmarks if relevant (we force pink skidmarks on when hitting + // a bubblegum) + if (m_kart_properties->getSkidEnabled() && m_skidmarks) + { + m_skidmarks->update(dt, + m_bubblegum_ticks > 0, + (m_bubblegum_ticks > 0 + ? (m_has_caught_nolok_bubblegum ? &green + : &pink) + : NULL)); + } + + // update speedstreak parameters + if(m_speedstreaks){ + m_speedstreaks->update(dt); + } } #endif diff --git a/src/karts/kart.hpp b/src/karts/kart.hpp index 762a51d7f95..4b5a212cf34 100644 --- a/src/karts/kart.hpp +++ b/src/karts/kart.hpp @@ -53,6 +53,7 @@ class Skidding; class SkidMarks; class SlipStream; class Stars; +class SpeedStreaks; class TerrainInfo; #ifndef SERVER_ONLY @@ -137,6 +138,9 @@ class Kart : public AbstractKart /** The skidmarks object for this kart. */ std::unique_ptr m_skidmarks; + + /** Parameters for SpeedStreaks effect. */ + std::unique_ptr m_speedstreaks; #endif /** All particle effects. */ @@ -368,6 +372,15 @@ class Kart : public AbstractKart // ---------------------------------------------------------------------------------------- virtual float getCurrentMaxSpeed() const OVERRIDE; // ---------------------------------------------------------------------------------------- + /** Returns the speed amount above the kart's own unboosted max speed. + */ + virtual float getCurrentAdditionalSpeed() const OVERRIDE; + // ---------------------------------------------------------------------------------------- + /** Returns the ratio of the speed amount above the kart's own unboosted max speed + * relative to the generic engine max speed. + */ + virtual float getCurrentAdditionalSpeedRatio() const OVERRIDE; + // ---------------------------------------------------------------------------------------- /** This is used on the client side only to set the speed of the kart * from the server information. */ virtual void setSpeed(float s) OVERRIDE { m_speed = s; } @@ -594,6 +607,8 @@ class Kart : public AbstractKart // ---------------------------------------------------------------------------------------- virtual Stars* getStarsEffect() const OVERRIDE { return m_stars_effect.get(); } + // ---------------------------------------------------------------------------------------- + SpeedStreaks* getSpeedStreaks() const { return m_speedstreaks.get(); } // ------------------------------------------------------------------------ /** Return the confirmed finish ticks (sent by the server) * indicating that this kart has really finished the race. */ diff --git a/src/utils/profiler.cpp b/src/utils/profiler.cpp index 9821f60a813..09ab23d655d 100644 --- a/src/utils/profiler.cpp +++ b/src/utils/profiler.cpp @@ -724,7 +724,7 @@ void Profiler::writeToFile() << "Dynamic lighting, Particle effects, Animated characters, Geometry Detail, " << "Bloom, Glow, Light Shaft, Anti-Aliasing (MLAA), SSAO," << "Anisotropic Filtering, Shadow Resolution, Light scattering, Degraded IBL," - << "Motion Blur, Depth of Field, Texture compression, HD Textures, HQ Mipmap,"; + << "Motion Blur, Depth of Field, Speed Streaks, Texture compression, HD Textures, HQ Mipmap,"; f << std::endl; f << "Values, " << UserConfigParams::m_real_width << ", " @@ -745,6 +745,7 @@ void Profiler::writeToFile() << UserConfigParams::m_degraded_IBL << ", " << UserConfigParams::m_motionblur << ", " << UserConfigParams::m_dof << ", " + << UserConfigParams::m_speed_streaks << ", " << UserConfigParams::m_texture_compression << ", " << UserConfigParams::m_high_definition_textures << ", " << UserConfigParams::m_hq_mipmap << ","; diff --git a/src/utils/profiler.hpp b/src/utils/profiler.hpp index afb3c60470e..b933c7b04b5 100644 --- a/src/utils/profiler.hpp +++ b/src/utils/profiler.hpp @@ -58,6 +58,7 @@ enum QueryPerf Q_BLOOM, Q_TONEMAP, Q_MOTIONBLUR, + Q_SPEEDSTREAKS, Q_LIGHTNING, Q_MLAA, Q_GUI,