// // SuperTuxKart - a fun racing game with go-kart // Copyright (C) 2010-2015 Joerg Henrichs // // 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/slip_stream.hpp" #include "graphics/central_settings.hpp" #include "config/user_config.hpp" #include "graphics/irr_driver.hpp" #include "graphics/material.hpp" #include "graphics/material_manager.hpp" #include "graphics/stk_mesh_scene_node.hpp" #include "graphics/texture_manager.hpp" #include "io/file_manager.hpp" #include "karts/abstract_kart.hpp" #include "karts/controller/controller.hpp" #include "karts/kart_properties.hpp" #include "karts/max_speed.hpp" #include "modes/world.hpp" #include "tracks/quad.hpp" #include "utils/constants.hpp" #include #include #include /** Creates the slip stream object using a moving texture. * \param kart Pointer to the kart to which the slip stream * belongs to. */ SlipStream::SlipStream(AbstractKart* kart) : MovingTexture(0, 0), m_kart(kart) { Material *material = material_manager->getMaterial("slipstream.png"); createMesh(material); m_node = irr_driver->addMesh(m_mesh, "splistream"); scene::IMeshBuffer* buffer = m_mesh->getMeshBuffer(0); material->setMaterialProperties(&buffer->getMaterial(), buffer); #ifndef SERVER_ONLY STKMeshSceneNode* stk_node = dynamic_cast(m_node); if (stk_node != NULL) stk_node->setReloadEachFrame(true); m_mesh->drop(); #endif #ifdef DEBUG std::string debug_name = m_kart->getIdent()+" (slip-stream)"; m_node->setName(debug_name.c_str()); #endif m_node->setPosition(core::vector3df(0, 0*0.25f+2.5, m_kart->getKartLength()) ); m_node->setVisible(false); setTextureMatrix(&(m_node->getMaterial(0).getTextureMatrix(0))); m_slipstream_time = 0.0f; float length = m_kart->getKartProperties()->getSlipstreamLength(); float kw = m_kart->getKartWidth(); float ew = m_kart->getKartProperties()->getSlipstreamWidth(); float kl = m_kart->getKartLength(); Vec3 p[4]; p[0]=Vec3(-kw*0.5f, 0, -kl*0.5f ); p[1]=Vec3(-ew*0.5f, 0, -kl*0.5f-length); p[2]=Vec3( ew*0.5f, 0, -kl*0.5f-length); p[3]=Vec3( kw*0.5f, 0, -kl*0.5f ); m_slipstream_quad = new Quad(p[0], p[1], p[2], p[3]); if(UserConfigParams::m_slipstream_debug) { video::SMaterial material; material.MaterialType = video::EMT_TRANSPARENT_ADD_COLOR; material.setFlag(video::EMF_BACK_FACE_CULLING, false); material.setFlag(video::EMF_LIGHTING, false); m_debug_mesh = irr_driver->createQuadMesh(&material, true); scene::IMeshBuffer *buffer = m_debug_mesh->getMeshBuffer(0); assert(buffer->getVertexType()==video::EVT_STANDARD); irr::video::S3DVertex* vertices = (video::S3DVertex*)buffer->getVertices(); video::SColor red(128, 255, 0, 0); for(unsigned int i=0; i<4; i++) { vertices[i].Pos = p[i].toIrrVector(); vertices[i].Color = red; vertices[i].TCoords = core::vector2df(0, 0); } video::SMaterial &mat = buffer->getMaterial(); // Meshes need a texture, otherwise stk crashes. #ifndef SERVER_ONLY video::ITexture *red_texture = getUnicolorTexture(red); mat.setTexture(0, red_texture); #endif buffer->recalculateBoundingBox(); m_mesh->setBoundingBox(buffer->getBoundingBox()); m_debug_node = irr_driver->addMesh(m_debug_mesh, "splistream_debug", m_kart->getNode()); m_debug_node->grab(); } else { m_debug_mesh = NULL; m_debug_node = NULL; } } // SlipStream //----------------------------------------------------------------------------- /** Removes the node from the scene graph. */ SlipStream::~SlipStream() { irr_driver->removeNode(m_node); if(m_debug_node) { m_debug_node->drop(); m_debug_mesh->drop(); } delete m_slipstream_quad; } // ~SlipStream //----------------------------------------------------------------------------- /** Called at re-start of a race. */ void SlipStream::reset() { m_slipstream_mode = SS_NONE; m_slipstream_time = 0; // Reset a potential max speed increase m_kart->increaseMaxSpeed(MaxSpeed::MS_INCREASE_SLIPSTREAM, 0, 0, 0, 0); } // reset //----------------------------------------------------------------------------- /** Creates the mesh for the slipstream effect. This function creates a * first a series of circles (with a certain number of vertices each and * distance from each other. Then it will create the triangles and add * texture coordniates. * \param material The material to use. */ void SlipStream::createMesh(Material* material) { // All radius, starting with the one closest to the kart (and // widest) to the one furthest away. A 0 indicates the end of the list float radius[] = {1.5f, 1.0f, 0.5f, 0.0f}; // The distance of each of the circle from the kart. The number of // entries in this array must be the same as the number of non-zero // entries in the radius[] array above. No 'end of list' entry required. // Note also that in order to avoid a 'bent' in the texture the // difference between the distances must be linearly correlated to the // difference in the corresponding radius, see the following sideview: // + // + | // + | | three radius // | | | // +----+----+ // 0 1 2 // distances // (radius1-radius0)/(distance1-distance0) = (radius2-radius1)/(distnace2-distance0) // This way the line connecting the upper "+" is a straight line, // and so the 3d cone shape will not be disturbed. float distance[] = {2.0f, 6.0f, 10.0f }; // The alpha values for the rings, no 'end of list' entry required. int alphas[] = {0, 255, 0}; // Loop through all given radius to determine the number // of segments to create. unsigned int num_circles=0; while(radius[num_circles]>0.0f) num_circles++; assert(num_circles > 0); // Length is distance of last circle to distance of first circle: m_length = distance[num_circles-1] - distance[0]; // The number of points for each circle. Since part of the slip stream // might be under the ground (esp. first and last segment), specify // which one is the first and last to be actually drawn. const unsigned int num_segments = 15; const unsigned int first_segment = 0; const unsigned int last_segment = 14; const float f = 2*M_PI/float(num_segments); scene::SMeshBuffer *buffer = new scene::SMeshBuffer(); buffer->getMaterial().TextureLayer[0].Texture = material->getTexture(); for(unsigned int j=0; jVertices.push_back(v); } // for iIndices.push_back( j *diff_segments+i ); buffer->Indices.push_back((j+1)*diff_segments+i ); buffer->Indices.push_back( j *diff_segments+i+1); buffer->Indices.push_back( j *diff_segments+i+1); buffer->Indices.push_back((j+1)*diff_segments+i ); buffer->Indices.push_back((j+1)*diff_segments+i+1); } } // for jsetMaterialProperties(&buffer->getMaterial(), buffer); #ifndef SERVER_ONLY if (!CVS->isGLSL()) #endif { buffer->Material.setFlag(video::EMF_BACK_FACE_CULLING, false); buffer->Material.setFlag(video::EMF_COLOR_MATERIAL, true); buffer->Material.ColorMaterial = video::ECM_DIFFUSE_AND_AMBIENT; //buffer->Material.MaterialType = video::EMT_TRANSPARENT_ADD_COLOR; } scene::SMesh *mesh = new scene::SMesh(); mesh->addMeshBuffer(buffer); mesh->recalculateBoundingBox(); buffer->drop(); m_mesh = mesh; } // createMesh //----------------------------------------------------------------------------- /** Sets the animation intensity (or speed). * \param f Intensity: 0 = no slip stream, * 1 = collecting * 2 = using slip stream bonus */ void SlipStream::setIntensity(float f, const AbstractKart *kart) { if(!kart) { m_node->setVisible(false); return; } m_node->setVisible(true); const float above_terrain = 0.2f; core::vector3df my_pos = m_kart->getNode()->getPosition(); my_pos.Y = m_kart->getHoT()+above_terrain; m_node->setPosition(my_pos); core::vector3df other_pos = kart->getNode()->getPosition(); other_pos.Y = kart->getHoT()+above_terrain; core::vector3df diff = other_pos - my_pos; core::vector3df rotation = diff.getHorizontalAngle(); m_node->setRotation(rotation); float fs = diff.getLength()/m_length; m_node->setScale(core::vector3df(1, 1, fs)); // For real testing in game: this needs some tuning! m_node->setVisible(f!=0); MovingTexture::setSpeed(f, 0); int c = (int)(f*255); if (c > 255) c = 255; const unsigned int bcount = m_node->getMesh()->getMeshBufferCount(); for (unsigned int b=0; bgetMesh()->getMeshBuffer(b); irr::video::S3DVertex* vertices = (video::S3DVertex*)mb->getVertices(); for (unsigned int i=0; igetVertexCount(); i++) { const int color = (int)(c*(vertices[i].Color.getAlpha()/255.0f)); vertices[i].Color.setRed( color ); vertices[i].Color.setGreen( color ); vertices[i].Color.setBlue( color ); } } return; // For debugging: make the slip stream effect visible all the time m_node->setVisible(true); MovingTexture::setSpeed(1.0f, 0.0f); } // setIntensity //----------------------------------------------------------------------------- /** Returns true if enough slipstream credits have been accumulated * to get a boost when leaving the slipstream area. */ bool SlipStream::isSlipstreamReady() const { return m_slipstream_time> m_kart->getKartProperties()->getSlipstreamCollectTime(); } // isSlipstreamReady //----------------------------------------------------------------------------- /** Returns the additional force being applied to the kart because of * slipstreaming. */ void SlipStream::updateSlipstreamPower() { // See if we are currently using accumulated slipstream credits: // ------------------------------------------------------------- if(m_slipstream_mode==SS_USE) { setIntensity(2.0f, NULL); const KartProperties *kp = m_kart->getKartProperties(); m_kart->increaseMaxSpeed(MaxSpeed::MS_INCREASE_SLIPSTREAM, kp->getSlipstreamMaxSpeedIncrease(), kp->getSlipstreamAddPower(), kp->getSlipstreamDuration(), kp->getSlipstreamFadeOutTime()); } } // upateSlipstreamPower //----------------------------------------------------------------------------- /** Sets the color of the debug mesh (which shows the area in which slipstream * can be accumulated). * Color codes: * black: kart too slow * red: not inside of slipstream area * green: slipstream is being accumulated. */ void SlipStream::setDebugColor(const video::SColor &color) { if(!UserConfigParams::m_slipstream_debug) return; // FIXME: Does not work anymore - the colour is changed, but // visually there is no change. scene::IMeshBuffer *buffer = m_debug_mesh->getMeshBuffer(0); irr::video::S3DVertex* vertices = (video::S3DVertex*)buffer->getVertices(); for(unsigned int i=0; i<4; i++) vertices[i].Color=color; } // setDebugColor //----------------------------------------------------------------------------- /** Update, called once per timestep. * \param dt Time step size. */ void SlipStream::update(float dt) { const KartProperties *kp = m_kart->getKartProperties(); // Low level AIs should not do any slipstreaming. if(m_kart->getController()->disableSlipstreamBonus()) return; MovingTexture::update(dt); if(m_slipstream_mode==SS_USE) { m_slipstream_time -= dt; if(m_slipstream_time<0) m_slipstream_mode=SS_NONE; } updateSlipstreamPower(); // If this kart is too slow for slipstreaming taking effect, do nothing // -------------------------------------------------------------------- // Define this to get slipstream effect shown even when the karts are // not moving. This is useful for debugging the graphics of SS-ing. //#define DISPLAY_SLIPSTREAM_WITH_0_SPEED_FOR_DEBUGGING #ifndef DISPLAY_SLIPSTREAM_WITH_0_SPEED_FOR_DEBUGGING if(m_kart->getSpeed() < kp->getSlipstreamMinSpeed()) { setIntensity(0, NULL); m_slipstream_mode = SS_NONE; if(UserConfigParams::m_slipstream_debug) setDebugColor(video::SColor(255, 0, 0, 0)); return; } #endif // Then test if this kart is in the slipstream range of another kart: // ------------------------------------------------------------------ World *world = World::getWorld(); unsigned int num_karts = world->getNumKarts(); bool is_sstreaming = false; m_target_kart = NULL; // Note that this loop can not be simply replaced with a shorter loop // using only the karts with a better position - since a kart might // be a lap behind for(unsigned int i=0; igetKart(i); // Don't test for slipstream with itself, a kart that is being // rescued or exploding, or an eliminated kart if(m_target_kart==m_kart || m_target_kart->getKartAnimation() || m_target_kart->isEliminated() ) continue; // Transform this kart location into target kart point of view Vec3 lc = m_target_kart->getTrans().inverse()(m_kart->getXYZ()); // If the kart is 'on top' of this kart (e.g. up on a bridge), // don't consider it for slipstreaming. if (fabsf(lc.y()) > 6.0f) continue; // If the kart we are testing against is too slow, no need to test // slipstreaming. Note: We compare the speed of the other kart // against the minimum slipstream speed kart of this kart - not // entirely sure if this makes sense, but it makes it easier to // give karts different slipstream properties. #ifndef DISPLAY_SLIPSTREAM_WITH_0_SPEED_FOR_DEBUGGING if (m_target_kart->getSpeed() < kp->getSlipstreamMinSpeed()) { if(UserConfigParams::m_slipstream_debug && m_kart->getController()->isLocalPlayerController()) m_target_kart->getSlipstream() ->setDebugColor(video::SColor(255, 0, 0, 0)); continue; } #endif // Quick test: the kart must be not more than // slipstream length+0.5*kart_length()+0.5*target_kart_length // away from the other kart Vec3 delta = m_kart->getXYZ() - m_target_kart->getXYZ(); float l = kp->getSlipstreamLength() + 0.5f*( m_target_kart->getKartLength() +m_kart->getKartLength() ); if(delta.length2() > l*l) { if(UserConfigParams::m_slipstream_debug && m_kart->getController()->isLocalPlayerController()) m_target_kart->getSlipstream() ->setDebugColor(video::SColor(255, 0, 0, 128)); continue; } // Real test: if in slipstream quad of other kart if(m_target_kart->getSlipstream()->m_slipstream_quad ->pointInside(lc)) { is_sstreaming = true; break; } if(UserConfigParams::m_slipstream_debug && m_kart->getController()->isLocalPlayerController()) m_target_kart->getSlipstream() ->setDebugColor(video::SColor(255, 0, 0, 255)); } // for i < num_karts if(!is_sstreaming) { if(UserConfigParams::m_slipstream_debug && m_kart->getController()->isLocalPlayerController()) m_target_kart->getSlipstream() ->setDebugColor(video::SColor(255, 255, 0, 0)); if(isSlipstreamReady()) { // The first time slipstream is ready after collecting // and you are leaving the slipstream area, you get a // zipper bonus. if(m_slipstream_mode==SS_COLLECT) { m_slipstream_mode = SS_USE; m_kart->handleZipper(); m_slipstream_time = kp->getSlipstreamCollectTime(); return; } } m_slipstream_time -=dt; if(m_slipstream_time<0) m_slipstream_mode = SS_NONE; setIntensity(0, NULL); return; } // if !is_sstreaming if(UserConfigParams::m_slipstream_debug && m_kart->getController()->isLocalPlayerController()) m_target_kart->getSlipstream()->setDebugColor(video::SColor(255, 0, 255, 0)); // Accumulate slipstream credits now m_slipstream_time = m_slipstream_mode==SS_NONE ? dt : m_slipstream_time+dt; if(isSlipstreamReady()) m_kart->setSlipstreamEffect(9.0f); setIntensity(m_slipstream_time, m_target_kart); m_slipstream_mode = SS_COLLECT; if (m_slipstream_time > kp->getSlipstreamCollectTime()) { setIntensity(1.0f, m_target_kart); } } // update