1 //
2 //  SuperTuxKart - a fun racing game with go-kart
3 //  Copyright (C) 2004-2015 Ingo Ruhnke <grumbel@gmx.de>
4 //  Copyright (C) 2013-2015 Joerg Henrichs
5 //
6 //  This program is free software; you can redistribute it and/or
7 //  modify it under the terms of the GNU General Public License
8 //  as published by the Free Software Foundation; either version 3
9 //  of the License, or (at your option) any later version.
10 //
11 //  This program is distributed in the hope that it will be useful,
12 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 //  GNU General Public License for more details.
15 //
16 //  You should have received a copy of the GNU General Public License
17 //  along with this program; if not, write to the Free Software
18 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19 
20 #include "graphics/skid_marks.hpp"
21 
22 #include "config/stk_config.hpp"
23 #include "graphics/central_settings.hpp"
24 #include "graphics/material.hpp"
25 #include "graphics/material_manager.hpp"
26 #include "graphics/sp/sp_dynamic_draw_call.hpp"
27 #include "graphics/sp/sp_per_object_uniform.hpp"
28 #include "graphics/sp/sp_shader.hpp"
29 #include "graphics/sp/sp_shader_manager.hpp"
30 #include "graphics/sp/sp_texture_manager.hpp"
31 #include "graphics/sp/sp_uniform_assigner.hpp"
32 #include "karts/abstract_kart.hpp"
33 #include "karts/skidding.hpp"
34 #include "modes/world.hpp"
35 #include "physics/btKart.hpp"
36 #include "utils/mini_glm.hpp"
37 
38 #ifndef SERVER_ONLY
39 
40 float     SkidMarks::m_avoid_z_fighting  = 0.005f;
41 const int SkidMarks::m_start_alpha       = 200;
42 const int SkidMarks::m_start_grey        = 32;
43 
44 /** Initialises empty skid marks. */
SkidMarks(const AbstractKart & kart,float width)45 SkidMarks::SkidMarks(const AbstractKart& kart, float width) : m_kart(kart)
46 {
47     m_width = width;
48     m_material = material_manager->getMaterialSPM("skidmarks.png", "",
49         "alphablend");
50     m_shader = SP::SPShaderManager::get()->getSPShader("alphablend");
51     assert(m_shader);
52     auto texture = SP::SPTextureManager::get()->getTexture(
53         m_material->getSamplerPath(0), m_material,
54         m_shader->isSrgbForTextureLayer(0), m_material->getContainerId());
55     m_skid_marking = false;
56 }   // SkidMark
57 
58 //-----------------------------------------------------------------------------
59 /** Removes all skid marks from the scene graph and frees the state. */
~SkidMarks()60 SkidMarks::~SkidMarks()
61 {
62     reset();  // remove all skid marks
63 }   // ~SkidMarks
64 
65 //-----------------------------------------------------------------------------
66 /** Removes all skid marks, called when a race is restarted.
67  */
reset()68 void SkidMarks::reset()
69 {
70     m_left.clear();
71     m_right.clear();
72     m_skid_marking = false;
73 }   // reset
74 
75 //-----------------------------------------------------------------------------
76 /** Either adds to an existing skid mark quad, or (if the kart is skidding)
77  *  starts a new skid mark quad.
78  *  \param dt Time step.
79  */
update(float dt,bool force_skid_marks,video::SColor * custom_color)80 void SkidMarks::update(float dt, bool force_skid_marks,
81                        video::SColor* custom_color)
82 {
83     //if the kart is gnu, then don't skid because he floats!
84     if (m_kart.isWheeless())
85         return;
86 
87     float f = dt / stk_config->m_skid_fadeout_time;
88     auto it = m_left.begin();
89     // Don't clean the current skidmarking
90     while (it != m_left.end())
91     {
92         if ((it + 1 != m_left.end() || !m_skid_marking)
93             && (*it)->fade(f))
94         {
95             it = m_left.erase(it);
96             continue;
97         }
98         it++;
99     }
100     it = m_right.begin();
101     while (it != m_right.end())
102     {
103         if ((it + 1 != m_right.end() || !m_skid_marking)
104             && (*it)->fade(f))
105         {
106             it = m_right.erase(it);
107             continue;
108         }
109         it++;
110     }
111 
112     // Get raycast information
113     // -----------------------
114     btKart *vehicle = m_kart.getVehicle();
115 
116     Vec3 raycast_right;
117     Vec3 raycast_left;
118     vehicle->getVisualContactPoint(m_kart.getSmoothedTrans(), &raycast_left,
119         &raycast_right);
120 
121     btTransform smoothed_inv = m_kart.getSmoothedTrans().inverse();
122     Vec3 lc_l = smoothed_inv(raycast_left);
123     Vec3 lc_r = smoothed_inv(raycast_right);
124     btTransform skidding_rotation = m_kart.getSmoothedTrans();
125     skidding_rotation.setRotation(m_kart.getSmoothedTrans().getRotation() *
126         btQuaternion(m_kart.getSkidding()->getVisualSkidRotation(), 0.0f, 0.0f));
127     raycast_left = skidding_rotation(lc_l);
128     raycast_right = skidding_rotation(lc_r);
129 
130     Vec3 delta = raycast_right - raycast_left;
131 
132     // The kart is making skid marks when it's:
133     // - forced to leave skid marks, or all of:
134     // - in accumulating skidding mode
135     // - not doing the grphical jump
136     // - wheels are in contact with floor, which includes a special case:
137     //   the physics force both wheels on one axis to touch the ground or not.
138     //   If only one wheel touches the ground, the 2nd one gets the same
139     //   raycast result --> delta is 0, which is considered to be not skidding.
140     const Skidding *skid = m_kart.getSkidding();
141     bool is_skidding = vehicle->visualWheelsTouchGround() &&
142                ( force_skid_marks ||
143                  (    (skid->getSkidState()==Skidding::SKID_ACCUMULATE_LEFT||
144                        skid->getSkidState()==Skidding::SKID_ACCUMULATE_RIGHT )
145                     && !skid->isJumping()
146                     && delta.length2()>=0.0001f                           ) );
147 
148     if(m_skid_marking)
149     {
150         assert(!m_left.empty());
151         assert(!m_right.empty());
152         if (!is_skidding)   // end skid marking
153         {
154             m_skid_marking = false;
155             return;
156         }
157 
158         // We are still skid marking, so add the latest quad
159         // -------------------------------------------------
160 
161         delta.normalize();
162         delta *= m_width*0.5f;
163 
164         Vec3 start = m_left.back()->getCenterStart();
165         Vec3 newPoint = (raycast_left + raycast_right)/2;
166         // this linear distance does not account for the kart turning, it's true,
167         // but it produces good enough results
168         float distance = (newPoint - start).length();
169 
170         const Vec3 up_offset = (m_kart.getNormal() * 0.05f);
171         m_left.back()->add(raycast_left - delta + up_offset,
172             raycast_left + delta + up_offset, m_kart.getNormal(), distance);
173         m_right.back()->add(raycast_right - delta + up_offset,
174             raycast_right + delta + up_offset, m_kart.getNormal(), distance);
175         return;
176     }
177 
178     // Currently no skid marking
179     // -------------------------
180     if (!is_skidding) return;
181 
182     // Start new skid marks
183     // --------------------
184     // No skidmarking if wheels don't have contact
185     if(!vehicle->visualWheelsTouchGround()) return;
186     if(delta.length2()<0.0001) return;
187 
188     delta.normalize();
189     delta *= m_width*0.5f;
190 
191     const int cleaning_threshold =
192         core::clamp(int(World::getWorld()->getNumKarts()), 5, 15);
193     while ((int)m_left.size() >=
194         stk_config->m_max_skidmarks / cleaning_threshold)
195     {
196         m_left.erase(m_left.begin());
197     }
198     while ((int)m_right.size() >=
199         stk_config->m_max_skidmarks / cleaning_threshold)
200     {
201         m_right.erase(m_right.begin());
202     }
203 
204     m_left.emplace_back(
205         new SkidMarkQuads(raycast_left - delta, raycast_left + delta,
206         m_kart.getNormal(), m_material, m_shader, m_avoid_z_fighting,
207         custom_color));
208 
209     m_right.emplace_back(
210         new SkidMarkQuads(raycast_right - delta, raycast_right + delta,
211         m_kart.getNormal(), m_material, m_shader, m_avoid_z_fighting,
212         custom_color));
213 
214     m_skid_marking = true;
215 }   // update
216 
217 //=============================================================================
SkidMarkQuads(const Vec3 & left,const Vec3 & right,const Vec3 & normal,Material * material,std::shared_ptr<SP::SPShader> shader,float z_offset,video::SColor * custom_color)218 SkidMarks::SkidMarkQuads::SkidMarkQuads(const Vec3 &left,
219                                         const Vec3 &right,
220                                         const Vec3 &normal,
221                                         Material* material,
222                                         std::shared_ptr<SP::SPShader> shader,
223                                         float z_offset,
224                                         video::SColor* custom_color)
225 {
226     m_center_start = (left + right)/2;
227     m_z_offset = z_offset;
228     m_fade_out = 0.0f;
229     m_dy_dc = std::make_shared<SP::SPDynamicDrawCall>
230         (scene::EPT_TRIANGLE_STRIP, shader, material);
231     static_cast<SP::SPPerObjectUniform*>(m_dy_dc.get())->addAssignerFunction
232         ("custom_alpha", [this](SP::SPUniformAssigner* ua)->void
233         {
234             // SP custom_alpha is assigned 1 - x, so this is correct
235             ua->setValue(m_fade_out);
236         });
237     SP::addDynamicDrawCall(m_dy_dc);
238     m_start_color = (custom_color != NULL ? *custom_color :
239         video::SColor(255, SkidMarks::m_start_grey, SkidMarks::m_start_grey,
240         SkidMarks::m_start_grey));
241 
242     if (CVS->isDeferredEnabled())
243     {
244         m_start_color.setRed(SP::srgb255ToLinear(m_start_color.getRed()));
245         m_start_color.setGreen(SP::srgb255ToLinear(m_start_color.getGreen()));
246         m_start_color.setBlue(SP::srgb255ToLinear(m_start_color.getBlue()));
247     }
248 
249     add(left, right, normal, 0.0f);
250 }   // SkidMarkQuads
251 
252 //-----------------------------------------------------------------------------
~SkidMarkQuads()253 SkidMarks::SkidMarkQuads::~SkidMarkQuads()
254 {
255     m_dy_dc->removeFromSP();
256 }   // ~SkidMarkQuads
257 
258 //-----------------------------------------------------------------------------
259 /** Adds the two points to this SkidMarkQuads.
260  *  \param left,right Left and right coordinates.
261  */
add(const Vec3 & left,const Vec3 & right,const Vec3 & normal,float distance)262 void SkidMarks::SkidMarkQuads::add(const Vec3 &left,
263                                    const Vec3 &right,
264                                    const Vec3 &normal,
265                                    float distance)
266 {
267     // The skid marks must be raised slightly higher, otherwise it blends
268     // too much with the track.
269     int n = m_dy_dc->getVertexCount();
270 
271     video::S3DVertexSkinnedMesh v;
272     v.m_color = m_start_color;
273     v.m_color.setAlpha(0); // initially create all vertices at alpha=0...
274 
275     // then when adding a new set of vertices, make the previous 2 opaque.
276     // this ensures that the last two vertices are always at alpha=0,
277     // producing a fade-out effect
278     if (n > 3)
279     {
280         m_dy_dc->getSPMVertex()[n - 1].m_color.setAlpha(m_start_alpha);
281         m_dy_dc->getSPMVertex()[n - 2].m_color.setAlpha(m_start_alpha);
282     }
283 
284     v.m_position = Vec3(right + normal * m_z_offset).toIrrVector();
285     v.m_normal = MiniGLM::compressVector3(normal.toIrrVector());
286     short half_float_1 = 15360;
287     v.m_all_uvs[0] = half_float_1;
288     v.m_all_uvs[1] = MiniGLM::toFloat16(distance * 0.5f);
289     m_dy_dc->addSPMVertex(v);
290 
291     v.m_position = Vec3(left + normal * m_z_offset).toIrrVector();
292     v.m_all_uvs[0] = 0;
293     v.m_all_uvs[1] = MiniGLM::toFloat16(distance * 0.5f);
294     m_dy_dc->addSPMVertex(v);
295     m_dy_dc->setUpdateOffset(n > 3 ? n - 2 : n);
296     m_dy_dc->recalculateBoundingBox();
297 
298 }   // add
299 
300 // ----------------------------------------------------------------------------
301 /** Fades the current skid marks.
302  *  \param f fade factor.
303  *  \return true if this skid mark can be deleted (alpha == zero)
304  */
fade(float f)305 bool SkidMarks::SkidMarkQuads::fade(float f)
306 {
307     m_fade_out += f;
308     if (m_fade_out >= 1.0f)
309     {
310         return true;
311     }
312     return false;
313 }   // fade
314 
315 #endif
316