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