Merge remote-tracking branch 'origin/master' into rewind

Fixed conflicts.
This commit is contained in:
hiker
2016-10-05 08:21:10 +11:00
105 changed files with 4219 additions and 4194 deletions

View File

@@ -67,6 +67,7 @@ set(ANGELSCRIPT_SOURCE
../../source/as_builder.cpp
../../source/as_bytecode.cpp
../../source/as_callfunc.cpp
../../source/as_callfunc_mips.cpp
../../source/as_callfunc_x86.cpp
../../source/as_callfunc_x64_gcc.cpp
../../source/as_callfunc_x64_msvc.cpp

View File

@@ -844,7 +844,7 @@
#define THISCALL_PASS_OBJECT_POINTER_ON_THE_STACK
#define AS_X86
#undef AS_NO_THISCALL_FUNCTOR_METHOD
#elif defined(__LP64__) && !defined(__arm64__)
#elif defined(__x86_64__)
#define AS_X64_GCC
#undef AS_NO_THISCALL_FUNCTOR_METHOD
#define HAS_128_BIT_PRIMITIVES

View File

@@ -16,3 +16,5 @@ add_library(glew STATIC
include/GL/glew.h include/GL/glxew.h include/GL/wglew.h
src/glew.c src/glewinfo.c
)
target_link_libraries(glew ${OPENGL_LIBRARIES})

View File

@@ -1,5 +1,5 @@
# Modify this file to change the last-modified date when you add/remove a file.
# This will then trigger a new cmake run automatically.
# This will then trigger a new cmake run automatically.
file(GLOB_RECURSE STK_HEADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "src/*.hpp")
file(GLOB_RECURSE STK_SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "src/*.cpp")
file(GLOB_RECURSE STK_SHADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "data/shaders/*")

View File

@@ -20,7 +20,7 @@
#include "karts/abstract_kart.hpp"
#include "karts/kart_properties.hpp"
#include "tracks/quad_graph.hpp"
#include "tracks/drive_graph.hpp"
#include "ICameraSceneNode.h"
@@ -62,7 +62,7 @@ void CameraEnd::readEndCamera(const XMLNode &root)
unsigned int index = i;
// In reverse mode, reverse the order in which the
// end cameras are read.
if(QuadGraph::get()->isReverse())
if(DriveGraph::get() != NULL && DriveGraph::get()->isReverse())
index = root.getNumNodes() - 1 - i;
const XMLNode *node = root.getNode(index);
EndCameraInformation eci;

View File

@@ -101,8 +101,7 @@ void CameraNormal::smoothMoveCamera(float dt)
Vec3 m_kart_camera_position_with_offset = m_kart->getTrans()(camera_offset);
// next target
core::vector3df current_target = m_kart->getXYZ().toIrrVector();
current_target.Y += 0.5f;
Vec3 current_target = m_kart->getTrans()(Vec3(0, 0.5f, 0));
// new required position of camera
core::vector3df wanted_position = m_kart_camera_position_with_offset.toIrrVector();
@@ -128,7 +127,7 @@ void CameraNormal::smoothMoveCamera(float dt)
if(getMode()!=CM_FALLING)
m_camera->setPosition(current_position);
m_camera->setTarget(current_target);//set new target
m_camera->setTarget(current_target.toIrrVector());//set new target
assert(!std::isnan(m_camera->getPosition().X));
assert(!std::isnan(m_camera->getPosition().Y));
@@ -252,8 +251,7 @@ void CameraNormal::positionCamera(float dt, float above_kart, float cam_angle,
float side_way, float distance, float smoothing)
{
Vec3 wanted_position;
Vec3 wanted_target = m_kart->getXYZ();
wanted_target.setY(wanted_target.getY() + above_kart);
Vec3 wanted_target = m_kart->getTrans()(Vec3(0, above_kart, 0));
float tan_up = tan(cam_angle);
Vec3 relative_position(side_way,

View File

@@ -355,7 +355,7 @@ void IrrDriver::createListOfVideoModes()
void IrrDriver::initDevice()
{
SIrrlichtCreationParameters params;
// If --no-graphics option was used, the null device can still be used.
if (!ProfileWorld::isNoGraphics())
{
@@ -438,7 +438,7 @@ void IrrDriver::initDevice()
m_device->drop();
m_device = NULL;
params.ForceLegacyDevice = (UserConfigParams::m_force_legacy_device ||
params.ForceLegacyDevice = (UserConfigParams::m_force_legacy_device ||
UserConfigParams::m_gamepad_visualisation);
// Try 32 and, upon failure, 24 then 16 bit per pixels
@@ -463,9 +463,9 @@ void IrrDriver::initDevice()
core::dimension2du(UserConfigParams::m_width,
UserConfigParams::m_height);
params.HandleSRGB = true;
params.ShadersPath = (file_manager->getShadersDir() +
params.ShadersPath = (file_manager->getShadersDir() +
"irrlicht/").c_str();
/*
switch ((int)UserConfigParams::m_antialiasing)
{
@@ -526,13 +526,13 @@ void IrrDriver::initDevice()
{
Log::fatal("irr_driver", "Couldn't initialise irrlicht device. Quitting.\n");
}
CVS->init();
bool recreate_device = false;
// Some drivers are able to create OpenGL 3.1 context, but shader-based
// pipeline doesn't work for them. For example some radeon drivers
// pipeline doesn't work for them. For example some radeon drivers
// support only GLSL 1.3 and it causes STK to crash. We should force to use
// fixed pipeline in this case.
if (!ProfileWorld::isNoGraphics() &&
@@ -542,13 +542,13 @@ void IrrDriver::initDevice()
"Re-creating device to workaround the issue.");
params.ForceLegacyDevice = true;
recreate_device = true;
recreate_device = true;
}
// This is the ugly hack for intel driver on linux, which doesn't
// use sRGB-capable visual, even if we request it. This causes
// the screen to be darker than expected. It affects mesa 10.6 and newer.
// Though we are able to force to use the proper format on mesa side by
// the screen to be darker than expected. It affects mesa 10.6 and newer.
// Though we are able to force to use the proper format on mesa side by
// setting WithAlphaChannel parameter.
else if (CVS->needsSRGBCapableVisualWorkaround())
{
@@ -558,19 +558,21 @@ void IrrDriver::initDevice()
params.WithAlphaChannel = true;
recreate_device = true;
}
if (!ProfileWorld::isNoGraphics() && recreate_device)
{
m_device->closeDevice();
m_device->drop();
m_device->clearSystemMessages();
m_device->run();
m_device->drop();
m_device = createDeviceEx(params);
if(!m_device)
{
Log::fatal("irr_driver", "Couldn't initialise irrlicht device. Quitting.\n");
}
CVS->init();
}
@@ -587,7 +589,7 @@ void IrrDriver::initDevice()
(UserConfigParams::m_shadows_resolution < 512 ||
UserConfigParams::m_shadows_resolution > 2048))
{
Log::warn("irr_driver",
Log::warn("irr_driver",
"Invalid value for UserConfigParams::m_shadows_resolution : %i",
(int)UserConfigParams::m_shadows_resolution);
UserConfigParams::m_shadows_resolution = 0;
@@ -697,7 +699,7 @@ void IrrDriver::setMaxTextureSize()
{
io::IAttributes &att = m_video_driver->getNonConstDriverAttributes();
att.setAttribute("MAX_TEXTURE_SIZE", core::dimension2du(
UserConfigParams::m_max_texture_size,
UserConfigParams::m_max_texture_size,
UserConfigParams::m_max_texture_size));
}
} // setMaxTextureSize
@@ -725,7 +727,7 @@ void IrrDriver::createSunInterposer()
mb->getMaterial().setTexture(7,
getUnicolorTexture(video::SColor(0, 0, 0, 0)));
}
m_sun_interposer = new STKMeshSceneNode(sphere,
m_sun_interposer = new STKMeshSceneNode(sphere,
m_scene_manager->getRootSceneNode(),
NULL, -1, "sun_interposer");
@@ -1176,7 +1178,7 @@ scene::IMeshSceneNode *IrrDriver::addSphere(float radius,
{
scene::IMesh *mesh = m_scene_manager->getGeometryCreator()
->createSphereMesh(radius);
mesh->setMaterialFlag(video::EMF_COLOR_MATERIAL, true);
video::SMaterial &m = mesh->getMeshBuffer(0)->getMaterial();
m.AmbientColor = color;
@@ -1227,7 +1229,7 @@ scene::IMeshSceneNode *IrrDriver::addMesh(scene::IMesh *mesh,
if (!parent)
parent = m_scene_manager->getRootSceneNode();
scene::IMeshSceneNode* node = new STKMeshSceneNode(mesh, parent,
scene::IMeshSceneNode* node = new STKMeshSceneNode(mesh, parent,
m_scene_manager, -1,
debug_name,
core::vector3df(0, 0, 0),
@@ -1266,7 +1268,7 @@ scene::ISceneNode *IrrDriver::addBillboard(const core::dimension2d< f32 > size,
if (!parent)
parent = m_scene_manager->getRootSceneNode();
node = new STKBillboard(parent, m_scene_manager, -1,
node = new STKBillboard(parent, m_scene_manager, -1,
vector3df(0., 0., 0.), size);
node->drop();
}
@@ -1487,7 +1489,7 @@ scene::ISceneNode *IrrDriver::addSkyBox(const std::vector<video::ITexture*> &tex
{
m_spherical_harmonics->setTextures(spherical_harmonics_textures);
}
return m_scene_manager->addSkyBoxSceneNode(texture[0], texture[1],
texture[2], texture[3],
texture[4], texture[5]);
@@ -2249,13 +2251,13 @@ void IrrDriver::update(float dt)
renderGLSL(dt);
else
renderFixed(dt);
GUIEngine::Screen* current_screen = GUIEngine::getCurrentScreen();
if (current_screen != NULL && current_screen->needs3D())
{
GUIEngine::render(dt);
}
if (world->getPhysics() != NULL)
{
IrrDebugDrawer* debug_drawer = world->getPhysics()->getDebugDrawer();
@@ -2274,7 +2276,7 @@ void IrrDriver::update(float dt)
m_video_driver->endScene();
}
if (m_request_screenshot) doScreenShot();
// Enable this next print statement to get render information printed
@@ -2448,7 +2450,7 @@ void IrrDriver::RTTProvider::setupRTTScene(PtrVector<scene::IMesh, REF>& mesh,
}
irr_driver->getSceneManager()->setAmbientLight(video::SColor(255, 35, 35, 35) );
const core::vector3df &spot_pos = core::vector3df(0, 30, 40);
m_light = irr_driver->getSceneManager()
->addLightSceneNode(NULL, spot_pos, video::SColorf(1.0f,1.0f,1.0f),
@@ -2564,9 +2566,9 @@ void IrrDriver::applyObjectPassShader(scene::ISceneNode * const node, bool rimli
const u32 mcount = node->getMaterialCount();
u32 i;
const video::E_MATERIAL_TYPE ref =
const video::E_MATERIAL_TYPE ref =
Shaders::getShader(rimlit ? ES_OBJECTPASS_RIMLIT : ES_OBJECTPASS_REF);
const video::E_MATERIAL_TYPE pass =
const video::E_MATERIAL_TYPE pass =
Shaders::getShader(rimlit ? ES_OBJECTPASS_RIMLIT : ES_OBJECTPASS);
const video::E_MATERIAL_TYPE origref = Shaders::getShader(ES_OBJECTPASS_REF);
@@ -2593,7 +2595,7 @@ void IrrDriver::applyObjectPassShader(scene::ISceneNode * const node, bool rimli
for (i = 0; i < mcount; i++)
{
video::SMaterial &nodemat = node->getMaterial(i);
video::SMaterial &mbmat = mesh ? mesh->getMeshBuffer(i)->getMaterial()
video::SMaterial &mbmat = mesh ? mesh->getMeshBuffer(i)->getMaterial()
: nodemat;
video::SMaterial *mat = &nodemat;

View File

@@ -153,9 +153,9 @@ void SkidMarks::update(float dt, bool force_skid_marks,
float distance = (newPoint - start).length();
m_left [m_current]->add(raycast_left-delta, raycast_left+delta,
distance);
m_kart.getNormal(), distance);
m_right[m_current]->add(raycast_right-delta, raycast_right+delta,
distance);
m_kart.getNormal(), distance);
// Adjust the boundary box of the mesh to include the
// adjusted aabb of its buffers.
core::aabbox3df aabb=m_nodes[m_current]->getMesh()
@@ -180,14 +180,16 @@ void SkidMarks::update(float dt, bool force_skid_marks,
delta *= m_width*0.5f;
SkidMarkQuads *smq_left =
new SkidMarkQuads(raycast_left-delta, raycast_left+delta ,
m_material, m_avoid_z_fighting, custom_color);
new SkidMarkQuads(raycast_left-delta, raycast_left+delta,
m_kart.getNormal(), m_material, m_avoid_z_fighting,
custom_color);
scene::SMesh *new_mesh = new scene::SMesh();
new_mesh->addMeshBuffer(smq_left);
SkidMarkQuads *smq_right =
new SkidMarkQuads(raycast_right-delta, raycast_right+delta,
m_material, m_avoid_z_fighting, custom_color);
m_kart.getNormal(), m_material, m_avoid_z_fighting,
custom_color);
new_mesh->addMeshBuffer(smq_right);
scene::IMeshSceneNode *new_node = irr_driver->addMesh(new_mesh, "skidmark");
if (STKMeshSceneNode* stkm = dynamic_cast<STKMeshSceneNode*>(new_node))
@@ -233,6 +235,7 @@ void SkidMarks::update(float dt, bool force_skid_marks,
//=============================================================================
SkidMarks::SkidMarkQuads::SkidMarkQuads(const Vec3 &left,
const Vec3 &right,
const Vec3 &normal,
video::SMaterial *material,
float z_offset,
video::SColor* custom_color)
@@ -250,7 +253,7 @@ SkidMarks::SkidMarkQuads::SkidMarkQuads(const Vec3 &left,
Material = *material;
m_aabb = core::aabbox3df(left.toIrrVector());
add(left, right, 0.0f);
add(left, right, normal, 0.0f);
} // SkidMarkQuads
@@ -261,6 +264,7 @@ SkidMarks::SkidMarkQuads::SkidMarkQuads(const Vec3 &left,
*/
void SkidMarks::SkidMarkQuads::add(const Vec3 &left,
const Vec3 &right,
const Vec3 &normal,
float distance)
{
// The skid marks must be raised slightly higher, otherwise it blends
@@ -280,13 +284,11 @@ void SkidMarks::SkidMarkQuads::add(const Vec3 &left,
Vertices[n - 2].Color.setAlpha(m_start_alpha);
}
v.Pos = left.toIrrVector();
v.Pos.Y += m_z_offset;
v.Normal = core::vector3df(0, 1, 0);
v.Pos = Vec3(left + normal * m_z_offset).toIrrVector();
v.Normal = normal.toIrrVector();
v.TCoords = core::vector2df(0.0f, distance*0.5f);
Vertices.push_back(v);
v.Pos = right.toIrrVector();
v.Pos.Y += m_z_offset;
v.Pos = Vec3(right + normal * m_z_offset).toIrrVector();
v.TCoords = core::vector2df(1.0f, distance*0.5f);
Vertices.push_back(v);
// Out of the box Irrlicht only supports triangle meshes and not

View File

@@ -85,10 +85,11 @@ private:
public:
SkidMarkQuads (const Vec3 &left, const Vec3 &right,
video::SMaterial *material, float z_offset,
video::SColor* custom_color = NULL);
const Vec3 &normal, video::SMaterial *material,
float z_offset, video::SColor* custom_color = NULL);
void add (const Vec3 &left,
const Vec3 &right,
const Vec3 &normal,
float distance);
void fade (float f);
/** Returns the aabb of this skid mark quads. */

View File

@@ -77,7 +77,6 @@ SlipStream::SlipStream(AbstractKart* kart) : MovingTexture(0, 0), m_kart(kart)
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_original_quad = new Quad(p[0], p[1], p[2], p[3]);
m_slipstream_quad = new Quad(p[0], p[1], p[2], p[3]);
if(UserConfigParams::m_slipstream_debug)
{
@@ -127,7 +126,6 @@ SlipStream::~SlipStream()
m_debug_node->drop();
m_debug_mesh->drop();
}
delete m_slipstream_original_quad;
delete m_slipstream_quad;
} // ~SlipStream
@@ -369,12 +367,6 @@ void SlipStream::update(float dt)
MovingTexture::update(dt);
// Update this karts slipstream quad (even for low level AI which don't
// use slipstream, since even then player karts can get slipstream,
// and so have to compare with the modified slipstream quad.
m_slipstream_original_quad->transform(m_kart->getTrans(),
m_slipstream_quad);
if(m_slipstream_mode==SS_USE)
{
m_slipstream_time -= dt;
@@ -418,12 +410,13 @@ void SlipStream::update(float dt)
m_target_kart->getKartAnimation() ||
m_target_kart->isEliminated() ) continue;
float diff = fabsf(m_target_kart->getXYZ().getY()
- m_kart->getXYZ().getY() );
// 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(diff>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
@@ -447,7 +440,7 @@ void SlipStream::update(float dt)
float l = kp->getSlipstreamLength()
+ 0.5f*( m_target_kart->getKartLength()
+m_kart->getKartLength() );
if(delta.length2_2d() > l*l)
if(delta.length2() > l*l)
{
if(UserConfigParams::m_slipstream_debug &&
m_kart->getController()->isLocalPlayerController())
@@ -457,7 +450,7 @@ void SlipStream::update(float dt)
}
// Real test: if in slipstream quad of other kart
if(m_target_kart->getSlipstream()->m_slipstream_quad
->pointInQuad(m_kart->getXYZ()))
->pointInside(lc))
{
is_sstreaming = true;
break;

View File

@@ -66,14 +66,9 @@ private:
* 'slipstream credits', or the kart is using accumulated credits. */
enum {SS_NONE, SS_COLLECT, SS_USE} m_slipstream_mode;
/** The quad inside which another kart is considered to be slipstreaming.
* This value is current area, i.e. takes the kart position into account. */
/** This is slipstream area if the kart is at 0,0,0 without rotation. */
Quad *m_slipstream_quad;
/** This is slipstream area if the kart is at 0,0,0 without rotation. From
* this value m_slipstream_area is computed by applying the kart transform. */
Quad *m_slipstream_original_quad;
/** The kart from which this kart gets slipstream. Used by the AI to
** overtake the right kart. */
AbstractKart* m_target_kart;

View File

@@ -572,19 +572,17 @@ void Attachment::update(float dt)
Vec3 hit_point;
Vec3 normal;
const Material* material_hit;
Vec3 pos = m_kart->getXYZ();
Vec3 to=pos+Vec3(0, -10000, 0);
World* world = World::getWorld();
world->getTrack()->getTriangleMesh().castRay(pos, to, &hit_point,
&material_hit, &normal);
world->getTrack()->getTriangleMesh().castRay(m_kart->getXYZ(),
m_kart->getTrans().getBasis() * Vec3(0, -10000, 0), &hit_point,
&material_hit, &normal);
// This can happen if the kart is 'over nothing' when dropping
// the bubble gum
if(material_hit)
{
normal.normalize();
pos.setY(hit_point.getY()-0.05f);
Vec3 pos = hit_point + m_kart->getTrans().getBasis() * Vec3(0, -0.05f, 0);
ItemManager::get()->newItem(Item::ITEM_BUBBLEGUM, pos, normal, m_kart);
}
}

View File

@@ -24,6 +24,7 @@
#include "graphics/material.hpp"
#include "io/xml_node.hpp"
#include "karts/abstract_kart.hpp"
#include "modes/linear_world.hpp"
#include "utils/random_generator.hpp"
#include "utils/log.hpp" //TODO: remove after debugging is done
@@ -55,10 +56,11 @@ Bowling::Bowling(AbstractKart *kart)
if(m_speed < min_speed) m_speed = min_speed;
}
const Vec3& normal = kart->getNormal();
createPhysics(y_offset, btVector3(0.0f, 0.0f, m_speed*2),
new btSphereShape(0.5f*m_extend.getY()),
0.8f /*restitution*/,
-70.0f /*gravity*/,
0.4f /*restitution*/,
-70.0f*normal /*gravity*/,
true /*rotates*/);
// Even if the ball is fired backwards, m_speed must be positive,
// otherwise the ball can start to vibrate when energy is added.
@@ -136,14 +138,15 @@ bool Bowling::updateAndDelete(float dt)
m_body->applyCentralForce(direction);
}
}
// Bowling balls lose energy (e.g. when hitting the track), so increase
// the speed if the ball is too slow, but only if it's not too high (if
// the ball is too high, it is 'pushed down', which can reduce the
// speed, which causes the speed to increase, which in turn causes
// the ball to fly higher and higher.
//btTransform trans = getTrans();
float hat = getXYZ().getY()-getHoT();
float hat = (getXYZ() - getHitPoint()).length();
if(hat-0.5f*m_extend.getY()<0.01f)
{
const Material *material = getMaterial();

View File

@@ -35,6 +35,12 @@ Cake::Cake (AbstractKart *kart) : Flyable(kart, PowerupManager::POWERUP_CAKE)
{
m_target = NULL;
setDoTerrainInfo(false);
btVector3 gravity_vector;
btQuaternion q = kart->getTrans().getRotation();
gravity_vector = Vec3(0, -1, 0).rotate(q.getAxis(), q.getAngle());
gravity_vector = gravity_vector.normalize() * m_gravity;
// A bit of a hack: the mass of this kinematic object is still 1.0
// (see flyable), which enables collisions. I tried setting
// collisionFilterGroup/mask, but still couldn't get this object to
@@ -85,13 +91,14 @@ Cake::Cake (AbstractKart *kart) : Flyable(kart, PowerupManager::POWERUP_CAKE)
&fire_angle, &up_velocity);
// apply transformation to the bullet object (without pitch)
trans.setRotation(btQuaternion(btVector3(0,1,0), fire_angle));
btQuaternion q;
q = trans.getRotation() * btQuaternion(btVector3(0, 1, 0), fire_angle);
trans.setRotation(q);
m_initial_velocity = Vec3(0.0f, up_velocity, m_speed);
createPhysics(forward_offset, m_initial_velocity,
new btCylinderShape(0.5f*m_extend),
0.5f /* restitution */, -m_gravity,
0.5f /* restitution */, gravity_vector,
true /* rotation */, false /* backwards */, &trans);
}
else
@@ -105,7 +112,7 @@ Cake::Cake (AbstractKart *kart) : Flyable(kart, PowerupManager::POWERUP_CAKE)
createPhysics(forward_offset, m_initial_velocity,
new btCylinderShape(0.5f*m_extend),
0.5f /* restitution */, -m_gravity,
0.5f /* restitution */, gravity_vector,
true /* rotation */, backwards, &trans);
}

View File

@@ -28,13 +28,14 @@
#include "graphics/explosion.hpp"
#include "graphics/irr_driver.hpp"
#include "graphics/material.hpp"
#include "graphics/mesh_tools.hpp"
#include "graphics/stars.hpp"
#include "io/xml_node.hpp"
#include "items/projectile_manager.hpp"
#include "karts/abstract_kart.hpp"
#include "karts/explosion_animation.hpp"
#include "modes/world.hpp"
#include "modes/linear_world.hpp"
#include "modes/soccer_world.hpp"
#include "physics/physics.hpp"
#include "tracks/track.hpp"
@@ -101,7 +102,7 @@ Flyable::Flyable(AbstractKart *kart, PowerupManager::PowerupType type,
*/
void Flyable::createPhysics(float forw_offset, const Vec3 &velocity,
btCollisionShape *shape,
float restitution, const float gravity,
float restitution, const btVector3& gravity,
const bool rotates, const bool turn_around,
const btTransform* custom_direction)
{
@@ -133,7 +134,7 @@ void Flyable::createPhysics(float forw_offset, const Vec3 &velocity,
m_user_pointer.set(this);
World::getWorld()->getPhysics()->addBody(getBody());
m_body->setGravity(btVector3(0.0f, gravity, 0));
m_body->setGravity(gravity);
// Rotate velocity to point in the right direction
btVector3 v=trans.getBasis()*velocity;
@@ -296,65 +297,59 @@ void Flyable::getLinearKartItemIntersection (const Vec3 &origin,
float *fire_angle,
float *up_velocity)
{
Vec3 relative_target_kart_loc = target_kart->getXYZ() - origin;
// Transform the target into the firing kart's frame of reference
btTransform inv_trans = m_owner->getTrans().inverse();
Vec3 relative_target_kart_loc = inv_trans(target_kart->getXYZ());
// Find the direction target is moving in
btTransform trans = target_kart->getTrans();
Vec3 target_direction(trans.getBasis().getColumn(2));
float dx = relative_target_kart_loc.getX();
float dy = relative_target_kart_loc.getY();
float dz = relative_target_kart_loc.getZ();
// Now rotate it to the firing kart's frame of reference
btQuaternion inv_rotate = inv_trans.getRotation();
target_direction =
target_direction.rotate(inv_rotate.getAxis(), inv_rotate.getAngle());
// Now we try to find the angle to aim at to hit the target.
// Warning : Funky math stuff going on below. To understand, see answer by
// Jeffrey Hantin here :
// http://stackoverflow.com/questions/2248876/2d-game-fire-at-a-moving-target-by-predicting-intersection-of-projectile-and-u
float target_x_speed = target_direction.getX()*target_kart->getSpeed();
float target_z_speed = target_direction.getZ()*target_kart->getSpeed();
float target_y_speed = target_direction.getY()*target_kart->getSpeed();
float gy = target_direction.getY();
float a = (target_x_speed*target_x_speed) + (target_z_speed*target_z_speed) -
(item_XZ_speed*item_XZ_speed);
float b = 2 * (target_x_speed * (relative_target_kart_loc.getX())
+ target_z_speed * (relative_target_kart_loc.getZ()));
float c = relative_target_kart_loc.getX()*relative_target_kart_loc.getX()
+ relative_target_kart_loc.getZ()*relative_target_kart_loc.getZ();
float discriminant = b*b - 4 * a*c;
if (discriminant < 0) discriminant = 0;
//Projected onto X-Z plane
float target_kart_speed = target_direction.length_2d()
* target_kart->getSpeed();
float target_kart_heading = target_kart->getHeading();
float dist = -(target_kart_speed / item_XZ_speed)
* (dx * cosf(target_kart_heading) -
dz * sinf(target_kart_heading) );
float f = dx*dx + dz*dz - dist*dist;
// Avoid negative square root
if(f>0)
f = sqrtf(f);
else
f = 0.0f;
float fire_th = (dx*dist - dz * f)
/ (dx*dx + dz*dz);
if(fire_th>1)
fire_th = 1.0f;
else if (fire_th<-1.0f)
fire_th = -1.0f;
fire_th = (((dist - dx*fire_th) / dz > 0) ? -acosf(fire_th)
: acosf(fire_th));
float time = 0.0f;
float a = item_XZ_speed * sinf (fire_th)
+ target_kart_speed * sinf (target_kart_heading);
float b = item_XZ_speed * cosf (fire_th)
+ target_kart_speed * cosf (target_kart_heading);
if (fabsf(a) > fabsf(b)) time = fabsf (dx / a);
else if (b != 0.0f) time = fabsf(dz / b);
if (fire_th > M_PI)
fire_th -= M_PI;
else
fire_th += M_PI;
float t1 = (-b + sqrt(discriminant)) / (2 * a);
float t2 = (-b - sqrt(discriminant)) / (2 * a);
float time;
if (t1 >= 0 && t1<t2) time = t1;
else time = t2;
//createPhysics offset
assert(sqrt(a*a+b*b)!=0);
time -= forw_offset / sqrt(a*a+b*b);
time -= forw_offset / item_XZ_speed;
float aimX = time*target_x_speed + relative_target_kart_loc.getX();
float aimZ = time*target_z_speed + relative_target_kart_loc.getZ();
assert(time!=0);
*fire_angle = fire_th;
*up_velocity = (0.5f * time * gravity) + (dy / time)
+ (gy * target_kart->getSpeed());
float angle = atan2f(aimX, aimZ);
*fire_angle = angle;
// Now find the up_velocity. This is an application of newton's equation.
*up_velocity = (0.5f * time * gravity) + (relative_target_kart_loc.getY() / time)
+ ( target_y_speed);
} // getLinearKartItemIntersection
//-----------------------------------------------------------------------------
@@ -396,15 +391,32 @@ bool Flyable::updateAndDelete(float dt)
return true;
}
// Add the position offset so that the flyable can adjust its position
// (usually to do the raycast from a slightly higher position to avoid
// problems finding the terrain in steep uphill sections).
if(m_do_terrain_info)
TerrainInfo::update(xyz+m_position_offset);
if (m_do_terrain_info)
{
Vec3 towards = getBody()->getGravity();
towards.normalize();
// Add the position offset so that the flyable can adjust its position
// (usually to do the raycast from a slightly higher position to avoid
// problems finding the terrain in steep uphill sections).
// Towards is a unit vector. so we can multiply -towards to offset the
// position by one unit.
TerrainInfo::update(xyz + m_position_offset*(-towards), towards);
// Make flyable anti-gravity when the it's projected on such surface
const Material* m = TerrainInfo::getMaterial();
if (m && m->hasGravity())
{
getBody()->setGravity(TerrainInfo::getNormal() * -70.0f);
}
else
{
getBody()->setGravity(Vec3(0, 1, 0) * -70.0f);
}
}
if(m_adjust_up_velocity)
{
float hat = xyz.getY()-getHoT();
float hat = (xyz - getHitPoint()).length();
// Use the Height Above Terrain to set the Z velocity.
// HAT is clamped by min/max height. This might be somewhat

View File

@@ -149,7 +149,7 @@ protected:
const Vec3 &velocity,
btCollisionShape *shape,
float restitution,
const float gravity=0.0f,
const btVector3& gravity=btVector3(0.0f,0.0f,0.0f),
const bool rotates=false,
const bool turn_around=false,
const btTransform* customDirection=NULL);

View File

@@ -25,6 +25,9 @@
#include "modes/easter_egg_hunt.hpp"
#include "modes/three_strikes_battle.hpp"
#include "modes/world.hpp"
#include "tracks/arena_graph.hpp"
#include "tracks/drive_graph.hpp"
#include "tracks/drive_node.hpp"
#include "tracks/track.hpp"
#include "utils/constants.hpp"
#include "utils/vec3.hpp"
@@ -37,10 +40,14 @@ Item::Item(ItemType type, const Vec3& xyz, const Vec3& normal,
{
assert(type != ITEM_TRIGGER); // use other constructor for that
m_distance_2 = 0.8f;
m_distance_2 = 1.2f;
initItem(type, xyz);
// Sets heading to 0, and sets pitch and roll depending on the normal. */
m_original_hpr = Vec3(0, normal);
Vec3 axis = -normal.cross(Vec3(0, 1, 0));
if (axis.length() == 0)
axis = Vec3(0, 0, 1);
m_original_rotation = btQuaternion(axis, normal.angle(Vec3(0, 1, 0)));
m_rotation_angle = 0.0f;
m_original_mesh = mesh;
m_original_lowmesh = lowres_mesh;
m_listener = NULL;
@@ -76,7 +83,9 @@ Item::Item(ItemType type, const Vec3& xyz, const Vec3& normal,
World::getWorld()->getTrack()->adjustForFog(m_node);
m_node->setAutomaticCulling(scene::EAC_FRUSTUM_BOX);
m_node->setPosition(xyz.toIrrVector());
m_node->setRotation(m_original_hpr.toIrrHPR());
Vec3 hpr;
hpr.setHPR(m_original_rotation);
m_node->setRotation(hpr.toIrrHPR());
m_node->grab();
} // Item(type, xyz, normal, mesh, lowres_mesh)
@@ -90,8 +99,8 @@ Item::Item(const Vec3& xyz, float distance, TriggerItemListener* trigger)
{
m_distance_2 = distance*distance;
initItem(ITEM_TRIGGER, xyz);
// Sets heading to 0, and sets pitch and roll depending on the normal. */
m_original_hpr = Vec3(0, 0, 0);
m_original_rotation = btQuaternion(0, 0, 0, 1);
m_rotation_angle = 0.0f;
m_original_mesh = NULL;
m_original_lowmesh = NULL;
m_node = NULL;
@@ -126,31 +135,25 @@ void Item::initItem(ItemType type, const Vec3 &xyz)
}
// Now determine in which quad this item is, and its distance
// from the center within this quad.
m_graph_node = QuadGraph::UNKNOWN_SECTOR;
QuadGraph* currentQuadGraph = QuadGraph::get();
m_graph_node = Graph::UNKNOWN_SECTOR;
m_distance_from_center = 9999.9f;
m_avoidance_points[0] = NULL;
m_avoidance_points[1] = NULL;
// Check that QuadGraph exist (it might not in battle mode for eg)
if (currentQuadGraph != NULL)
// Check that Graph exist (it might not in battle mode without navmesh)
if (Graph::get())
{
QuadGraph::get()->findRoadSector(xyz, &m_graph_node);
Graph::get()->findRoadSector(xyz, &m_graph_node);
}
if(m_graph_node==QuadGraph::UNKNOWN_SECTOR)
if (DriveGraph::get() && m_graph_node != Graph::UNKNOWN_SECTOR)
{
m_graph_node = -1;
m_distance_from_center = 9999.9f; // is not used
m_avoidance_points[0] = NULL;
m_avoidance_points[1] = NULL;
}
else
{
// Item is on quad graph. Pre-compute the distance from center
// Item is on drive graph. Pre-compute the distance from center
// of this item, which is used by the AI (mostly for avoiding items)
Vec3 distances;
QuadGraph::get()->spatialToTrack(&distances, m_xyz, m_graph_node);
DriveGraph::get()->spatialToTrack(&distances, m_xyz, m_graph_node);
m_distance_from_center = distances.getX();
const GraphNode &gn = QuadGraph::get()->getNode(m_graph_node);
const Vec3 right = gn.getRightUnitVector();
const DriveNode* dn = DriveGraph::get()->getNode(m_graph_node);
const Vec3& right = dn->getRightUnitVector();
// Give it 10% more space, since the kart will not always come
// parallel to the drive line.
Vec3 delta = right * sqrt(m_distance_2) * 1.3f;
@@ -223,7 +226,9 @@ void Item::switchBack()
}
World::getWorld()->getTrack()->adjustForFog(m_node);
m_node->setRotation(m_original_hpr.toIrrHPR());
Vec3 hpr;
hpr.setHPR(m_original_rotation);
m_node->setRotation(hpr.toIrrHPR());
} // switchBack
//-----------------------------------------------------------------------------
@@ -320,12 +325,17 @@ void Item::update(float dt)
if(!m_rotate || m_node == NULL) return;
// have it rotate
Vec3 rotation(0, dt*M_PI, 0);
core::vector3df r = m_node->getRotation();
r.Y += dt*180.0f;
if(r.Y>360.0f) r.Y -= 360.0f;
m_rotation_angle += dt * M_PI ;
if (m_rotation_angle > M_PI * 2) m_rotation_angle -= M_PI * 2;
m_node->setRotation(r);
btMatrix3x3 m;
m.setRotation(m_original_rotation);
btQuaternion r = btQuaternion(m.getColumn(1), m_rotation_angle) *
m_original_rotation;
Vec3 hpr;
hpr.setHPR(r);
m_node->setRotation(hpr.toIrrHPR());
return;
} // not m_collected
} // update

View File

@@ -102,7 +102,10 @@ private:
* (bubble gums don't rotate, but it will be replaced with
* a nitro which rotates, and so overwrites the original
* rotation). */
Vec3 m_original_hpr;
btQuaternion m_original_rotation;
/** Used when rotating the item */
float m_rotation_angle;
/** True if item was collected & is not displayed. */
bool m_collected;
@@ -181,7 +184,6 @@ public:
const AbstractKart* getEmitter() const { return m_emitter; }
// ------------------------------------------------------------------------
/** Returns true if the Kart is close enough to hit this item, the item is
* not deactivated anymore, and it wasn't placed by this kart (this is
@@ -190,31 +192,12 @@ public:
* \param xyz Location of kart (avoiding to use kart->getXYZ() so that
* kart.hpp does not need to be included here).
*/
bool hitKart (const Vec3 &xyz, const AbstractKart *kart=NULL) const
bool hitKart(const Vec3 &xyz, const AbstractKart *kart=NULL) const
{
return (m_event_handler!=kart || m_deactive_time <=0) &&
(xyz-m_xyz).length2()<m_distance_2;
} // hitKart
private:
// ------------------------------------------------------------------------
/** Returns true if the Kart is close enough to hit this item, the item is
* not deactivated anymore, and it wasn't placed by this kart (this is
* e.g. used to avoid that a kart hits a bubble gum it just dropped).
* This function only uses the 2d coordinates, and it used by the AI only.
* \param kart Kart to test.
* \param xyz Location of kart (avoiding to use kart->getXYZ() so that
* kart.hpp does not need to be included here).
*/
bool hitKart (const core::vector2df &xyz,
const AbstractKart *kart=NULL) const
{
if(m_event_handler==kart && m_deactive_time >0) return false;
float d2 = (m_xyz.getX()-xyz.X)*(m_xyz.getX()-xyz.X)
+ (m_xyz.getZ()-xyz.Y)*(m_xyz.getZ()-xyz.Y);
return d2 < m_distance_2;
} // hitKart
protected:
// ------------------------------------------------------------------------
// Some convenient functions for the AI only
@@ -225,12 +208,12 @@ protected:
* \param line The line segment which is tested if it is close enough
* to this item so that this item would be collected.
*/
bool hitLine(const core::line2df &line,
bool hitLine(const core::line3df &line,
const AbstractKart *kart=NULL) const
{
if(m_event_handler==kart && m_deactive_time >0) return false;
core::vector2df p2d = m_xyz.toIrrVector2d();
core::vector2df closest = line.getClosestPoint(p2d);
Vec3 closest = line.getClosestPoint(m_xyz.toIrrVector());
return hitKart(closest, kart);
} // hitLine

View File

@@ -32,8 +32,9 @@
#include "modes/linear_world.hpp"
#include "network/network_config.hpp"
#include "network/race_event_manager.hpp"
#include "tracks/quad_graph.hpp"
#include "tracks/battle_graph.hpp"
#include "physics/triangle_mesh.hpp"
#include "tracks/arena_graph.hpp"
#include "tracks/arena_node.hpp"
#include "tracks/track.hpp"
#include "utils/string_utils.hpp"
@@ -157,12 +158,12 @@ ItemManager::ItemManager()
m_switch_to.push_back((Item::ItemType)i);
setSwitchItems(stk_config->m_switch_items);
if(QuadGraph::get())
if(Graph::get())
{
m_items_in_quads = new std::vector<AllItemTypes>;
// Entries 0 to n-1 are for the quads, entry
// n is for all items that are not on a quad.
m_items_in_quads->resize(QuadSet::get()->getNumberOfQuads()+1);
m_items_in_quads->resize(Graph::get()->getNumNodes()+1);
}
else
{
@@ -224,11 +225,10 @@ void ItemManager::insertItem(Item *item)
if(m_items_in_quads)
{
int graph_node = item->getGraphNode();
// If the item is on the driveline, store it at the appropriate index
// If the item is on the graph, store it at the appropriate index
if(graph_node > -1)
{
int sector = QuadGraph::get()->getNode(graph_node).getQuadIndex();
(*m_items_in_quads)[sector].push_back(item);
(*m_items_in_quads)[graph_node].push_back(item);
}
else // otherwise store it in the 'outside' index
(*m_items_in_quads)[m_items_in_quads->size()-1].push_back(item);
@@ -425,10 +425,8 @@ void ItemManager::deleteItem(Item *item)
// First check if the item needs to be removed from the items-in-quad list
if(m_items_in_quads)
{
const Vec3 &xyz = item->getXYZ();
int sector = QuadGraph::UNKNOWN_SECTOR;
QuadGraph::get()->findRoadSector(xyz, &sector);
unsigned int indx = sector==QuadGraph::UNKNOWN_SECTOR
int sector = item->getGraphNode();
unsigned int indx = sector==Graph::UNKNOWN_SECTOR
? (unsigned int) m_items_in_quads->size()-1
: sector;
AllItemTypes &items = (*m_items_in_quads)[indx];
@@ -480,40 +478,41 @@ void ItemManager::switchItems()
bool ItemManager::randomItemsForArena(const AlignedArray<btTransform>& pos)
{
if (!UserConfigParams::m_random_arena_item) return false;
if (!BattleGraph::get()) return false;
if (!ArenaGraph::get()) return false;
const ArenaGraph* ag = ArenaGraph::get();
std::vector<int> used_location;
std::vector<int> invalid_location;
for (unsigned int i = 0; i < pos.size(); i++)
{
// Load all starting positions of arena, so no items will be near them
int node = BattleGraph::get()->pointToNode(/*cur_node*/-1,
Vec3(pos[i].getOrigin()), /*ignore_vertical*/true);
int node = -1;
ag->findRoadSector(pos[i].getOrigin(), &node, NULL, true);
assert(node != -1);
used_location.push_back(node);
invalid_location.push_back(node);
}
RandomGenerator random;
const unsigned int MIN_DIST = int(sqrt(BattleGraph::get()->getNumNodes()));
const unsigned int ALL_NODES = ag->getNumNodes();
const unsigned int MIN_DIST = int(sqrt(ALL_NODES));
const unsigned int TOTAL_ITEM = MIN_DIST / 2;
Log::info("[ItemManager]","Creating %d random items for arena", TOTAL_ITEM);
for (unsigned int i = 0; i < TOTAL_ITEM; i++)
{
int chosen_node = -1;
const unsigned int total_node = BattleGraph::get()->getNumNodes();
while(true)
{
if (used_location.size() - pos.size() +
invalid_location.size() == total_node)
invalid_location.size() == ALL_NODES)
{
Log::warn("[ItemManager]","Can't place more random items! "
"Use default item location.");
return false;
}
const int node = random.get(total_node);
const int node = random.get(ALL_NODES);
// Check if tried
std::vector<int>::iterator it = std::find(invalid_location.begin(),
@@ -522,7 +521,7 @@ bool ItemManager::randomItemsForArena(const AlignedArray<btTransform>& pos)
continue;
// Check if near edge
if (BattleGraph::get()->isNearEdge(node))
if (ag->getNode(node)->isNearEdge())
{
invalid_location.push_back(node);
continue;
@@ -532,8 +531,7 @@ bool ItemManager::randomItemsForArena(const AlignedArray<btTransform>& pos)
for (unsigned int j = 0; j < used_location.size(); j++)
{
if (!found) continue;
float test_distance = BattleGraph::get()
->getDistance(used_location[j], node);
float test_distance = ag->getDistance(used_location[j], node);
found = test_distance > MIN_DIST;
}
if (found)
@@ -566,10 +564,31 @@ bool ItemManager::randomItemsForArena(const AlignedArray<btTransform>& pos)
Item::ItemType type = (j > BONUS_BOX ? Item::ITEM_BONUS_BOX :
j > NITRO_BIG ? Item::ITEM_NITRO_BIG :
j > NITRO_SMALL ? Item::ITEM_NITRO_SMALL : Item::ITEM_BANANA);
Vec3 loc = BattleGraph::get()
->getQuadOfNode(used_location[i]).getCenter();
Item* item = newItem(type, loc, Vec3(0, 1, 0));
BattleGraph::get()->insertItems(item, used_location[i]);
ArenaNode* an = ag->getNode(used_location[i]);
Vec3 loc = an->getCenter();
Vec3 quad_normal = an->getNormal();
loc += quad_normal;
// Do a raycast to help place it fully on the surface
const Material* m;
Vec3 normal;
Vec3 hit_point;
const TriangleMesh& tm =
World::getWorld()->getTrack()->getTriangleMesh();
bool success = tm.castRay(loc, an->getCenter() + (-10000*quad_normal),
&hit_point, &m, &normal);
if (success)
{
newItem(type, hit_point, normal);
}
else
{
Log::warn("[ItemManager]","Raycast to surface failed"
"from node %d", used_location[i]);
newItem(type, an->getCenter(), quad_normal);
}
}
return true;

View File

@@ -82,7 +82,7 @@ private:
/** Stores which items are on which quad. m_items_in_quads[#quads]
* contains all items that are not on a quad. Note that this
* field is undefined if no QuadGraph exist, e.g. in battle mode. */
* field is undefined if no Graph exist, e.g. arena without navmesh. */
std::vector< AllItemTypes > *m_items_in_quads;
/** What item this item is switched to. */
@@ -132,6 +132,16 @@ public:
assert(n<(*m_items_in_quads).size());
return (*m_items_in_quads)[n];
} // getItemsInQuads
// ------------------------------------------------------------------------
/** Returns the first item (NULL if none) on the specified quad
*/
Item* getFirstItemInQuad(unsigned int n) const
{
assert(m_items_in_quads);
assert(n < m_items_in_quads->size());
return ((*m_items_in_quads)[n]).empty() ? NULL :
(*m_items_in_quads)[n].front();
} // getFirstItemInQuad
}; // ItemManager
#endif

View File

@@ -41,6 +41,8 @@ Plunger::Plunger(AbstractKart *kart)
{
const float gravity = 0.0f;
setDoTerrainInfo(false);
float forward_offset = 0.5f*kart->getKartLength()+0.5f*m_extend.getZ();
float up_velocity = 0.0f;
float plunger_speed = 2 * m_speed;
@@ -56,7 +58,6 @@ Plunger::Plunger(AbstractKart *kart)
kart /* search in front of this kart */, m_reverse_mode);
btTransform kart_transform = kart->getAlignedTransform();
btMatrix3x3 kart_rotation = kart_transform.getBasis();
float heading =kart->getHeading();
float pitch = kart->getTerrainPitch(heading);
@@ -70,21 +71,22 @@ Plunger::Plunger(AbstractKart *kart)
&fire_angle, &up_velocity);
btTransform trans = kart->getTrans();
trans.setRotation(btQuaternion(btVector3(0, 1, 0), fire_angle));
btQuaternion q;
q = trans.getRotation()*(btQuaternion(btVector3(0, 1, 0), fire_angle));
trans.setRotation(q);
m_initial_velocity = btVector3(0.0f, up_velocity, plunger_speed);
createPhysics(forward_offset, m_initial_velocity,
new btCylinderShape(0.5f*m_extend),
0.5f /* restitution */ , gravity,
0.5f /* restitution */ , btVector3(.0f,gravity,.0f),
/* rotates */false , /*turn around*/false, &trans);
}
else
{
createPhysics(forward_offset, btVector3(pitch, 0.0f, plunger_speed),
new btCylinderShape(0.5f*m_extend),
0.5f /* restitution */, gravity,
0.5f /* restitution */, btVector3(.0f,gravity,.0f),
false /* rotates */, m_reverse_mode, &kart_transform);
}

View File

@@ -278,9 +278,9 @@ void Powerup::use()
Vec3 normal;
const Material* material_hit;
Vec3 pos = m_kart->getXYZ();
Vec3 to=pos+Vec3(0, -10000, 0);
Vec3 to = pos+ m_kart->getTrans().getBasis() * Vec3(0, -10000, 0);
world->getTrack()->getTriangleMesh().castRay(pos, to, &hit_point,
&material_hit, &normal);
&material_hit, &normal);
// This can happen if the kart is 'over nothing' when dropping
// the bubble gum
if(!material_hit)
@@ -290,8 +290,7 @@ void Powerup::use()
Powerup::adjustSound();
m_sound_use->play();
pos.setY(hit_point.getY()-0.05f);
pos = hit_point + m_kart->getTrans().getBasis() * Vec3(0, -0.05f, 0);
ItemManager::get()->newItem(Item::ITEM_BUBBLEGUM, pos, normal, m_kart);
}
else // if the kart is looking forward, use the bubblegum as a shield

View File

@@ -29,6 +29,8 @@
#include "modes/linear_world.hpp"
#include "physics/btKart.hpp"
#include "physics/triangle_mesh.hpp"
#include "tracks/drive_graph.hpp"
#include "tracks/drive_node.hpp"
#include "tracks/track.hpp"
#include "utils/log.hpp" //TODO: remove after debugging is done
@@ -66,8 +68,8 @@ RubberBall::RubberBall(AbstractKart *kart)
float forw_offset = 0.5f*kart->getKartLength() + m_extend.getZ()*0.5f+5.0f;
createPhysics(forw_offset, btVector3(0.0f, 0.0f, m_speed*2),
new btSphereShape(0.5f*m_extend.getY()),
-70.0f /*gravity*/,
new btSphereShape(0.5f*m_extend.getY()), -70.0f,
btVector3(.0f,.0f,.0f) /*gravity*/,
true /*rotates*/);
// Do not adjust the up velocity
@@ -97,7 +99,9 @@ RubberBall::RubberBall(AbstractKart *kart)
// initialises the current graph node
TrackSector::update(getXYZ());
TerrainInfo::update(getXYZ());
const Vec3& normal =
DriveGraph::get()->getNode(getCurrentGraphNode())->getNormal();
TerrainInfo::update(getXYZ(), -normal);
initializeControlPoints(m_owner->getXYZ());
} // RubberBall
@@ -132,7 +136,7 @@ void RubberBall::initializeControlPoints(const Vec3 &xyz)
// left or right when firing the ball off track.
getNextControlPoint();
m_control_points[2] =
QuadGraph::get()->getQuadOfNode(m_last_aimed_graph_node).getCenter();
DriveGraph::get()->getNode(m_last_aimed_graph_node)->getCenter();
// This updates m_last_aimed_graph_node, and sets m_control_points[3]
getNextControlPoint();
@@ -197,12 +201,12 @@ unsigned int RubberBall::getSuccessorToHitTarget(unsigned int node_index,
unsigned int sect =
lin_world->getSectorForKart(m_target);
succ = QuadGraph::get()->getNode(node_index).getSuccessorToReach(sect);
succ = DriveGraph::get()->getNode(node_index)->getSuccessorToReach(sect);
if(dist)
*dist += QuadGraph::get()->getNode(node_index)
.getDistanceToSuccessor(succ);
return QuadGraph::get()->getNode(node_index).getSuccessor(succ);
*dist += DriveGraph::get()->getNode(node_index)
->getDistanceToSuccessor(succ);
return DriveGraph::get()->getNode(node_index)->getSuccessor(succ);
} // getSuccessorToHitTarget
// ----------------------------------------------------------------------------
@@ -220,21 +224,21 @@ void RubberBall::getNextControlPoint()
// spline between the control points.
float dist=0;
float f = QuadGraph::get()->getDistanceFromStart(m_last_aimed_graph_node);
float f = DriveGraph::get()->getDistanceFromStart(m_last_aimed_graph_node);
int next = getSuccessorToHitTarget(m_last_aimed_graph_node, &dist);
float d = QuadGraph::get()->getDistanceFromStart(next)-f;
float d = DriveGraph::get()->getDistanceFromStart(next)-f;
while(d<m_st_min_interpolation_distance && d>=0)
{
next = getSuccessorToHitTarget(next, &dist);
d = QuadGraph::get()->getDistanceFromStart(next)-f;
d = DriveGraph::get()->getDistanceFromStart(next)-f;
}
m_last_aimed_graph_node = next;
m_length_cp_2_3 = dist;
const Quad &quad =
QuadGraph::get()->getQuadOfNode(m_last_aimed_graph_node);
m_control_points[3] = quad.getCenter();
const DriveNode* dn =
DriveGraph::get()->getNode(m_last_aimed_graph_node);
m_control_points[3] = dn->getCenter();
} // getNextControlPoint
// ----------------------------------------------------------------------------
@@ -342,39 +346,42 @@ bool RubberBall::updateAndDelete(float dt)
bool close_to_ground = 2.0*m_previous_height < m_current_max_height;
float vertical_offset = close_to_ground ? 4.0f : 2.0f;
// Note that at this stage getHoT still reports the height at
// the previous location (since TerrainInfo wasn't updated). On
// the other hand, we can't update TerrainInfo without having
// at least a good estimation of the height.
next_xyz.setY(getHoT() + vertical_offset);
// Update height of terrain (which isn't done as part of
// Flyable::update for rubber balls.
TerrainInfo::update(next_xyz);
TerrainInfo::update(next_xyz + getNormal()*vertical_offset, -getNormal());
m_height_timer += dt;
float height = updateHeight()+m_extend.getY()*0.5f;
float new_y = getHoT()+height;
if(UserConfigParams::logFlyable())
Log::debug("[RubberBall]", "ball %d: %f %f %f height %f new_y %f gethot %f ",
m_id, next_xyz.getX(), next_xyz.getY(), next_xyz.getZ(), height, new_y, getHoT());
Log::debug("[RubberBall]", "ball %d: %f %f %f height %f gethot %f ",
m_id, next_xyz.getX(), next_xyz.getY(), next_xyz.getZ(), height, getHoT());
// No need to check for terrain height if the ball is low to the ground
if(height > 0.5f)
{
float terrain_height = getMaxTerrainHeight(vertical_offset)
float tunnel_height = getTunnelHeight(next_xyz, vertical_offset)
- m_extend.getY();
if(new_y>terrain_height)
new_y = terrain_height;
// If the current height of ball (above terrain) is higher than the
// tunnel height then set adjust max height and compute new height again.
// Else reset the max height.
if (height > tunnel_height)
{
m_max_height = tunnel_height;
height = updateHeight();
}
else
m_max_height = m_st_max_height[m_type];
}
if(UserConfigParams::logFlyable())
Log::verbose("RubberBall", "newy2 %f gmth %f", new_y,
getMaxTerrainHeight(vertical_offset));
Log::verbose("RubberBall", "newy2 %f gmth %f", height,
getTunnelHeight(next_xyz,vertical_offset));
next_xyz.setY(new_y);
next_xyz = next_xyz + getNormal()*(height);
m_previous_xyz = getXYZ();
m_previous_height = next_xyz.getY()-getHoT();
m_previous_height = (getXYZ() - getHitPoint()).length();
setXYZ(next_xyz);
if(checkTunneling())
@@ -405,39 +412,33 @@ void RubberBall::moveTowardsTarget(Vec3 *next_xyz, float dt)
// If the rubber ball is already close to a target, i.e. aiming
// at it directly, stop interpolating, instead fly straight
// towards it.
Vec3 diff = m_target->getXYZ()-getXYZ();
Vec3 diff = m_target->getXYZ() - getXYZ();
diff = diff - diff.dot(getNormal())*getNormal();
// Avoid potential division by zero
if(diff.length2()==0)
*next_xyz = getXYZ();
*next_xyz = getXYZ() - getNormal()*m_previous_height;
else
*next_xyz = getXYZ() + (dt*m_speed/diff.length())*diff;
*next_xyz = getXYZ() - getNormal()*m_previous_height +(dt*m_speed / diff.length())*diff;
Vec3 old_vec = getXYZ()-m_previous_xyz;
Vec3 new_vec = *next_xyz - getXYZ();
float angle = atan2(new_vec.getZ(), new_vec.getX())
- atan2(old_vec.getZ(), old_vec.getX());
//float angle = atan2(new_vec.getZ(), new_vec.getX())
// - atan2(old_vec.getZ(), old_vec.getX());
float angle = new_vec.angle(old_vec);
// Adjust angle to be between -180 and 180 degrees
if(angle < -M_PI)
angle += 2*M_PI;
else if(angle > M_PI)
angle -= 2*M_PI;
// If the angle is too large, adjust next xyz
if(fabsf(angle)>m_st_target_max_angle*dt)
{
core::vector2df old_2d(old_vec.getX(), old_vec.getZ());
if(old_2d.getLengthSQ()==0.0f) old_2d.Y = 1.0f;
old_2d.normalize();
old_2d.rotateBy( RAD_TO_DEGREE * dt
* (angle > 0 ? m_st_target_max_angle
: -m_st_target_max_angle));
next_xyz->setX(getXYZ().getX() + old_2d.X*dt*m_speed);
next_xyz->setZ(getXYZ().getZ() + old_2d.Y*dt*m_speed);
} // if fabsf(angle) > m_st_target_angle_max*dt
// If ball is close to the target, then explode
if (diff.length() < m_target->getKartLength())
hit((AbstractKart*)m_target);
assert(!std::isnan((*next_xyz)[0]));
assert(!std::isnan((*next_xyz)[1]));
assert(!std::isnan((*next_xyz)[2]));
} // moveTowardsTarget
// ----------------------------------------------------------------------------
@@ -585,29 +586,26 @@ float RubberBall::updateHeight()
} // updateHeight
// ----------------------------------------------------------------------------
/** Returns the maximum height of the terrain at the current point. While
* generall the height is arbitrary (a skybox is not part of the physics and
* will therefore not be detected), it is important that a rubber ball does
* not end up on top of a tunnel.
/** When the ball is in a tunnel, this will return the tunnel height.
* NOTE: When this function is called next_xyz is usually the interpolated point
* on the track and not the ball's current location. Look at updateAndDelete().
*
* \param vertical_offset A vertical offset which is added to the current
* position of the kart in order to avoid tunneling effects (it could
* happen that the raycast down find the track since it uses the
* vertical offset, while the raycast up would hit under the track
* if the vertical offset is not used).
* \returns The height (Y coordinate) of the next terrain element found by
* a raycast up. If no terrain is found, it returns 99990
* position in order to avoid hitting the track when doing a raycast up.
* \returns The distance to the terrain element found by raycast in the up
direction. If no terrain found, it returns 99990
*/
float RubberBall::getMaxTerrainHeight(const Vec3 &vertical_offset) const
float RubberBall::getTunnelHeight(const Vec3 &next_xyz, const float vertical_offset) const
{
const TriangleMesh &tm = World::getWorld()->getTrack()->getTriangleMesh();
Vec3 to(getXYZ());
to.setY(10000.0f);
Vec3 from(next_xyz + vertical_offset*getNormal());
Vec3 to(next_xyz + 10000.0f*getNormal());
Vec3 hit_point;
const Material *material;
tm.castRay(getXYZ()+vertical_offset, to, &hit_point, &material);
tm.castRay(from, to, &hit_point, &material);
return (material) ? hit_point.getY() : 99999.f;
} // getMaxTerrainHeight
return (material) ? (hit_point - next_xyz).length() : 99999.f;
} // getTunnelHeight
// ----------------------------------------------------------------------------
/** Determines the distance to the target kart. If the target is close, the
@@ -633,7 +631,7 @@ void RubberBall::updateDistanceToTarget()
m_target->getXYZ().getZ(),m_distance_to_target
);
float height_diff = fabsf(m_target->getXYZ().getY() - getXYZ().getY());
float height_diff = fabsf((m_target->getXYZ() - getXYZ()).dot(getNormal().normalized()));
if(m_distance_to_target < m_st_fast_ping_distance &&
height_diff < m_st_max_height_difference)

View File

@@ -25,7 +25,6 @@
#include "tracks/track_sector.hpp"
class AbstractKart;
class QuadGraph;
class SFXBase;
/**
@@ -196,7 +195,8 @@ private:
void interpolate(Vec3 *next_xyz, float dt);
void moveTowardsTarget(Vec3 *next_xyz, float dt);
void initializeControlPoints(const Vec3 &xyz);
float getMaxTerrainHeight(const Vec3 &vertical_offset) const;
float getTunnelHeight(const Vec3 &next_xyz,
const float vertical_offset) const;
bool checkTunneling();
public:
RubberBall (AbstractKart* kart);

View File

@@ -280,13 +280,11 @@ void Swatter::pointToTarget()
}
else
{
Vec3 swatter_to_target = m_target->getXYZ()
-Vec3(m_scene_node->getAbsolutePosition());
Vec3 swatter_to_target =
m_kart->getTrans().inverse()(m_target->getXYZ());
float dy = -swatter_to_target.getZ();
float dx = swatter_to_target.getX();
float angle = SWAT_ANGLE_OFFSET + (atan2(dy, dx)-m_kart->getHeading())
* 180.0f/M_PI;
float angle = SWAT_ANGLE_OFFSET + atan2f(dy, dx) * 180 / M_PI;
m_scene_node->setRotation(core::vector3df(0.0, angle, 0.0));
}
} // pointToTarget

View File

@@ -441,6 +441,10 @@ public:
// ------------------------------------------------------------------------
virtual void crashed(const Material *m, const Vec3 &normal) = 0;
// ------------------------------------------------------------------------
/** Returns the normal of the terrain the kart is over atm. This is
* defined even if the kart is flying. */
virtual const Vec3& getNormal() const = 0;
// ------------------------------------------------------------------------
/** Returns the height of the terrain. we're currently above */
virtual float getHoT() const = 0;
// ------------------------------------------------------------------------

View File

@@ -86,9 +86,7 @@ float AIBaseController::steerToPoint(const Vec3 &point)
// First translate and rotate the point the AI is aiming
// at into the kart's local coordinate system.
btQuaternion q(btVector3(0,1,0), -m_kart->getHeading());
Vec3 p = point - m_kart->getXYZ();
Vec3 lc = quatRotate(q, p);
Vec3 lc = m_kart->getTrans().inverse()(point);
// The point the kart is aiming at can be reached 'incorrectly' if the
// point is below the y=x line: Instead of aiming at that point directly
@@ -282,34 +280,50 @@ void AIBaseController::crashed(const Material *m)
} // crashed(Material)
//-----------------------------------------------------------------------------
void AIBaseController::checkPosition(const Vec3 &point, posData *pos_data,
Vec3 *lc, bool use_front_xyz) const
// ----------------------------------------------------------------------------
/** Determine the center point and radius of a circle given two points on
* the circle and the tangent at the first point. This is done as follows:
* 1. Determine the line going through the center point start+end, which is
* orthogonal to the vector from start to end. This line goes through the
* center of the circle.
* 2. Determine the line going through the first point and is orthogonal
* to the given tangent.
* 3. The intersection of these two lines is the center of the circle.
* \param[in] end Second point on circle.
* \param[out] center Center point of the circle (local coordinate).
* \param[out] radius Radius of the circle.
*/
void AIBaseController::determineTurnRadius(const Vec3 &end, Vec3 *center,
float *radius) const
{
// Convert to local coordinates from the point of view of current kart
btQuaternion q(btVector3(0, 1, 0), -m_kart->getHeading());
Vec3 p = point -
(use_front_xyz ? m_kart->getFrontXYZ() : m_kart->getXYZ());
Vec3 local_coordinates = quatRotate(q, p);
// Convert end point to local coordinate, so start will be 0, 0, 0
Vec3 lc = m_kart->getTrans().inverse()(end);
// Save local coordinates for later use if needed
if (lc) *lc = local_coordinates;
// 1) Line through middle of start+end
Vec3 mid = 0.5f * lc;
Vec3 direction = lc;
if (pos_data == NULL) return;
// lhs: tell whether it's left or right hand side
if (local_coordinates.getX() < 0)
pos_data->lhs = true;
Vec3 orthogonal(direction.getZ(), 0, -direction.getX());
Vec3 q1 = mid + orthogonal;
irr::core::line2df line1(mid.getX(), mid.getZ(),
q1.getX(), q1.getZ() );
irr::core::line2df line2(0, 0, 1, 0);
irr::core::vector2df result;
if (line1.intersectWith(line2, result, /*checkOnlySegments*/false))
{
Vec3 lc_center(result.X, 0, result.Y);
if (center)
*center = lc_center;
*radius = lc_center.length();
}
else
pos_data->lhs = false;
{
// No intersection. In this case assume that the two points are
// on a semicircle, in which case the center is at 0.5*(start+end):
if (center)
*center = mid;
*radius = 0.5f*(lc).length();
}
// behind: tell whether it's behind or not
if (local_coordinates.getZ() < 0)
pos_data->behind = true;
else
pos_data->behind = false;
pos_data->angle = atan2(fabsf(local_coordinates.getX()),
fabsf(local_coordinates.getZ()));
pos_data->distance = p.length();
} // checkPosition
} // determineTurnRadius

View File

@@ -64,22 +64,18 @@ protected:
* for AI testing only. */
static int m_test_ai;
/** Position info structure of targets. */
struct posData {bool behind; bool lhs; float angle; float distance;};
void setControllerName(const std::string &name);
float steerToPoint(const Vec3 &point);
float normalizeAngle(float angle);
virtual void update (float delta);
virtual void setSteering (float angle, float dt);
virtual bool canSkid(float steer_fraction) = 0;
// ------------------------------------------------------------------------
/** This can be called to detect if the kart is stuck (i.e. repeatedly
* hitting part of the track). */
bool isStuck() const { return m_stuck; }
void checkPosition(const Vec3&, posData*,
Vec3* lc = NULL,
bool use_front_xyz = false) const;
void determineTurnRadius(const Vec3 &end, Vec3 *center,
float *radius) const;
virtual void update (float delta);
virtual void setSteering (float angle, float dt);
virtual bool canSkid(float steer_fraction) = 0;
public:
AIBaseController(AbstractKart *kart);

View File

@@ -25,7 +25,7 @@
#include "karts/kart_properties.hpp"
#include "karts/controller/ai_properties.hpp"
#include "modes/linear_world.hpp"
#include "tracks/track.hpp"
#include "tracks/drive_graph.hpp"
#include "utils/constants.hpp"
@@ -37,7 +37,7 @@ I.e. the controller that takes over from a player (or AI) when the race is
finished.
This base class defines some basic operations:
- It takes care on which part of the QuadGraph the AI currently is.
- It takes care on which part of the DriveGraph the AI currently is.
- It determines which path the AI should take (in case of shortcuts
or forks in the road).
@@ -139,14 +139,14 @@ void AIBaseLapController::newLap(int lap)
*/
void AIBaseLapController::computePath()
{
m_next_node_index.resize(QuadGraph::get()->getNumNodes());
m_successor_index.resize(QuadGraph::get()->getNumNodes());
m_next_node_index.resize(DriveGraph::get()->getNumNodes());
m_successor_index.resize(DriveGraph::get()->getNumNodes());
std::vector<unsigned int> next;
for(unsigned int i=0; i<QuadGraph::get()->getNumNodes(); i++)
for(unsigned int i=0; i<DriveGraph::get()->getNumNodes(); i++)
{
next.clear();
// Get all successors the AI is allowed to take.
QuadGraph::get()->getSuccessors(i, next, /*for_ai*/true);
DriveGraph::get()->getSuccessors(i, next, /*for_ai*/true);
// In case of short cuts hidden for the AI it can be that a node
// might not have a successor (since the first and last edge of
// a hidden shortcut is ignored). Since in the case that the AI
@@ -154,7 +154,7 @@ void AIBaseLapController::computePath()
// allowed way to drive, it should still be able to drive, so add
// the non-AI successors of that node in this case.
if(next.size()==0)
QuadGraph::get()->getSuccessors(i, next, /*for_ai*/false);
DriveGraph::get()->getSuccessors(i, next, /*for_ai*/false);
// For now pick one part on random, which is not adjusted during the
// race. Long term statistics might be gathered to determine the
// best way, potentially depending on race position etc.
@@ -171,12 +171,12 @@ void AIBaseLapController::computePath()
// Now compute for each node in the graph the list of the next 'look_ahead'
// graph nodes. This is the list of node that is tested in checkCrashes.
// If the look_ahead is too big, the AI can skip loops (see
// QuadGraph::findRoadSector for details), if it's too short the AI won't
// Graph::findRoadSector for details), if it's too short the AI won't
// find too good a driveline. Note that in general this list should
// be computed recursively, but since the AI for now is using only
// (randomly picked) path this is fine
m_all_look_aheads.resize(QuadGraph::get()->getNumNodes());
for(unsigned int i=0; i<QuadGraph::get()->getNumNodes(); i++)
m_all_look_aheads.resize(DriveGraph::get()->getNumNodes());
for(unsigned int i=0; i<DriveGraph::get()->getNumNodes(); i++)
{
std::vector<int> l;
int current = i;
@@ -199,24 +199,24 @@ void AIBaseLapController::computePath()
void AIBaseLapController::update(float dt)
{
AIBaseController::update(dt);
if(QuadGraph::get())
if(DriveGraph::get())
{
// Update the current node:
int old_node = m_track_node;
if(m_track_node!=QuadGraph::UNKNOWN_SECTOR)
if(m_track_node!=Graph::UNKNOWN_SECTOR)
{
QuadGraph::get()->findRoadSector(m_kart->getXYZ(), &m_track_node,
DriveGraph::get()->findRoadSector(m_kart->getXYZ(), &m_track_node,
&m_all_look_aheads[m_track_node]);
}
// If we can't find a proper place on the track, to a broader search
// on off-track locations.
if(m_track_node==QuadGraph::UNKNOWN_SECTOR)
if(m_track_node==Graph::UNKNOWN_SECTOR)
{
m_track_node = QuadGraph::get()->findOutOfRoadSector(m_kart->getXYZ());
m_track_node = DriveGraph::get()->findOutOfRoadSector(m_kart->getXYZ());
}
// IF the AI is off track (or on a branch of the track it did not
// select to be on), keep the old position.
if(m_track_node==QuadGraph::UNKNOWN_SECTOR ||
if(m_track_node==Graph::UNKNOWN_SECTOR ||
m_next_node_index[m_track_node]==-1)
m_track_node = old_node;
}
@@ -233,7 +233,7 @@ void AIBaseLapController::update(float dt)
unsigned int AIBaseLapController::getNextSector(unsigned int index)
{
std::vector<unsigned int> successors;
QuadGraph::get()->getSuccessors(index, successors);
DriveGraph::get()->getSuccessors(index, successors);
return successors[0];
} // getNextSector
@@ -245,8 +245,8 @@ unsigned int AIBaseLapController::getNextSector(unsigned int index)
float AIBaseLapController::steerToAngle(const unsigned int sector,
const float add_angle)
{
float angle = QuadGraph::get()->getAngleToNext(sector,
getNextSector(sector));
float angle = DriveGraph::get()->getAngleToNext(sector,
getNextSector(sector));
//Desired angle minus current angle equals how many angles to turn
float steer_angle = angle - m_kart->getHeading();

View File

@@ -22,7 +22,6 @@
class AIProperties;
class LinearWorld;
class QuadGraph;
class Track;
class Vec3;

View File

@@ -23,27 +23,21 @@
#include "items/powerup.hpp"
#include "items/projectile_manager.hpp"
#include "karts/abstract_kart.hpp"
#include "karts/controller/player_controller.hpp"
#include "karts/controller/ai_properties.hpp"
#include "karts/kart_properties.hpp"
#include "tracks/battle_graph.hpp"
#include "tracks/quad.hpp"
#include "utils/log.hpp"
#include "karts/rescue_animation.hpp"
#include "tracks/arena_graph.hpp"
#include "tracks/arena_node.hpp"
int ArenaAI::m_test_node_for_banana = BattleGraph::UNKNOWN_POLY;
bool isNodeWithBanana(const std::pair<const Item*, int>& item_pair)
{
return item_pair.second == ArenaAI::m_test_node_for_banana &&
item_pair.first->getType() == Item::ITEM_BANANA &&
!item_pair.first->wasCollected();
}
#include <algorithm>
ArenaAI::ArenaAI(AbstractKart *kart)
: AIBaseController(kart)
{
m_debug_sphere = NULL;
m_debug_sphere_next = NULL;
m_graph = ArenaGraph::get();
assert(m_graph != NULL);
} // ArenaAI
//-----------------------------------------------------------------------------
@@ -51,28 +45,26 @@ ArenaAI::ArenaAI(AbstractKart *kart)
*/
void ArenaAI::reset()
{
m_target_node = BattleGraph::UNKNOWN_POLY;
m_current_forward_node = BattleGraph::UNKNOWN_POLY;
m_target_node = Graph::UNKNOWN_SECTOR;
m_current_forward_node = Graph::UNKNOWN_SECTOR;
m_current_forward_point = Vec3(0, 0, 0);
m_adjusting_side = false;
m_closest_kart = NULL;
m_closest_kart_node = BattleGraph::UNKNOWN_POLY;
m_closest_kart_node = Graph::UNKNOWN_SECTOR;
m_closest_kart_point = Vec3(0, 0, 0);
m_closest_kart_pos_data = {0};
m_cur_kart_pos_data = {0};
m_is_stuck = false;
m_is_uturn = false;
m_avoiding_banana = false;
m_mini_skid = false;
m_target_point = Vec3(0, 0, 0);
m_target_point_lc = Vec3(0, 0, 0);
m_time_since_last_shot = 0.0f;
m_time_since_driving = 0.0f;
m_time_since_off_road = 0.0f;
m_time_since_reversing = 0.0f;
m_time_since_uturn = 0.0f;
m_turn_radius = 0.0f;
m_turn_angle = 0.0f;
m_steering_angle = 0.0f;
m_on_node.clear();
m_aiming_points.clear();
m_aiming_nodes.clear();
m_cur_difficulty = race_manager->getDifficulty();
AIBaseController::reset();
@@ -88,7 +80,6 @@ void ArenaAI::update(float dt)
// This is used to enable firing an item backwards.
m_controls->setLookBack(false);
m_controls->setNitro(false);
m_avoiding_banana = false;
// Don't do anything if there is currently a kart animations shown.
if (m_kart->getKartAnimation())
@@ -97,6 +88,24 @@ void ArenaAI::update(float dt)
return;
}
if (!isKartOnRoad() && m_kart->isOnGround())
{
m_time_since_off_road += dt;
}
else if (m_time_since_off_road != 0.0f)
{
m_time_since_off_road = 0.0f;
}
// If the kart needs to be rescued, do it now (and nothing else)
if (m_time_since_off_road > 5.0f && m_kart->isOnGround())
{
m_time_since_off_road = 0.0f;
new RescueAnimation(m_kart);
AIBaseController::update(dt);
return;
}
if (isWaiting())
{
AIBaseController::update(dt);
@@ -104,14 +113,21 @@ void ArenaAI::update(float dt)
}
checkIfStuck(dt);
if (handleArenaUnstuck(dt))
if (gettingUnstuck(dt))
return;
findClosestKart(true);
findTarget();
handleArenaItems(dt);
if (m_kart->getSpeed() > 15.0f && m_turn_angle < 20)
// After found target, convert it to local coordinate, used for skidding or
// u-turn
m_target_point_lc = m_kart->getTrans().inverse()(m_target_point);
doSkiddingTest();
configSteering();
useItems(dt);
if (m_kart->getSpeed() > 15.0f && !m_is_uturn && m_turn_radius > 30.0f &&
!ignorePathFinding())
{
// Only use nitro when turn angle is big (180 - angle)
m_controls->setNitro(true);
@@ -120,13 +136,12 @@ void ArenaAI::update(float dt)
if (m_is_uturn)
{
resetAfterStop();
handleArenaUTurn(dt);
doUTurn(dt);
}
else
{
handleArenaAcceleration(dt);
handleArenaSteering(dt);
handleArenaBraking();
configSpeed();
setSteering(m_steering_angle, dt);
}
AIBaseController::update(dt);
@@ -134,127 +149,139 @@ void ArenaAI::update(float dt)
} // update
//-----------------------------------------------------------------------------
bool ArenaAI::updateAimingPosition()
/** Update aiming position, use path finding if necessary.
* \param[out] target_point Suitable target point.
* \return True if found a suitable target point.
*/
bool ArenaAI::updateAimingPosition(Vec3* target_point)
{
#ifdef AI_DEBUG
m_debug_sphere_next->setVisible(false);
#endif
// Notice: we use the point ahead of kart to determine next node,
// to compensate the time difference between steering
m_current_forward_point =
m_kart->getTrans()(Vec3(0, 0, m_kart->getKartLength()));
m_current_forward_node = BattleGraph::get()->pointToNode
(m_current_forward_node, m_current_forward_point,
false/*ignore_vertical*/);
m_turn_radius = 0.0f;
std::vector<int>* test_nodes = NULL;
if (m_current_forward_node != Graph::UNKNOWN_SECTOR)
{
test_nodes =
m_graph->getNode(m_current_forward_node)->getNearbyNodes();
}
m_graph->findRoadSector(m_current_forward_point, &m_current_forward_node,
test_nodes);
// Use current node if forward node is unknown, or near the target
const int forward = (m_current_forward_node == BattleGraph::UNKNOWN_POLY ||
const int forward =
m_current_forward_node == Graph::UNKNOWN_SECTOR ||
m_current_forward_node == m_target_node ||
getCurrentNode() == m_target_node ? getCurrentNode() :
m_current_forward_node);
getCurrentNode() == m_target_node ?
getCurrentNode() : m_current_forward_node;
if (forward == BattleGraph::UNKNOWN_POLY ||
m_target_node == BattleGraph::UNKNOWN_POLY)
if (forward == Graph::UNKNOWN_SECTOR ||
m_target_node == Graph::UNKNOWN_SECTOR)
{
Log::error("ArenaAI", "Next node is unknown, path finding failed!");
return false;
}
if (forward == m_target_node)
{
m_aiming_points.push_back(BattleGraph::get()
->getQuadOfNode(forward).getCenter());
m_aiming_points.push_back(m_target_point);
m_aiming_nodes.insert(forward);
m_aiming_nodes.insert(getCurrentNode());
determineTurnRadius(m_target_point, NULL, &m_turn_radius);
*target_point = m_target_point;
return true;
}
const int next_node = BattleGraph::get()
->getNextShortestPathPoly(forward, m_target_node);
if (next_node == BattleGraph::UNKNOWN_POLY)
std::vector<int> path;
int next_node = m_graph->getNextNode(forward, m_target_node);
if (next_node == Graph::UNKNOWN_SECTOR)
{
Log::error("ArenaAI", "Next node is unknown, did you forget to link"
"adjacent face in navmesh?");
" adjacent face in navmesh?");
return false;
}
m_aiming_points.push_back(BattleGraph::get()
->getQuadOfNode(forward).getCenter());
m_aiming_points.push_back(BattleGraph::get()
->getQuadOfNode(next_node).getCenter());
path.push_back(next_node);
while (m_target_node != next_node)
{
int previous_node = next_node;
next_node = m_graph->getNextNode(previous_node, m_target_node);
if (next_node == Graph::UNKNOWN_SECTOR)
{
Log::error("ArenaAI", "Next node is unknown, did you forget to"
" link adjacent face in navmesh?");
return false;
}
path.push_back(next_node);
}
m_aiming_nodes.insert(forward);
m_aiming_nodes.insert(next_node);
m_aiming_nodes.insert(getCurrentNode());
determinePath(forward, &path);
*target_point = m_graph->getNode(path.front())->getCenter();
return true;
} // updateAimingPosition
//-----------------------------------------------------------------------------
/** This function sets the steering.
* \param dt Time step size.
/** This function config the steering of AI.
*/
void ArenaAI::handleArenaSteering(const float dt)
void ArenaAI::configSteering()
{
m_steering_angle = 0.0f;
const int current_node = getCurrentNode();
if (current_node == BattleGraph::UNKNOWN_POLY ||
m_target_node == BattleGraph::UNKNOWN_POLY)
if (current_node == Graph::UNKNOWN_SECTOR ||
m_target_node == Graph::UNKNOWN_SECTOR)
{
return;
}
m_aiming_points.clear();
m_aiming_nodes.clear();
const bool found_position = updateAimingPosition();
if (ignorePathFinding())
{
// Steer directly
checkPosition(m_target_point, &m_cur_kart_pos_data);
// Steer directly, don't brake
m_turn_radius = 100.0f;
#ifdef AI_DEBUG
m_debug_sphere->setPosition(m_target_point.toIrrVector());
#endif
if (m_cur_kart_pos_data.behind)
if (m_target_point_lc.z() < 0)
{
m_adjusting_side = m_cur_kart_pos_data.lhs;
// Local coordinate z < 0 == target point is behind
m_adjusting_side = m_target_point_lc.x() < 0;
m_is_uturn = true;
}
else
{
float target_angle = steerToPoint(m_target_point);
setSteering(target_angle, dt);
m_steering_angle = steerToPoint(m_target_point);
}
return;
}
else if (found_position)
// Otherwise use path finding to get target point
Vec3 target_point;
const bool found_position = updateAimingPosition(&target_point);
if (found_position)
{
updateBananaLocation();
assert(m_aiming_points.size() == 2);
updateTurnRadius(m_kart->getXYZ(), m_aiming_points[0],
m_aiming_points[1]);
m_target_point = m_aiming_points[1];
checkPosition(m_target_point, &m_cur_kart_pos_data);
m_target_point = target_point;
m_target_point_lc = m_kart->getTrans().inverse()(m_target_point);
#ifdef AI_DEBUG
m_debug_sphere->setVisible(true);
m_debug_sphere_next->setVisible(true);
m_debug_sphere->setPosition(m_aiming_points[0].toIrrVector());
m_debug_sphere_next->setPosition(m_aiming_points[1].toIrrVector());
m_debug_sphere->setPosition(m_target_point.toIrrVector());
#endif
if (m_cur_kart_pos_data.behind)
if (m_target_point_lc.z() < 0)
{
m_adjusting_side = m_cur_kart_pos_data.lhs;
m_adjusting_side = m_target_point_lc.x() < 0;
m_is_uturn = true;
}
else
{
float target_angle = steerToPoint(m_target_point);
setSteering(target_angle, dt);
m_steering_angle = steerToPoint(m_target_point);
}
return;
}
else
{
// Do nothing (go straight) if no targets found
setSteering(0.0f, dt);
return;
}
} // handleSteering
} // configSteering
//-----------------------------------------------------------------------------
void ArenaAI::checkIfStuck(const float dt)
@@ -292,26 +319,37 @@ void ArenaAI::checkIfStuck(const float dt)
} // checkIfStuck
//-----------------------------------------------------------------------------
/** Handles acceleration.
* \param dt Time step size.
/** Configure a suitable speed depends on current turn radius.
*/
void ArenaAI::handleArenaAcceleration(const float dt)
void ArenaAI::configSpeed()
{
m_controls->setAccel(0.0f);
m_controls->setBrake(false);
if (m_controls->getBrake())
{
m_controls->setAccel(0.0f);
return;
}
const float handicap =
(m_cur_difficulty == RaceManager::DIFFICULTY_EASY ? 0.7f : 1.0f);
// A kart will not brake when the speed is already slower than this
// value. This prevents a kart from going too slow (or even backwards)
// in tight curves.
const float MIN_SPEED = 5.0f;
const float handicap = (m_cur_difficulty == RaceManager::DIFFICULTY_EASY
? 0.7f : 1.0f );
m_controls->setAccel(stk_config->m_ai_acceleration * handicap);
} // handleArenaAcceleration
const float max_turn_speed = m_kart->getSpeedForTurnRadius(m_turn_radius);
if ((m_kart->getSpeed() > max_turn_speed || forceBraking()) &&
m_kart->getSpeed() > MIN_SPEED * handicap)
{
// Brake if necessary
m_controls->setBrake(true);
}
else
{
// Otherwise accelerate
m_controls->setAccel(stk_config->m_ai_acceleration * handicap);
}
} // configSpeed
//-----------------------------------------------------------------------------
void ArenaAI::handleArenaUTurn(const float dt)
void ArenaAI::doUTurn(const float dt)
{
const float turn_side = (m_adjusting_side ? 1.0f : -1.0f);
@@ -329,19 +367,20 @@ void ArenaAI::handleArenaUTurn(const float dt)
setSteering(turn_side, dt);
m_time_since_uturn += dt;
checkPosition(m_target_point, &m_cur_kart_pos_data);
if (!m_cur_kart_pos_data.behind || m_time_since_uturn >
if (m_target_point_lc.z() > 0 || m_time_since_uturn >
(m_cur_difficulty == RaceManager::DIFFICULTY_EASY ? 3.5f : 3.0f))
{
// End U-turn until target point is in front of this AI
m_is_uturn = false;
m_time_since_uturn = 0.0f;
m_time_since_driving = 0.0f;
}
else
m_is_uturn = true;
} // handleArenaUTurn
} // doUTurn
//-----------------------------------------------------------------------------
bool ArenaAI::handleArenaUnstuck(const float dt)
bool ArenaAI::gettingUnstuck(const float dt)
{
if (!m_is_stuck || m_is_uturn) return false;
@@ -365,142 +404,10 @@ bool ArenaAI::handleArenaUnstuck(const float dt)
AIBaseController::update(dt);
return true;
} // handleArenaUnstuck
} // gettingUnstuck
//-----------------------------------------------------------------------------
void ArenaAI::updateBananaLocation()
{
std::vector<std::pair<const Item*, int>>& item_list =
BattleGraph::get()->getItemList();
std::set<int>::iterator node = m_aiming_nodes.begin();
while (node != m_aiming_nodes.end())
{
m_test_node_for_banana = *node;
std::vector<std::pair<const Item*, int>>::iterator it =
std::find_if(item_list.begin(), item_list.end(), isNodeWithBanana);
if (it != item_list.end())
{
Vec3 banana_lc;
checkPosition(it->first->getXYZ(), NULL, &banana_lc,
true/*use_front_xyz*/);
// If satisfy the below condition, AI should not eat banana:
// banana_lc.z() < 0.0f, behind the kart
if (banana_lc.z() < 0.0f)
{
node++;
continue;
}
// If the node AI will pass has a banana, adjust the aim position
banana_lc = (banana_lc.x() < 0 ? banana_lc + Vec3(5, 0, 0) :
banana_lc - Vec3(5, 0, 0));
m_aiming_points[1] = m_kart->getTrans()(banana_lc);
m_avoiding_banana = true;
// Handle one banana only
return;
}
node++;
}
} // updateBananaLocation
//-----------------------------------------------------------------------------
/** This function handles braking. It used the turn radius found by
* updateTurnRadius(). Depending on the turn radius, it finds out the maximum
* speed. If the current speed is greater than the max speed and a set minimum
* speed, brakes are applied.
*/
void ArenaAI::handleArenaBraking()
{
// A kart will not brake when the speed is already slower than this
// value. This prevents a kart from going too slow (or even backwards)
// in tight curves.
const float MIN_SPEED = 5.0f;
if (forceBraking() && m_kart->getSpeed() > MIN_SPEED)
{
// Brake now
m_controls->setBrake(true);
return;
}
m_controls->setBrake(false);
if (getCurrentNode() == BattleGraph::UNKNOWN_POLY ||
m_target_node == BattleGraph::UNKNOWN_POLY) return;
if (m_aiming_points.empty()) return;
const float max_turn_speed = m_kart->getSpeedForTurnRadius(m_turn_radius);
if (m_kart->getSpeed() > 1.25f * max_turn_speed &&
fabsf(m_controls->getSteer()) > 0.95f &&
m_kart->getSpeed() > MIN_SPEED)
{
m_controls->setBrake(true);
}
} // handleArenaBraking
//-----------------------------------------------------------------------------
void ArenaAI::updateTurnRadius(const Vec3& p1, const Vec3& p2,
const Vec3& p3)
{
// First use cosine formula to find out the angle made by the distance
// between kart (point one) to point two and point two between point three
const float a = (p1 - p2).length();
const float b = (p2 - p3).length();
const float c = (p1 - p3).length();
const float angle = 180 - findAngleFrom3Edges(a, b, c);
// Only calculate radius if not almost straight line
if (angle > 1 && angle < 179)
{
// angle
// ^
// a / \ b
// 90/\ /\90
// \ / \ /
// \ /
// \ /
// \ /
// |
// Try to estimate the turn radius with the help of a kite-like
// polygon as shown, find out the lowest angle which is
// (4 - 2) * 180 - 90 - 90 - angle (180 - angle from above)
// Then we use this value as the angle of a sector of circle,
// a + b as the arc length, then the radius can be calculated easily
m_turn_radius = ((a + b) / (angle / 360)) / M_PI / 2;
}
else
{
// Return large radius so no braking is needed otherwise
m_turn_radius = 45.0f;
}
m_turn_angle = angle;
} // updateTurnRadius
//-----------------------------------------------------------------------------
float ArenaAI::findAngleFrom3Edges(float a, float b, float c)
{
// Cosine forumla : c2 = a2 + b2 - 2ab cos C
float test_value = ((c * c) - (a * a) - (b * b)) / (-(2 * a * b));
// Prevent error
if (test_value < -1)
test_value = -1;
else if (test_value > 1)
test_value = 1;
return acosf(test_value) * RAD_TO_DEGREE;
} // findAngleFrom3Edges
//-----------------------------------------------------------------------------
void ArenaAI::handleArenaItems(const float dt)
void ArenaAI::useItems(const float dt)
{
m_controls->setFire(false);
if (m_kart->getKartAnimation() ||
@@ -509,18 +416,27 @@ void ArenaAI::handleArenaItems(const float dt)
// Find a closest kart again, this time we ignore difficulty
findClosestKart(false);
if (!m_closest_kart) return;
Vec3 closest_kart_point_lc =
m_kart->getTrans().inverse()(m_closest_kart_point);
m_time_since_last_shot += dt;
float min_bubble_time = 2.0f;
const bool difficulty = m_cur_difficulty == RaceManager::DIFFICULTY_EASY ||
m_cur_difficulty == RaceManager::DIFFICULTY_MEDIUM;
const bool fire_behind = m_closest_kart_pos_data.behind && !difficulty;
const bool fire_behind = closest_kart_point_lc.z() < 0 && !difficulty;
const bool perfect_aim = m_closest_kart_pos_data.angle < 0.2f;
const float abs_angle = atan2f(fabsf(closest_kart_point_lc.x()),
fabsf(closest_kart_point_lc.z()));
const bool perfect_aim = abs_angle < 0.2f;
// Compensate the distance because this distance is straight to straight
// in graph node, so if kart to kart are not facing like so as, their real
// distance maybe smaller
const float dist_to_kart = getKartDistance(m_closest_kart) * 0.8f;
switch(m_kart->getPowerup()->getType())
{
@@ -535,14 +451,11 @@ void ArenaAI::handleArenaItems(const float dt)
// has a swatter attachment. If so, use bubblegum
// as shield
if ( (!m_kart->isShielded() &&
projectile_manager->projectileIsClose(m_kart,
m_ai_properties->m_shield_incoming_radius)
) ||
(m_closest_kart_pos_data.distance < 15.0f &&
m_closest_kart->getAttachment()->getType() ==
Attachment::ATTACH_SWATTER
)
)
projectile_manager->projectileIsClose(m_kart,
m_ai_properties->m_shield_incoming_radius) ) ||
(dist_to_kart < 15.0f &&
(m_closest_kart->getAttachment()->
getType() == Attachment::ATTACH_SWATTER) ) )
{
m_controls->setFire(true);
m_controls->setLookBack(false);
@@ -552,11 +465,9 @@ void ArenaAI::handleArenaItems(const float dt)
// Avoid dropping all bubble gums one after another
if (m_time_since_last_shot < 3.0f) break;
// Use bubblegum if the next kart behind is 'close' but not too close,
// Use bubblegum if the kart around is close,
// or can't find a close kart for too long time
if ((m_closest_kart_pos_data.distance < 15.0f &&
m_closest_kart_pos_data.distance > 3.0f) ||
m_time_since_last_shot > 15.0f)
if (dist_to_kart < 15.0f || m_time_since_last_shot > 15.0f)
{
m_controls->setFire(true);
m_controls->setLookBack(true);
@@ -574,7 +485,7 @@ void ArenaAI::handleArenaItems(const float dt)
// Leave some time between shots
if (m_time_since_last_shot < 1.0f) break;
if (m_closest_kart_pos_data.distance < 25.0f &&
if (dist_to_kart < 25.0f &&
!m_closest_kart->isInvulnerable())
{
m_controls->setFire(true);
@@ -594,7 +505,7 @@ void ArenaAI::handleArenaItems(const float dt)
// Leave some time between shots
if (m_time_since_last_shot < 1.0f) break;
if (m_closest_kart_pos_data.distance < 6.0f &&
if (dist_to_kart < 6.0f &&
(difficulty || perfect_aim) &&
!m_closest_kart->isInvulnerable())
{
@@ -615,7 +526,7 @@ void ArenaAI::handleArenaItems(const float dt)
break;
if (!m_closest_kart->isSquashed() &&
m_closest_kart_pos_data.distance < d2 &&
dist_to_kart * dist_to_kart < d2 &&
m_closest_kart->getSpeed() < m_kart->getSpeed())
{
m_controls->setFire(true);
@@ -653,60 +564,55 @@ void ArenaAI::handleArenaItems(const float dt)
}
if (m_controls->getFire())
m_time_since_last_shot = 0.0f;
} // handleArenaItems
} // useItems
//-----------------------------------------------------------------------------
void ArenaAI::collectItemInArena(Vec3* aim_point, int* target_node) const
{
float distance = 99999.9f;
const std::vector< std::pair<const Item*, int> >& item_list =
BattleGraph::get()->getItemList();
const unsigned int items_count = item_list.size();
float distance = 999999.9f;
Item* selected = (*target_node == Graph::UNKNOWN_SECTOR ? NULL :
ItemManager::get()->getFirstItemInQuad(*target_node));
if (item_list.empty())
// Don't look for a new item unless it's collected or swapped
if (selected && !(selected->wasCollected() ||
selected->getType() == Item::ITEM_BANANA ||
selected->getType() == Item::ITEM_BUBBLEGUM ||
selected->getType() == Item::ITEM_BUBBLEGUM_NOLOK))
{
// Notice: this should not happen, as it makes no sense
// for an arean without items, if so how can attack happen?
Log::fatal ("ArenaAI",
"AI can't find any items in the arena, "
"maybe there is something wrong with the navmesh, "
"make sure it lies closely to the ground.");
*aim_point = selected->getXYZ();
return;
}
unsigned int closest_item_num = 0;
for (unsigned int i = 0; i < items_count; ++i)
for (unsigned int i = 0; i < m_graph->getNumNodes(); i++)
{
const Item* item = item_list[i].first;
Item* cur_item = ItemManager::get()->getFirstItemInQuad(i);
if (cur_item == NULL) continue;
if (cur_item->wasCollected() ||
cur_item->getType() == Item::ITEM_BANANA ||
cur_item->getType() == Item::ITEM_BUBBLEGUM ||
cur_item->getType() == Item::ITEM_BUBBLEGUM_NOLOK)
continue;
if (item->wasCollected()) continue;
if ((item->getType() == Item::ITEM_NITRO_BIG ||
item->getType() == Item::ITEM_NITRO_SMALL) &&
if ((cur_item->getType() == Item::ITEM_NITRO_BIG ||
cur_item->getType() == Item::ITEM_NITRO_SMALL) &&
(m_kart->getEnergy() >
m_kart->getKartProperties()->getNitroSmallContainer()))
continue; // Ignore nitro when already has some
float test_distance = BattleGraph::get()
->getDistance(item_list[i].second, getCurrentNode());
if (test_distance <= distance &&
(item->getType() == Item::ITEM_BONUS_BOX ||
item->getType() == Item::ITEM_NITRO_BIG ||
item->getType() == Item::ITEM_NITRO_SMALL))
const int cur_node = cur_item->getGraphNode();
assert(cur_node != Graph::UNKNOWN_SECTOR);
float test_distance = m_graph->getDistance(cur_node, getCurrentNode());
if (test_distance <= distance)
{
closest_item_num = i;
selected = cur_item;
distance = test_distance;
}
}
const Item *item_selected = item_list[closest_item_num].first;
if (item_selected->getType() == Item::ITEM_BONUS_BOX ||
item_selected->getType() == Item::ITEM_NITRO_BIG ||
item_selected->getType() == Item::ITEM_NITRO_SMALL)
if (selected != NULL)
{
*aim_point = item_selected->getXYZ();
*target_node = item_list[closest_item_num].second;
*aim_point = selected->getXYZ();
*target_node = selected->getGraphNode();
}
else
{
@@ -715,3 +621,121 @@ void ArenaAI::collectItemInArena(Vec3* aim_point, int* target_node) const
*target_node = m_closest_kart_node;
}
} // collectItemInArena
//-----------------------------------------------------------------------------
void ArenaAI::doSkiddingTest()
{
m_mini_skid = false;
// No skidding when u-turn
if (m_is_uturn) return;
// Skid when close to target, but not straight ahead, in front of it, same
// steering side and with suitable difficulties.
const float abs_angle = atan2f(fabsf(m_target_point_lc.x()),
fabsf(m_target_point_lc.z()));
if ((m_cur_difficulty == RaceManager::DIFFICULTY_HARD ||
m_cur_difficulty == RaceManager::DIFFICULTY_BEST) &&
m_target_point_lc.z() > 0 && abs_angle > 0.15f &&
m_target_point_lc.length() < 10.0f &&
((m_steering_angle < 0 && m_target_point_lc.x() < 0) ||
(m_steering_angle > 0 && m_target_point_lc.x() > 0)))
{
m_mini_skid = true;
}
} // doSkiddingTest
//-----------------------------------------------------------------------------
/** Determine if the path to target needs to be changed to avoid bad items, it
* will also set the turn radius based on the new path if necessary.
* \param forward Forward node of current AI position.
* \param path Default path to target.
*/
void ArenaAI::determinePath(int forward, std::vector<int>* path)
{
std::vector<int> bad_item_nodes;
// First, test if the nodes AI will cross contain bad item
for (unsigned int i = 0; i < path->size(); i++)
{
// Only test few nodes ahead
if (i == 6) break;
const int node = (*path)[i];
Item* selected = ItemManager::get()->getFirstItemInQuad(node);
if (selected && !selected->wasCollected() &&
(selected->getType() == Item::ITEM_BANANA ||
selected->getType() == Item::ITEM_BUBBLEGUM ||
selected->getType() == Item::ITEM_BUBBLEGUM_NOLOK))
{
bad_item_nodes.push_back(node);
}
}
// If so try to avoid
if (!bad_item_nodes.empty())
{
bool failed_avoid = false;
for (unsigned int i = 0; i < path->size(); i++)
{
if (failed_avoid) break;
if (i == 6) break;
// Choose any adjacent node that is in front of the AI to prevent
// hitting bad item
ArenaNode* cur_node =
m_graph->getNode(i == 0 ? forward : (*path)[i - 1]);
float dist = 99999.9f;
const std::vector<int>& adj_nodes = cur_node->getAdjacentNodes();
int chosen_node = Graph::UNKNOWN_SECTOR;
for (const int& adjacent : adj_nodes)
{
if (std::find(bad_item_nodes.begin(), bad_item_nodes.end(),
adjacent) != bad_item_nodes.end())
continue;
Vec3 lc = m_kart->getTrans().inverse()
(m_graph->getNode(adjacent)->getCenter());
const float dist_to_target =
m_graph->getDistance(adjacent, m_target_node);
if (lc.z() > 0 && dist > dist_to_target)
{
chosen_node = adjacent;
dist = dist_to_target;
}
if (chosen_node == Graph::UNKNOWN_SECTOR)
{
Log::debug("ArenaAI", "Too many bad items to avoid!");
failed_avoid = true;
break;
}
(*path)[i] = chosen_node;
}
}
}
// Now find the first turning corner to determine turn radius
for (unsigned int i = 0; i < path->size() - 1; i++)
{
const Vec3& p1 = m_kart->getXYZ();
const Vec3& p2 = m_graph->getNode((*path)[i])->getCenter();
const Vec3& p3 = m_graph->getNode((*path)[i + 1])->getCenter();
float edge1 = (p1 - p2).length();
float edge2 = (p2 - p3).length();
float to_target = (p1 - p3).length();
// Triangle test
if (fabsf(edge1 + edge2 - to_target) > 0.1f)
{
determineTurnRadius(p3, NULL, &m_turn_radius);
#ifdef AI_DEBUG
m_debug_sphere_next->setVisible(true);
m_debug_sphere_next->setPosition(p3.toIrrVector());
#endif
return;
}
}
// Fallback calculation
determineTurnRadius(m_target_point, NULL, &m_turn_radius);
} // determinePath

View File

@@ -21,14 +21,13 @@
#include "karts/controller/ai_base_controller.hpp"
#include "race/race_manager.hpp"
#include "utils/random_generator.hpp"
#undef AI_DEBUG
#ifdef AI_DEBUG
#include "graphics/irr_driver.hpp"
#endif
class Vec3;
class ArenaGraph;
namespace irr
{
@@ -42,14 +41,14 @@ namespace irr
class ArenaAI : public AIBaseController
{
protected:
ArenaGraph* m_graph;
/** Pointer to the closest kart around this kart. */
AbstractKart *m_closest_kart;
int m_closest_kart_node;
Vec3 m_closest_kart_point;
posData m_closest_kart_pos_data;
/** Holds the current difficulty. */
RaceManager::Difficulty m_cur_difficulty;
@@ -58,22 +57,21 @@ protected:
irr::scene::ISceneNode *m_debug_sphere;
irr::scene::ISceneNode *m_debug_sphere_next;
/** The node(poly) at which the target point lies in. */
/** The node(quad) at which the target point lies in. */
int m_target_node;
/** The target point. */
Vec3 m_target_point;
bool m_avoiding_banana;
bool m_mini_skid;
void collectItemInArena(Vec3*, int*) const;
float findAngleFrom3Edges(float a, float b, float c);
private:
/** Used by handleArenaUTurn, it tells whether to do left or right
* turning when steering is overridden. */
bool m_adjusting_side;
posData m_cur_kart_pos_data;
Vec3 m_target_point_lc;
/** Indicates that the kart is currently stuck, and m_time_since_reversing is
* counting down. */
@@ -99,40 +97,43 @@ private:
/** This is a timer that counts down when the kart is doing u-turn. */
float m_time_since_uturn;
/** This is a timer that counts when the kart start going off road. */
float m_time_since_off_road;
float m_turn_radius;
float m_turn_angle;
float m_steering_angle;
Vec3 m_current_forward_point;
int m_current_forward_node;
std::set<int> m_aiming_nodes;
std::vector<Vec3> m_aiming_points;
void checkIfStuck(const float dt);
void handleArenaAcceleration(const float dt);
void handleArenaBraking();
void handleArenaItems(const float dt);
void handleArenaSteering(const float dt);
void handleArenaUTurn(const float dt);
bool handleArenaUnstuck(const float dt);
bool updateAimingPosition();
void updateBananaLocation();
void updateTurnRadius(const Vec3& p1, const Vec3& p2,
const Vec3& p3);
virtual int getCurrentNode() const = 0;
virtual bool isWaiting() const = 0;
virtual void resetAfterStop() {};
virtual void findClosestKart(bool use_difficulty) = 0;
virtual void findTarget() = 0;
virtual bool forceBraking() { return m_avoiding_banana; }
virtual bool ignorePathFinding() { return false; }
void configSpeed();
void configSteering();
void checkIfStuck(const float dt);
void determinePath(int forward, std::vector<int>* path);
void doSkiddingTest();
void doUTurn(const float dt);
bool gettingUnstuck(const float dt);
bool updateAimingPosition(Vec3* target_point);
void useItems(const float dt);
virtual bool canSkid(float steer_fraction) OVERRIDE
{ return m_mini_skid; }
virtual void findClosestKart(bool use_difficulty) = 0;
virtual void findTarget() = 0;
virtual bool forceBraking() { return false; }
virtual int getCurrentNode() const = 0;
virtual float getKartDistance(const AbstractKart* kart) const = 0;
virtual bool ignorePathFinding() { return false; }
virtual bool isWaiting() const = 0;
virtual bool isKartOnRoad() const = 0;
virtual void resetAfterStop() {};
public:
static int m_test_node_for_banana;
ArenaAI(AbstractKart *kart);
virtual ~ArenaAI() {};
virtual void update (float delta);
virtual void reset ();
virtual void newLap(int lap) {};
virtual void update (float delta) OVERRIDE;
virtual void reset () OVERRIDE;
virtual void newLap (int lap) OVERRIDE {}
};
#endif

View File

@@ -25,12 +25,10 @@
#include "karts/abstract_kart.hpp"
#include "karts/controller/kart_control.hpp"
#include "modes/three_strikes_battle.hpp"
#include "tracks/arena_graph.hpp"
#ifdef AI_DEBUG
#include "irrlicht.h"
#include <iostream>
using namespace irr;
using namespace std;
#endif
BattleAI::BattleAI(AbstractKart *kart)
@@ -72,14 +70,11 @@ BattleAI::~BattleAI()
void BattleAI::reset()
{
ArenaAI::reset();
AIBaseController::reset();
m_mini_skid = false;
} // reset
//-----------------------------------------------------------------------------
void BattleAI::update(float dt)
{
m_mini_skid = false;
ArenaAI::update(dt);
} // update
@@ -118,33 +113,19 @@ void BattleAI::findClosestKart(bool use_difficulty)
continue;
}
Vec3 d = kart->getXYZ() - m_kart->getXYZ();
if (d.length() <= distance)
float dist_to_kart = m_graph->getDistance(getCurrentNode(),
m_world->getSectorForKart(kart));
if (dist_to_kart <= distance)
{
distance = d.length();
distance = dist_to_kart;
closest_kart_num = i;
}
}
const AbstractKart* closest_kart = m_world->getKart(closest_kart_num);
m_closest_kart_node = m_world->getKartNode(closest_kart_num);
m_closest_kart_point = closest_kart->getXYZ();
m_closest_kart = m_world->getKart(closest_kart_num);
m_closest_kart_node = m_world->getSectorForKart(m_closest_kart);
m_closest_kart_point = m_closest_kart->getXYZ();
if (!use_difficulty)
{
m_closest_kart = m_world->getKart(closest_kart_num);
checkPosition(m_closest_kart_point, &m_closest_kart_pos_data);
// Do a mini-skid to closest kart only when firing target,
// not straight ahead, not too far, in front of it
// and with suitable difficulties.
if (m_closest_kart_pos_data.angle > 0.2f &&
m_closest_kart_pos_data.distance < 20.0f &&
!m_closest_kart_pos_data.behind &&
(m_cur_difficulty == RaceManager::DIFFICULTY_HARD ||
m_cur_difficulty == RaceManager::DIFFICULTY_BEST))
m_mini_skid = true;
}
} // findClosestKart
//-----------------------------------------------------------------------------
@@ -164,10 +145,24 @@ void BattleAI::findTarget()
//-----------------------------------------------------------------------------
int BattleAI::getCurrentNode() const
{
return m_world->getKartNode(m_kart->getWorldKartId());
return m_world->getSectorForKart(m_kart);
} // getCurrentNode
//-----------------------------------------------------------------------------
bool BattleAI::isWaiting() const
{
return m_world->isStartPhase();
} // isWaiting
//-----------------------------------------------------------------------------
float BattleAI::getKartDistance(const AbstractKart* kart) const
{
return m_graph->getDistance(getCurrentNode(),
m_world->getSectorForKart(kart));
} // getKartDistance
//-----------------------------------------------------------------------------
bool BattleAI::isKartOnRoad() const
{
return m_world->isOnRoad(m_kart->getWorldKartId());
} // isKartOnRoad

View File

@@ -24,8 +24,6 @@
#include "karts/controller/arena_ai.hpp"
class ThreeStrikesBattle;
class Vec3;
class Item;
/** The actual battle AI.
* \ingroup controller
@@ -36,18 +34,17 @@ private:
/** Keep a pointer to world. */
ThreeStrikesBattle *m_world;
bool m_mini_skid;
virtual void findClosestKart(bool use_difficulty);
virtual void findTarget();
virtual int getCurrentNode() const;
virtual bool isWaiting() const;
virtual bool canSkid(float steer_fraction) { return m_mini_skid; }
virtual void findClosestKart(bool use_difficulty) OVERRIDE;
virtual void findTarget() OVERRIDE;
virtual int getCurrentNode() const OVERRIDE;
virtual float getKartDistance(const AbstractKart* kart) const OVERRIDE;
virtual bool isKartOnRoad() const OVERRIDE;
virtual bool isWaiting() const OVERRIDE;
public:
BattleAI(AbstractKart *kart);
~BattleAI();
virtual void update (float delta);
virtual void reset ();
virtual void update (float delta) OVERRIDE;
virtual void reset () OVERRIDE;
};
#endif

View File

@@ -45,7 +45,8 @@
#include "modes/linear_world.hpp"
#include "race/race_manager.hpp"
#include "states_screens/race_result_gui.hpp"
#include "tracks/quad_graph.hpp"
#include "tracks/drive_graph.hpp"
#include "tracks/drive_node.hpp"
#include "tracks/track.hpp"
#include "utils/constants.hpp"
#include "utils/log.hpp"
@@ -62,14 +63,14 @@ EndController::EndController(AbstractKart *kart,
// with a path that always picks the first branch (i.e. it follows
// the main driveline).
std::vector<unsigned int> next;
for(unsigned int i=0; i<QuadGraph::get()->getNumNodes(); i++)
for(unsigned int i=0; i<DriveGraph::get()->getNumNodes(); i++)
{
// 0 is always a valid successor - so even if the kart should end
// up by accident on a non-selected path, it will keep on working.
m_successor_index[i] = 0;
next.clear();
QuadGraph::get()->getSuccessors(i, next);
DriveGraph::get()->getSuccessors(i, next);
m_next_node_index[i] = next[0];
}
@@ -77,11 +78,11 @@ EndController::EndController(AbstractKart *kart,
// Now compute for each node in the graph the list of the next 'look_ahead'
// graph nodes. This is the list of node that is tested in checkCrashes.
// If the look_ahead is too big, the AI can skip loops (see
// QuadGraph::findRoadSector for details), if it's too short the AI won't
// DriveGraph::findRoadSector for details), if it's too short the AI won't
// find too good a driveline. Note that in general this list should
// be computed recursively, but since the AI for now is using only
// (randomly picked) path this is fine
for(unsigned int i=0; i<QuadGraph::get()->getNumNodes(); i++)
for(unsigned int i=0; i<DriveGraph::get()->getNumNodes(); i++)
{
std::vector<int> l;
int current = i;
@@ -94,7 +95,7 @@ EndController::EndController(AbstractKart *kart,
}
} // if not battle mode
// Reset must be called after QuadGraph::get() etc. is set up
// Reset must be called after DriveGraph::get() etc. is set up
reset();
m_max_handicap_accel = 1.0f;
@@ -129,18 +130,18 @@ void EndController::reset()
m_crash_time = 0.0f;
m_time_since_stuck = 0.0f;
m_track_node = QuadGraph::UNKNOWN_SECTOR;
m_track_node = Graph::UNKNOWN_SECTOR;
// In battle mode there is no quad graph, so nothing to do in this case
if(race_manager->getMinorMode()!=RaceManager::MINOR_MODE_3_STRIKES &&
race_manager->getMinorMode()!=RaceManager::MINOR_MODE_SOCCER)
{
QuadGraph::get()->findRoadSector(m_kart->getXYZ(), &m_track_node);
DriveGraph::get()->findRoadSector(m_kart->getXYZ(), &m_track_node);
// Node that this can happen quite easily, e.g. an AI kart is
// taken over by the end controller while it is off track.
if(m_track_node==QuadGraph::UNKNOWN_SECTOR)
if(m_track_node==Graph::UNKNOWN_SECTOR)
{
m_track_node = QuadGraph::get()->findOutOfRoadSector(m_kart->getXYZ());
m_track_node = DriveGraph::get()->findOutOfRoadSector(m_kart->getXYZ());
}
}
} // reset
@@ -211,10 +212,10 @@ void EndController::handleSteering(float dt)
*/
//Reaction to being outside of the road
if( fabsf(m_world->getDistanceToCenterForKart( m_kart->getWorldKartId() )) >
0.5f* QuadGraph::get()->getNode(m_track_node).getPathWidth()+0.5f )
0.5f* DriveGraph::get()->getNode(m_track_node)->getPathWidth()+0.5f )
{
const int next = m_next_node_index[m_track_node];
target_point = QuadGraph::get()->getQuadOfNode(next).getCenter();
target_point = DriveGraph::get()->getNode(next)->getCenter();
#ifdef AI_DEBUG
Log::debug("end_controller.cpp", "- Outside of road: steer to center point.");
#endif
@@ -274,10 +275,10 @@ void EndController::findNonCrashingPoint(Vec3 *result)
target_sector = m_next_node_index[sector];
//direction is a vector from our kart to the sectors we are testing
direction = QuadGraph::get()->getQuadOfNode(target_sector).getCenter()
direction = DriveGraph::get()->getNode(target_sector)->getCenter()
- m_kart->getXYZ();
float len=direction.length_2d();
float len=direction.length();
steps = int( len / m_kart_length );
if( steps < 3 ) steps = 3;
@@ -292,16 +293,16 @@ void EndController::findNonCrashingPoint(Vec3 *result)
{
step_coord = m_kart->getXYZ()+direction*m_kart_length * float(i);
QuadGraph::get()->spatialToTrack(&step_track_coord, step_coord,
DriveGraph::get()->spatialToTrack(&step_track_coord, step_coord,
sector );
distance = fabsf(step_track_coord[0]);
//If we are outside, the previous sector is what we are looking for
if ( distance + m_kart_width * 0.5f
> QuadGraph::get()->getNode(sector).getPathWidth()*0.5f )
> DriveGraph::get()->getNode(sector)->getPathWidth()*0.5f )
{
*result = QuadGraph::get()->getQuadOfNode(sector).getCenter();
*result = DriveGraph::get()->getNode(sector)->getCenter();
return;
}
}

View File

@@ -25,7 +25,6 @@
class Camera;
class LinearWorld;
class QuadGraph;
class Track;
class Vec3;

View File

@@ -33,7 +33,6 @@
#include "modes/world.hpp"
#include "race/history.hpp"
#include "states_screens/race_gui_base.hpp"
#include "tracks/battle_graph.hpp"
#include "utils/constants.hpp"
#include "utils/log.hpp"
#include "utils/translation.hpp"

View File

@@ -41,7 +41,7 @@
#include "modes/linear_world.hpp"
#include "modes/profile_world.hpp"
#include "race/race_manager.hpp"
#include "tracks/quad_graph.hpp"
#include "tracks/drive_graph.hpp"
#include "tracks/track.hpp"
#include "utils/constants.hpp"
#include "utils/log.hpp"
@@ -171,22 +171,23 @@ void SkiddingAI::reset()
m_distance_behind = 0.0f;
m_current_curve_radius = 0.0f;
m_curve_center = Vec3(0,0,0);
m_current_track_direction = GraphNode::DIR_STRAIGHT;
m_current_track_direction = DriveNode::DIR_STRAIGHT;
m_item_to_collect = NULL;
m_last_direction_node = 0;
m_avoid_item_close = false;
m_skid_probability_state = SKID_PROBAB_NOT_YET;
m_last_item_random = NULL;
AIBaseLapController::reset();
m_track_node = QuadGraph::UNKNOWN_SECTOR;
QuadGraph::get()->findRoadSector(m_kart->getXYZ(), &m_track_node);
if(m_track_node==QuadGraph::UNKNOWN_SECTOR)
m_track_node = Graph::UNKNOWN_SECTOR;
DriveGraph::get()->findRoadSector(m_kart->getXYZ(), &m_track_node);
if(m_track_node==Graph::UNKNOWN_SECTOR)
{
Log::error(getControllerName().c_str(),
"Invalid starting position for '%s' - not on track"
" - can be ignored.",
m_kart->getIdent().c_str());
m_track_node = QuadGraph::get()->findOutOfRoadSector(m_kart->getXYZ());
m_track_node = DriveGraph::get()->findOutOfRoadSector(m_kart->getXYZ());
}
AIBaseLapController::reset();
@@ -414,7 +415,7 @@ void SkiddingAI::handleBraking()
// If the kart is not facing roughly in the direction of the track, brake
// so that it is easier for the kart to turn in the right direction.
if(m_current_track_direction==GraphNode::DIR_UNDEFINED &&
if(m_current_track_direction==DriveNode::DIR_UNDEFINED &&
m_kart->getSpeed() > MIN_SPEED)
{
#ifdef DEBUG
@@ -426,8 +427,8 @@ void SkiddingAI::handleBraking()
m_controls->setBrake(true);
return;
}
if(m_current_track_direction==GraphNode::DIR_LEFT ||
m_current_track_direction==GraphNode::DIR_RIGHT )
if(m_current_track_direction==DriveNode::DIR_LEFT ||
m_current_track_direction==DriveNode::DIR_RIGHT )
{
float max_turn_speed =
m_kart->getSpeedForTurnRadius(m_current_curve_radius);
@@ -476,14 +477,14 @@ void SkiddingAI::handleSteering(float dt)
m_world->getDistanceToCenterForKart( m_kart->getWorldKartId() );
if( fabsf(side_dist) >
0.5f* QuadGraph::get()->getNode(m_track_node).getPathWidth()+0.5f )
0.5f* DriveGraph::get()->getNode(m_track_node)->getPathWidth()+0.5f )
{
steer_angle = steerToPoint(QuadGraph::get()->getQuadOfNode(next)
.getCenter());
steer_angle = steerToPoint(DriveGraph::get()->getNode(next)
->getCenter());
#ifdef AI_DEBUG
m_debug_sphere[0]->setPosition(QuadGraph::get()->getQuadOfNode(next)
.getCenter().toIrrVector());
m_debug_sphere[0]->setPosition(DriveGraph::get()->getNode(next)
->getCenter().toIrrVector());
Log::debug(getControllerName().c_str(),
"Outside of road: steer to center point.");
#endif
@@ -530,7 +531,7 @@ void SkiddingAI::handleSteering(float dt)
{
m_start_kart_crash_direction = 0;
Vec3 aim_point;
int last_node = QuadGraph::UNKNOWN_SECTOR;
int last_node = Graph::UNKNOWN_SECTOR;
switch(m_point_selection_algorithm)
{
@@ -619,12 +620,11 @@ void SkiddingAI::handleItemCollectionAndAvoidance(Vec3 *aim_point,
#ifdef AI_DEBUG
m_item_sphere->setVisible(false);
#endif
// Angle of line from kart to aim_point
float kart_aim_angle = atan2(aim_point->getX()-m_kart->getXYZ().getX(),
aim_point->getZ()-m_kart->getXYZ().getZ());
// Angle to aim_point
Vec3 kart_aim_direction = *aim_point - m_kart->getXYZ();
// Make sure we have a valid last_node
if(last_node==QuadGraph::UNKNOWN_SECTOR)
if(last_node==Graph::UNKNOWN_SECTOR)
last_node = m_next_node_index[m_track_node];
int node = m_track_node;
@@ -637,15 +637,15 @@ void SkiddingAI::handleItemCollectionAndAvoidance(Vec3 *aim_point,
const float max_item_lookahead_distance = 30.f;
while(distance < max_item_lookahead_distance)
{
int q_index= QuadGraph::get()->getNode(node).getQuadIndex();
int n_index= DriveGraph::get()->getNode(node)->getIndex();
const std::vector<Item *> &items_ahead =
ItemManager::get()->getItemsInQuads(q_index);
ItemManager::get()->getItemsInQuads(n_index);
for(unsigned int i=0; i<items_ahead.size(); i++)
{
evaluateItems(items_ahead[i], kart_aim_angle,
evaluateItems(items_ahead[i], kart_aim_direction,
&items_to_avoid, &items_to_collect);
} // for i<items_ahead;
distance += QuadGraph::get()->getDistanceToNext(node,
distance += DriveGraph::get()->getDistanceToNext(node,
m_successor_index[node]);
node = m_next_node_index[node];
// Stop when we have reached the last quad
@@ -654,10 +654,8 @@ void SkiddingAI::handleItemCollectionAndAvoidance(Vec3 *aim_point,
m_avoid_item_close = items_to_avoid.size()>0;
core::line2df line_to_target(aim_point->getX(),
aim_point->getZ(),
m_kart->getXYZ().getX(),
m_kart->getXYZ().getZ());
core::line3df line_to_target_3d((*aim_point).toIrrVector(),
m_kart->getXYZ().toIrrVector());
// 2) If the kart is aiming for an item, but (suddenly) detects
// some close-by items to avoid (e.g. behind the item, which was too
@@ -670,7 +668,7 @@ void SkiddingAI::handleItemCollectionAndAvoidance(Vec3 *aim_point,
for(unsigned int i=0; i< items_to_avoid.size(); i++)
{
Vec3 d = items_to_avoid[i]->getXYZ()-m_item_to_collect->getXYZ();
if( d.length2_2d()>m_ai_properties->m_bad_item_closeness_2)
if( d.length2()>m_ai_properties->m_bad_item_closeness_2)
continue;
// It could make sense to also test if the bad item would
// actually be hit, not only if it is close (which can result
@@ -690,7 +688,7 @@ void SkiddingAI::handleItemCollectionAndAvoidance(Vec3 *aim_point,
// -------------------------------------
if(m_item_to_collect)
{
if(handleSelectedItem(kart_aim_angle, aim_point))
if(handleSelectedItem(kart_aim_direction, aim_point))
{
// Still aim at the previsouly selected item.
*aim_point = m_item_to_collect->getXYZ();
@@ -715,7 +713,7 @@ void SkiddingAI::handleItemCollectionAndAvoidance(Vec3 *aim_point,
{
// If we need to steer to avoid an item, this takes priority,
// ignore items to collect and return the new aim_point.
if(steerToAvoid(items_to_avoid, line_to_target, aim_point))
if(steerToAvoid(items_to_avoid, line_to_target_3d, aim_point))
{
#ifdef AI_DEBUG
m_item_sphere->setVisible(true);
@@ -764,7 +762,7 @@ void SkiddingAI::handleItemCollectionAndAvoidance(Vec3 *aim_point,
// it's on a good enough driveline, so make this item a permanent
// target. Otherwise only try to get closer (till hopefully this
// item s on our driveline)
if(item_to_collect->hitLine(line_to_target, m_kart))
if(item_to_collect->hitLine(line_to_target_3d, m_kart))
{
#ifdef AI_DEBUG
m_item_sphere->setVisible(true);
@@ -782,10 +780,10 @@ void SkiddingAI::handleItemCollectionAndAvoidance(Vec3 *aim_point,
{
// Kart will not hit item, try to get closer to this item
// so that it can potentially become a permanent target.
Vec3 xyz = item_to_collect->getXYZ();
float item_angle = atan2(xyz.getX() - m_kart->getXYZ().getX(),
xyz.getZ() - m_kart->getXYZ().getZ());
float angle = normalizeAngle(kart_aim_angle - item_angle);
const Vec3& xyz = item_to_collect->getXYZ();
float angle_to_item = (xyz - m_kart->getXYZ())
.angle(kart_aim_direction);
float angle = normalizeAngle(angle_to_item);
if(fabsf(angle) < 0.3)
{
@@ -826,8 +824,8 @@ void SkiddingAI::handleItemCollectionAndAvoidance(Vec3 *aim_point,
bool SkiddingAI::hitBadItemWhenAimAt(const Item *item,
const std::vector<const Item *> &items_to_avoid)
{
core::line2df to_item(m_kart->getXYZ().getX(), m_kart->getXYZ().getZ(),
item->getXYZ().getX(), item->getXYZ().getZ() );
core::line3df to_item(m_kart->getXYZ().toIrrVector(),
item->getXYZ().toIrrVector());
for(unsigned int i=0; i<items_to_avoid.size(); i++)
{
if(items_to_avoid[i]->hitLine(to_item, m_kart))
@@ -848,7 +846,7 @@ bool SkiddingAI::hitBadItemWhenAimAt(const Item *item,
* \param last_node
* \return True if th AI should still aim for the pre-selected item.
*/
bool SkiddingAI::handleSelectedItem(float kart_aim_angle, Vec3 *aim_point)
bool SkiddingAI::handleSelectedItem(Vec3 kart_aim_direction, Vec3 *aim_point)
{
// If the item is unavailable keep on testing. It is not necessary
// to test if an item has turned bad, this was tested before this
@@ -857,10 +855,9 @@ bool SkiddingAI::handleSelectedItem(float kart_aim_angle, Vec3 *aim_point)
return false;
const Vec3 &xyz = m_item_to_collect->getXYZ();
float item_angle = atan2(xyz.getX() - m_kart->getXYZ().getX(),
xyz.getZ() - m_kart->getXYZ().getZ());
float angle_to_item = (xyz - m_kart->getXYZ()).angle(kart_aim_direction);
float angle = normalizeAngle(angle_to_item);
float angle = normalizeAngle(kart_aim_angle - item_angle);
if(fabsf(angle)>1.5)
{
// We (likely) have passed the item we were aiming for
@@ -885,7 +882,7 @@ bool SkiddingAI::handleSelectedItem(float kart_aim_angle, Vec3 *aim_point)
* \return True if steering is necessary to avoid an item.
*/
bool SkiddingAI::steerToAvoid(const std::vector<const Item *> &items_to_avoid,
const core::line2df &line_to_target,
const core::line3df &line_to_target,
Vec3 *aim_point)
{
// First determine the left-most and right-most item.
@@ -911,14 +908,19 @@ bool SkiddingAI::steerToAvoid(const std::vector<const Item *> &items_to_avoid,
// Check if we would drive left of the leftmost or right of the
// rightmost point - if so, nothing to do.
core::vector2df left(items_to_avoid[index_left_most]->getXYZ().getX(),
items_to_avoid[index_left_most]->getXYZ().getZ());
const Vec3& left = items_to_avoid[index_left_most]->getXYZ();
int node_index = items_to_avoid[index_left_most]->getGraphNode();
const Vec3& normal = DriveGraph::get()->getNode(node_index)->getNormal();
Vec3 p1 = line_to_target.start,
p2 = line_to_target.getMiddle() + normal.toIrrVector(),
p3 = line_to_target.end;
int item_index = -1;
bool is_left = false;
// >=0 means the point is to the right of the line, or the line is
// to the left of the point.
if(line_to_target.getPointOrientation(left)>=0)
if(left.sideofPlane(p1,p2,p3) <= 0)
{
// Left of leftmost point
item_index = index_left_most;
@@ -926,9 +928,14 @@ bool SkiddingAI::steerToAvoid(const std::vector<const Item *> &items_to_avoid,
}
else
{
core::vector2df right(items_to_avoid[index_right_most]->getXYZ().getX(),
items_to_avoid[index_right_most]->getXYZ().getZ());
if(line_to_target.getPointOrientation(right)<=0)
const Vec3& right = items_to_avoid[index_right_most]->getXYZ();
int node_index = items_to_avoid[index_right_most]->getGraphNode();
const Vec3& normal = DriveGraph::get()->getNode(node_index)->getNormal();
Vec3 p1 = line_to_target.start,
p2 = line_to_target.getMiddle() + normal.toIrrVector(),
p3 = line_to_target.end;
if (right.sideofPlane(p1, p2, p3) >= 0)
{
// Right of rightmost point
item_index = index_right_most;
@@ -969,20 +976,19 @@ bool SkiddingAI::steerToAvoid(const std::vector<const Item *> &items_to_avoid,
float min_distance[2] = {99999.9f, 99999.9f};
int index[2] = {-1, -1};
core::vector2df closest2d[2];
core::vector3df closest3d[2];
for(unsigned int i=0; i<items_to_avoid.size(); i++)
{
const Vec3 &xyz = items_to_avoid[i]->getXYZ();
core::vector2df item2d = xyz.toIrrVector2d();
core::vector2df point2d = line_to_target.getClosestPoint(item2d);
float d = (xyz.toIrrVector2d() - point2d).getLengthSQ();
float direction = line_to_target.getPointOrientation(item2d);
int ind = direction<0 ? 0 : 1;
core::vector3df point3d = line_to_target.getClosestPoint(xyz.toIrrVector());
float d = (xyz.toIrrVector() - point3d).getLengthSQ();
float direction = xyz.sideofPlane(p1,p2,p3);
int ind = direction<0 ? 1 : 0;
if(d<min_distance[ind])
{
min_distance[ind] = d;
index[ind] = i;
closest2d[ind] = point2d;
closest3d[ind] = point3d;
}
}
@@ -992,8 +998,8 @@ bool SkiddingAI::steerToAvoid(const std::vector<const Item *> &items_to_avoid,
// We are driving between item_to_avoid[index[0]] and ...[1].
// If we don't hit any of them, just keep on driving as normal
bool hit_left = items_to_avoid[index[0]]->hitKart(closest2d[0], m_kart);
bool hit_right = items_to_avoid[index[1]]->hitKart(closest2d[1], m_kart);
bool hit_left = items_to_avoid[index[0]]->hitKart(closest3d[0], m_kart);
bool hit_right = items_to_avoid[index[1]]->hitKart(closest3d[1], m_kart);
if( !hit_left && !hit_right)
return false;
@@ -1027,7 +1033,7 @@ bool SkiddingAI::steerToAvoid(const std::vector<const Item *> &items_to_avoid,
* (NULL if no item was avoided so far).
* \param item_to_collect A pointer to a previously selected item to collect.
*/
void SkiddingAI::evaluateItems(const Item *item, float kart_aim_angle,
void SkiddingAI::evaluateItems(const Item *item, Vec3 kart_aim_direction,
std::vector<const Item *> *items_to_avoid,
std::vector<const Item *> *items_to_collect)
{
@@ -1075,13 +1081,10 @@ void SkiddingAI::evaluateItems(const Item *item, float kart_aim_angle,
// to avoid are collected).
if(!avoid)
{
// item_angle The angle of the item (relative to the forward axis,
// so 0 means straight ahead in world coordinates!).
const Vec3 &xyz = item->getXYZ();
float item_angle = atan2(xyz.getX() - m_kart->getXYZ().getX(),
xyz.getZ() - m_kart->getXYZ().getZ());
float diff = normalizeAngle(kart_aim_angle-item_angle);
float angle_to_item =
(xyz - m_kart->getXYZ()).angle(kart_aim_direction);
float diff = normalizeAngle(angle_to_item);
// The kart is driving at high speed, when the current max speed
// is higher than the max speed of the kart (which is caused by
@@ -1110,14 +1113,14 @@ void SkiddingAI::evaluateItems(const Item *item, float kart_aim_angle,
else
list = items_to_collect;
float new_distance = (item->getXYZ() - m_kart->getXYZ()).length2_2d();
float new_distance = (item->getXYZ() - m_kart->getXYZ()).length2();
// This list is usually very short, so use a simple bubble sort
list->push_back(item);
int i;
for(i=(int)list->size()-2; i>=0; i--)
{
float d = ((*list)[i]->getXYZ() - m_kart->getXYZ()).length2_2d();
float d = ((*list)[i]->getXYZ() - m_kart->getXYZ()).length2();
if(d<=new_distance)
{
break;
@@ -1299,15 +1302,19 @@ void SkiddingAI::handleItems(const float dt)
bool straight_ahead = false;
if (m_kart_behind)
{
posData behind_pos = {0};
checkPosition(m_kart_behind->getXYZ(), &behind_pos);
if (behind_pos.angle < 0.2f) straight_behind = true;
Vec3 behind_lc = m_kart->getTrans().inverse()
(m_kart_behind->getXYZ());
const float abs_angle =
atan2f(fabsf(behind_lc.x()), fabsf(behind_lc.z()));
if (abs_angle < 0.2f) straight_behind = true;
}
if (m_kart_ahead)
{
posData ahead_pos = {0};
checkPosition(m_kart_ahead->getXYZ(), &ahead_pos);
if (ahead_pos.angle < 0.2f) straight_ahead = true;
Vec3 ahead_lc = m_kart->getTrans().inverse()
(m_kart_ahead->getXYZ());
const float abs_angle =
atan2f(fabsf(ahead_lc.x()), fabsf(ahead_lc.z()));
if (abs_angle < 0.2f) straight_ahead = true;
}
// Bowling balls are slower, so only fire on closer karts - but when
@@ -1679,14 +1686,14 @@ void SkiddingAI::handleNitroAndZipper()
m_kart->getSpeed()>1.0f &&
m_kart->getSpeedIncreaseTimeLeft(MaxSpeed::MS_INCREASE_ZIPPER)<=0)
{
GraphNode::DirectionType dir;
DriveNode::DirectionType dir;
unsigned int last;
const GraphNode &gn = QuadGraph::get()->getNode(m_track_node);
gn.getDirectionData(m_successor_index[m_track_node], &dir, &last);
if(dir==GraphNode::DIR_STRAIGHT)
const DriveNode* dn = DriveGraph::get()->getNode(m_track_node);
dn->getDirectionData(m_successor_index[m_track_node], &dir, &last);
if(dir==DriveNode::DIR_STRAIGHT)
{
float diff = QuadGraph::get()->getDistanceFromStart(last)
- QuadGraph::get()->getDistanceFromStart(m_track_node);
float diff = DriveGraph::get()->getDistanceFromStart(last)
- DriveGraph::get()->getDistanceFromStart(m_track_node);
if(diff<0) diff+=World::getWorld()->getTrack()->getTrackLength();
if(diff>m_ai_properties->m_straight_length_for_zipper)
m_controls->setFire(true);
@@ -1730,16 +1737,14 @@ void SkiddingAI::checkCrashes(const Vec3& pos )
const size_t NUM_KARTS = m_world->getNumKarts();
//Protection against having vel_normal with nan values
const Vec3 &VEL = m_kart->getVelocity();
Vec3 vel_normal(VEL.getX(), 0.0, VEL.getZ());
float speed=vel_normal.length();
float speed = m_kart->getVelocity().length();
// If the velocity is zero, no sense in checking for crashes in time
if(speed==0) return;
Vec3 vel_normal = m_kart->getVelocity().normalized();
// Time it takes to drive for m_kart_length units.
float dt = m_kart_length / speed;
vel_normal/=speed;
int current_node = m_track_node;
if(steps<1 || steps>1000)
@@ -1769,7 +1774,7 @@ void SkiddingAI::checkCrashes(const Vec3& pos )
continue;
Vec3 other_kart_xyz = other_kart->getXYZ()
+ other_kart->getVelocity()*(i*dt);
float kart_distance = (step_coord - other_kart_xyz).length_2d();
float kart_distance = (step_coord - other_kart_xyz).length();
if( kart_distance < m_kart_length)
m_crashes.m_kart = j;
@@ -1777,12 +1782,12 @@ void SkiddingAI::checkCrashes(const Vec3& pos )
}
/*Find if we crash with the drivelines*/
if(current_node!=QuadGraph::UNKNOWN_SECTOR &&
if(current_node!=Graph::UNKNOWN_SECTOR &&
m_next_node_index[current_node]!=-1)
QuadGraph::get()->findRoadSector(step_coord, &current_node,
DriveGraph::get()->findRoadSector(step_coord, &current_node,
/* sectors to test*/ &m_all_look_aheads[current_node]);
if( current_node == QuadGraph::UNKNOWN_SECTOR)
if( current_node == Graph::UNKNOWN_SECTOR)
{
m_crashes.m_road = true;
return;
@@ -1830,23 +1835,23 @@ void SkiddingAI::findNonCrashingPointNew(Vec3 *result, int *last_node)
*last_node = m_next_node_index[m_track_node];
const core::vector2df xz = m_kart->getXYZ().toIrrVector2d();
const Quad &q = QuadGraph::get()->getQuadOfNode(*last_node);
const DriveNode* dn = DriveGraph::get()->getNode(*last_node);
// Index of the left and right end of a quad.
const unsigned int LEFT_END_POINT = 0;
const unsigned int RIGHT_END_POINT = 1;
core::line2df left (xz, q[LEFT_END_POINT ].toIrrVector2d());
core::line2df right(xz, q[RIGHT_END_POINT].toIrrVector2d());
core::line2df left (xz, (*dn)[LEFT_END_POINT ].toIrrVector2d());
core::line2df right(xz, (*dn)[RIGHT_END_POINT].toIrrVector2d());
#if defined(AI_DEBUG) && defined(AI_DEBUG_NEW_FIND_NON_CRASHING)
const Vec3 eps1(0,0.5f,0);
m_curve[CURVE_LEFT]->clear();
m_curve[CURVE_LEFT]->addPoint(m_kart->getXYZ()+eps1);
m_curve[CURVE_LEFT]->addPoint(q[LEFT_END_POINT]+eps1);
m_curve[CURVE_LEFT]->addPoint((*dn)[LEFT_END_POINT]+eps1);
m_curve[CURVE_LEFT]->addPoint(m_kart->getXYZ()+eps1);
m_curve[CURVE_RIGHT]->clear();
m_curve[CURVE_RIGHT]->addPoint(m_kart->getXYZ()+eps1);
m_curve[CURVE_RIGHT]->addPoint(q[RIGHT_END_POINT]+eps1);
m_curve[CURVE_RIGHT]->addPoint((*dn)[RIGHT_END_POINT]+eps1);
m_curve[CURVE_RIGHT]->addPoint(m_kart->getXYZ()+eps1);
#endif
#if defined(AI_DEBUG_KART_HEADING) || defined(AI_DEBUG_NEW_FIND_NON_CRASHING)
@@ -1859,13 +1864,13 @@ void SkiddingAI::findNonCrashingPointNew(Vec3 *result, int *last_node)
while(1)
{
unsigned int next_sector = m_next_node_index[*last_node];
const Quad &q_next = QuadGraph::get()->getQuadOfNode(next_sector);
const DriveNode* dn_next = DriveGraph::get()->getNode(next_sector);
// Test if the next left point is to the right of the left
// line. If so, a new left line is defined.
if(left.getPointOrientation(q_next[LEFT_END_POINT].toIrrVector2d())
if(left.getPointOrientation((*dn_next)[LEFT_END_POINT].toIrrVector2d())
< 0 )
{
core::vector2df p = q_next[LEFT_END_POINT].toIrrVector2d();
core::vector2df p = (*dn_next)[LEFT_END_POINT].toIrrVector2d();
// Stop if the new point is to the right of the right line
if(right.getPointOrientation(p)<0)
break;
@@ -1881,10 +1886,10 @@ void SkiddingAI::findNonCrashingPointNew(Vec3 *result, int *last_node)
// Test if new right point is to the left of the right line. If
// so, a new right line is defined.
if(right.getPointOrientation(q_next[RIGHT_END_POINT].toIrrVector2d())
if(right.getPointOrientation((*dn_next)[RIGHT_END_POINT].toIrrVector2d())
> 0 )
{
core::vector2df p = q_next[RIGHT_END_POINT].toIrrVector2d();
core::vector2df p = (*dn_next)[RIGHT_END_POINT].toIrrVector2d();
// Break if new point is to the left of left line
if(left.getPointOrientation(p)>0)
break;
@@ -1906,7 +1911,7 @@ void SkiddingAI::findNonCrashingPointNew(Vec3 *result, int *last_node)
// 0.5f*(left.end.Y+right.end.Y));
//*result = ppp;
*result = QuadGraph::get()->getQuadOfNode(*last_node).getCenter();
*result = DriveGraph::get()->getNode(*last_node)->getCenter();
} // findNonCrashingPointNew
//-----------------------------------------------------------------------------
@@ -1943,10 +1948,10 @@ void SkiddingAI::findNonCrashingPointFixed(Vec3 *aim_position, int *last_node)
target_sector = m_next_node_index[*last_node];
//direction is a vector from our kart to the sectors we are testing
direction = QuadGraph::get()->getQuadOfNode(target_sector).getCenter()
direction = DriveGraph::get()->getNode(target_sector)->getCenter()
- m_kart->getXYZ();
float len=direction.length_2d();
float len=direction.length();
unsigned int steps = (unsigned int)( len / m_kart_length );
if( steps < 3 ) steps = 3;
@@ -1966,23 +1971,23 @@ void SkiddingAI::findNonCrashingPointFixed(Vec3 *aim_position, int *last_node)
{
step_coord = m_kart->getXYZ()+direction*m_kart_length * float(i);
QuadGraph::get()->spatialToTrack(&step_track_coord, step_coord,
DriveGraph::get()->spatialToTrack(&step_track_coord, step_coord,
*last_node );
float distance = fabsf(step_track_coord[0]);
//If we are outside, the previous node is what we are looking for
if ( distance + m_kart_width * 0.5f
> QuadGraph::get()->getNode(*last_node).getPathWidth()*0.5f )
> DriveGraph::get()->getNode(*last_node)->getPathWidth()*0.5f )
{
*aim_position = QuadGraph::get()->getQuadOfNode(*last_node)
.getCenter();
*aim_position = DriveGraph::get()->getNode(*last_node)
->getCenter();
return;
}
}
*last_node = target_sector;
} // for i<100
*aim_position = QuadGraph::get()->getQuadOfNode(*last_node).getCenter();
*aim_position = DriveGraph::get()->getNode(*last_node)->getCenter();
} // findNonCrashingPointFixed
//-----------------------------------------------------------------------------
@@ -1990,14 +1995,14 @@ void SkiddingAI::findNonCrashingPointFixed(Vec3 *aim_position, int *last_node)
* 1. the test:
*
* distance + m_kart_width * 0.5f
* > QuadGraph::get()->getNode(*last_node).getPathWidth() )
* > DriveGraph::get()->getNode(*last_node)->getPathWidth() )
*
* is incorrect, it should compare with getPathWith*0.5f (since distance
* is the distance from the center, i.e. it is half the path width if
* the point is at the edge).
* 2. the test:
*
* QuadGraph::get()->spatialToTrack(&step_track_coord, step_coord,
* DriveGraph::get()->spatialToTrack(&step_track_coord, step_coord,
* *last_node );
* in the for loop tests always against distance from the same
* graph node (*last_node), while de-fact the loop will test points
@@ -2022,7 +2027,7 @@ void SkiddingAI::findNonCrashingPointFixed(Vec3 *aim_position, int *last_node)
m_curve[CURVE_KART]->addPoint(m_kart->getTrans()(forw)+eps);
#endif
*last_node = m_next_node_index[m_track_node];
float angle = QuadGraph::get()->getAngleToNext(m_track_node,
float angle = DriveGraph::get()->getAngleToNext(m_track_node,
m_successor_index[m_track_node]);
int target_sector;
@@ -2038,7 +2043,7 @@ void SkiddingAI::findNonCrashingPointFixed(Vec3 *aim_position, int *last_node)
// target_sector is the sector at the longest distance that we can
// drive to without crashing with the track.
target_sector = m_next_node_index[*last_node];
angle1 = QuadGraph::get()->getAngleToNext(target_sector,
angle1 = DriveGraph::get()->getAngleToNext(target_sector,
m_successor_index[target_sector]);
// In very sharp turns this algorithm tends to aim at off track points,
// resulting in hitting a corner. So test for this special case and
@@ -2046,16 +2051,16 @@ void SkiddingAI::findNonCrashingPointFixed(Vec3 *aim_position, int *last_node)
float diff = normalizeAngle(angle1-angle);
if(fabsf(diff)>1.5f)
{
*aim_position = QuadGraph::get()->getQuadOfNode(target_sector)
.getCenter();
*aim_position = DriveGraph::get()->getNode(target_sector)
->getCenter();
return;
}
//direction is a vector from our kart to the sectors we are testing
direction = QuadGraph::get()->getQuadOfNode(target_sector).getCenter()
direction = DriveGraph::get()->getNode(target_sector)->getCenter()
- m_kart->getXYZ();
float len=direction.length_2d();
float len=direction.length();
unsigned int steps = (unsigned int)( len / m_kart_length );
if( steps < 3 ) steps = 3;
@@ -2075,24 +2080,24 @@ void SkiddingAI::findNonCrashingPointFixed(Vec3 *aim_position, int *last_node)
{
step_coord = m_kart->getXYZ()+direction*m_kart_length * float(i);
QuadGraph::get()->spatialToTrack(&step_track_coord, step_coord,
DriveGraph::get()->spatialToTrack(&step_track_coord, step_coord,
*last_node );
float distance = fabsf(step_track_coord[0]);
//If we are outside, the previous node is what we are looking for
if ( distance + m_kart_width * 0.5f
> QuadGraph::get()->getNode(*last_node).getPathWidth() )
> DriveGraph::get()->getNode(*last_node)->getPathWidth() )
{
*aim_position = QuadGraph::get()->getQuadOfNode(*last_node)
.getCenter();
*aim_position = DriveGraph::get()->getNode(*last_node)
->getCenter();
return;
}
}
angle = angle1;
*last_node = target_sector;
} // for i<100
*aim_position = QuadGraph::get()->getQuadOfNode(*last_node).getCenter();
*aim_position = DriveGraph::get()->getNode(*last_node)->getCenter();
} // findNonCrashingPoint
//-----------------------------------------------------------------------------
@@ -2101,10 +2106,17 @@ void SkiddingAI::findNonCrashingPointFixed(Vec3 *aim_position, int *last_node)
*/
void SkiddingAI::determineTrackDirection()
{
const QuadGraph *qg = QuadGraph::get();
unsigned int succ = m_successor_index[m_track_node];
float angle_to_track = qg->getNode(m_track_node).getAngleToSuccessor(succ)
- m_kart->getHeading();
const DriveGraph *dg = DriveGraph::get();
unsigned int succ = m_successor_index[m_track_node];
unsigned int next = dg->getNode(m_track_node)->getSuccessor(succ);
float angle_to_track = 0.0f;
if (m_kart->getVelocity().length() > 0.0f)
{
Vec3 track_direction = -dg->getNode(m_track_node)->getCenter()
+ dg->getNode(next)->getCenter();
angle_to_track =
track_direction.angle(m_kart->getVelocity().normalized());
}
angle_to_track = normalizeAngle(angle_to_track);
// In certain circumstances (esp. S curves) it is possible that the
@@ -2120,26 +2132,24 @@ void SkiddingAI::determineTrackDirection()
// quicker be aligned with the track again).
if(fabsf(angle_to_track) > 0.22222f * M_PI)
{
m_current_track_direction = GraphNode::DIR_UNDEFINED;
m_current_track_direction = DriveNode::DIR_UNDEFINED;
return;
}
unsigned int next = qg->getNode(m_track_node).getSuccessor(succ);
qg->getNode(next).getDirectionData(m_successor_index[next],
&m_current_track_direction,
&m_last_direction_node);
dg->getNode(next)->getDirectionData(m_successor_index[next],
&m_current_track_direction,
&m_last_direction_node);
#ifdef AI_DEBUG
m_curve[CURVE_QG]->clear();
for(unsigned int i=m_track_node; i<=m_last_direction_node; i++)
{
m_curve[CURVE_QG]->addPoint(qg->getNode(i).getCenter());
m_curve[CURVE_QG]->addPoint(dg->getNode(i)->getCenter());
}
#endif
if(m_current_track_direction==GraphNode::DIR_LEFT ||
m_current_track_direction==GraphNode::DIR_RIGHT )
if(m_current_track_direction==DriveNode::DIR_LEFT ||
m_current_track_direction==DriveNode::DIR_RIGHT )
{
handleCurve();
} // if(m_current_track_direction == DIR_LEFT || DIR_RIGHT )
@@ -2164,13 +2174,10 @@ void SkiddingAI::handleCurve()
// kart will already point towards the direction of the circle), and
// the case that the kart is facing wrong was already tested for before
const QuadGraph *qg = QuadGraph::get();
Vec3 xyz = m_kart->getXYZ();
Vec3 tangent = m_kart->getTrans()(Vec3(0,0,1)) - xyz;
Vec3 last_xyz = qg->getNode(m_last_direction_node).getCenter();
const DriveGraph *dg = DriveGraph::get();
const Vec3& last_xyz = dg->getNode(m_last_direction_node)->getCenter();
determineTurnRadius(xyz, tangent, last_xyz,
&m_curve_center, &m_current_curve_radius);
determineTurnRadius(last_xyz, &m_curve_center, &m_current_curve_radius);
assert(!std::isnan(m_curve_center.getX()));
assert(!std::isnan(m_curve_center.getY()));
assert(!std::isnan(m_curve_center.getZ()));
@@ -2184,13 +2191,14 @@ void SkiddingAI::handleCurve()
{
i = m_next_node_index[i];
// Pick either the lower left or right point:
int index = m_current_track_direction==GraphNode::DIR_LEFT
int index = m_current_track_direction==DriveNode::DIR_LEFT
? 0 : 1;
float r = (m_curve_center - qg->getQuadOfNode(i)[index]).length();
Vec3 curve_center_wc = m_kart->getTrans()(m_curve_center);
float r = (curve_center_wc - *(dg->getNode(i))[index]).length();
if(m_current_curve_radius < r)
{
last_xyz = qg->getQuadOfNode(i)[index];
determineTurnRadius(xyz, tangent, last_xyz,
last_xyz = *(dg->getNode(i)[index]);
determineTurnRadius(last_xyz,
&m_curve_center, &m_current_curve_radius);
m_last_direction_node = i;
break;
@@ -2200,11 +2208,11 @@ void SkiddingAI::handleCurve()
}
#endif
#if defined(AI_DEBUG) && defined(AI_DEBUG_CIRCLES)
m_curve[CURVE_PREDICT1]->makeCircle(m_curve_center,
m_curve[CURVE_PREDICT1]->makeCircle(m_kart->getTrans()(m_curve_center),
m_current_curve_radius);
m_curve[CURVE_PREDICT1]->addPoint(last_xyz);
m_curve[CURVE_PREDICT1]->addPoint(m_curve_center);
m_curve[CURVE_PREDICT1]->addPoint(xyz);
m_curve[CURVE_PREDICT1]->addPoint(m_kart->getTrans()(m_curve_center));
m_curve[CURVE_PREDICT1]->addPoint(m_kart->getXYZ());
#endif
} // handleCurve
@@ -2243,8 +2251,8 @@ bool SkiddingAI::canSkid(float steer_fraction)
}
// No skidding on straights
if(m_current_track_direction==GraphNode::DIR_STRAIGHT ||
m_current_track_direction==GraphNode::DIR_UNDEFINED )
if(m_current_track_direction==DriveNode::DIR_STRAIGHT ||
m_current_track_direction==DriveNode::DIR_UNDEFINED )
{
#ifdef DEBUG
if(m_controls->getSkidControl() && m_ai_debug)
@@ -2258,18 +2266,19 @@ bool SkiddingAI::canSkid(float steer_fraction)
}
const float MIN_SKID_SPEED = 5.0f;
const QuadGraph *qg = QuadGraph::get();
Vec3 last_xyz = qg->getNode(m_last_direction_node).getCenter();
const DriveGraph *dg = DriveGraph::get();
Vec3 last_xyz = m_kart->getTrans().inverse()
(dg->getNode(m_last_direction_node)->getCenter());
// Only try skidding when a certain minimum speed is reached.
if(m_kart->getSpeed()<MIN_SKID_SPEED) return false;
// Estimate how long it takes to finish the curve
Vec3 diff_kart = m_kart->getXYZ() - m_curve_center;
Vec3 diff_last = last_xyz - m_curve_center;
Vec3 diff_kart = -m_curve_center;
Vec3 diff_last = last_xyz - m_curve_center;
float angle_kart = atan2(diff_kart.getX(), diff_kart.getZ());
float angle_last = atan2(diff_last.getX(), diff_last.getZ());
float angle = m_current_track_direction == GraphNode::DIR_RIGHT
float angle = m_current_track_direction == DriveNode::DIR_RIGHT
? angle_last - angle_kart
: angle_kart - angle_last;
angle = normalizeAngle(angle);
@@ -2296,9 +2305,9 @@ bool SkiddingAI::canSkid(float steer_fraction)
// left turn steer right to avoid getting too close to the left
// vorder). In this case skidding will be useless.
else if( (steer_fraction > 0 &&
m_current_track_direction==GraphNode::DIR_LEFT) ||
m_current_track_direction==DriveNode::DIR_LEFT) ||
(steer_fraction < 0 &&
m_current_track_direction==GraphNode::DIR_RIGHT) )
m_current_track_direction==DriveNode::DIR_RIGHT) )
{
#ifdef DEBUG
if(m_controls->getSkidControl() && m_ai_debug)
@@ -2450,55 +2459,3 @@ void SkiddingAI::setSteering(float angle, float dt)
} // setSteering
// ----------------------------------------------------------------------------
/** Determine the center point and radius of a circle given two points on
* the ccircle and the tangent at the first point. This is done as follows:
* 1. Determine the line going through the center point start+end, which is
* orthogonal to the vector from start to end. This line goes through the
* center of the circle.
* 2. Determine the line going through the first point and is orthogonal
* to the given tangent.
* 3. The intersection of these two lines is the center of the circle.
* \param[in] start First point.
* \param[in] tangent Tangent at first point.
* \param[in] end Second point on circle.
* \param[out] center Center point of the circle.
* \param[out] radius Radius of the circle.
*/
void SkiddingAI::determineTurnRadius(const Vec3 &start,
const Vec3 &tangent,
const Vec3 &end,
Vec3 *center,
float *radius)
{
// 1) Line through middle of start+end
Vec3 mid = 0.5f*(start+end);
Vec3 direction = end-start;
Vec3 orthogonal(direction.getZ(), 0, -direction.getX());
Vec3 q1 = mid + orthogonal;
irr::core::line2df line1(mid.getX(), mid.getZ(),
q1.getX(), q1.getZ() );
Vec3 ortho_tangent(tangent.getZ(), 0, -tangent.getX());
Vec3 q2 = start + ortho_tangent;
irr::core::line2df line2(start.getX(), start.getZ(),
q2.getX(), q2.getZ());
irr::core::vector2df result;
if(line1.intersectWith(line2, result, /*checkOnlySegments*/false))
{
*center = Vec3(result.X, start.getY(), result.Y);
*radius = (start - *center).length();
}
else
{
// No intersection. In this case assume that the two points are
// on a semicircle, in which case the center is at 0.5*(start+end):
*center = 0.5f*(start+end);
*radius = 0.5f*(end-start).length();
}
return;
} // determineTurnRadius

View File

@@ -45,11 +45,11 @@
#include "karts/controller/ai_base_lap_controller.hpp"
#include "race/race_manager.hpp"
#include "tracks/graph_node.hpp"
#include "tracks/drive_node.hpp"
#include "utils/random_generator.hpp"
class LinearWorld;
class QuadGraph;
class DriveGraph;
class ShowCurve;
class Track;
@@ -155,7 +155,7 @@ private:
int m_start_kart_crash_direction;
/** The direction of the track where the kart is on atm. */
GraphNode::DirectionType m_current_track_direction;
DriveNode::DirectionType m_current_track_direction;
/** The radius of the curve the kart is currently driving. Undefined
* when being on a straigt section. */
@@ -249,13 +249,13 @@ private:
void computeNearestKarts();
void handleItemCollectionAndAvoidance(Vec3 *aim_point,
int last_node);
bool handleSelectedItem(float kart_aim_angle, Vec3 *aim_point);
bool handleSelectedItem(Vec3 kart_aim_direction, Vec3 *aim_point);
bool steerToAvoid(const std::vector<const Item *> &items_to_avoid,
const core::line2df &line_to_target,
const core::line3df &line_to_target,
Vec3 *aim_point);
bool hitBadItemWhenAimAt(const Item *item,
const std::vector<const Item *> &items_to_avoid);
void evaluateItems(const Item *item, float kart_aim_angle,
void evaluateItems(const Item *item, Vec3 kart_aim_direction,
std::vector<const Item *> *items_to_avoid,
std::vector<const Item *> *items_to_collect);
@@ -265,11 +265,6 @@ private:
void findNonCrashingPoint(Vec3 *result, int *last_node);
void determineTrackDirection();
void determineTurnRadius(const Vec3 &start,
const Vec3 &start_direction,
const Vec3 &end,
Vec3 *center,
float *radius);
virtual bool canSkid(float steer_fraction);
virtual void setSteering(float angle, float dt);
void handleCurve();

View File

@@ -24,13 +24,10 @@
#include "karts/controller/kart_control.hpp"
#include "karts/kart_properties.hpp"
#include "modes/soccer_world.hpp"
#include "tracks/battle_graph.hpp"
#include "tracks/arena_graph.hpp"
#ifdef AI_DEBUG
#include "irrlicht.h"
#include <iostream>
using namespace irr;
using namespace std;
#endif
#ifdef BALL_AIM_DEBUG
@@ -95,12 +92,14 @@ SoccerAI::~SoccerAI()
void SoccerAI::reset()
{
ArenaAI::reset();
AIBaseController::reset();
m_overtake_ball = false;
m_force_brake = false;
m_chasing_ball = false;
m_front_transform.setOrigin(m_kart->getFrontXYZ());
m_front_transform.setBasis(m_kart->getTrans().getBasis());
} // reset
//-----------------------------------------------------------------------------
@@ -114,6 +113,8 @@ void SoccerAI::update(float dt)
#endif
m_force_brake = false;
m_chasing_ball = false;
m_front_transform.setOrigin(m_kart->getFrontXYZ());
m_front_transform.setBasis(m_kart->getTrans().getBasis());
if (m_world->getPhase() == World::GOAL_PHASE)
{
@@ -155,11 +156,9 @@ void SoccerAI::findClosestKart(bool use_difficulty)
}
}
const AbstractKart* closest_kart = m_world->getKart(closest_kart_num);
m_closest_kart_node = m_world->getKartNode(closest_kart_num);
m_closest_kart_point = closest_kart->getXYZ();
m_closest_kart = m_world->getKart(closest_kart_num);
checkPosition(m_closest_kart_point, &m_closest_kart_pos_data);
m_closest_kart_node = m_world->getSectorForKart(m_closest_kart);
m_closest_kart_point = m_closest_kart->getXYZ();
} // findClosestKart
@@ -188,8 +187,9 @@ void SoccerAI::findTarget()
{
// This AI will attack the other team ball chaser
int id = m_world->getBallChaser(m_opp_team);
m_target_point = m_world->getKart(id)->getXYZ();
m_target_node = m_world->getKartNode(id);
const AbstractKart* kart = m_world->getKart(id);
m_target_point = kart->getXYZ();
m_target_node = m_world->getSectorForKart(kart);
}
else
{
@@ -215,10 +215,8 @@ Vec3 SoccerAI::determineBallAimingPosition()
const Vec3& ball_aim_pos = m_world->getBallAimPosition(m_opp_team);
const Vec3& orig_pos = m_world->getBallPosition();
Vec3 ball_lc;
Vec3 aim_lc;
checkPosition(orig_pos, NULL, &ball_lc, true/*use_front_xyz*/);
checkPosition(ball_aim_pos, NULL, &aim_lc, true/*use_front_xyz*/);
Vec3 ball_lc = m_front_transform.inverse()(orig_pos);
Vec3 aim_lc = m_front_transform.inverse()(ball_aim_pos);
// Too far from the ball,
// use path finding from arena ai to get close
@@ -236,7 +234,7 @@ Vec3 SoccerAI::determineBallAimingPosition()
return ball_aim_pos;
}
else
return m_kart->getTrans()(Vec3(overtake_lc));
return m_front_transform(overtake_lc);
}
else
{
@@ -456,10 +454,24 @@ float SoccerAI::rotateSlope(float old_slope, bool rotate_up)
//-----------------------------------------------------------------------------
int SoccerAI::getCurrentNode() const
{
return m_world->getKartNode(m_kart->getWorldKartId());
return m_world->getSectorForKart(m_kart);
} // getCurrentNode
//-----------------------------------------------------------------------------
bool SoccerAI::isWaiting() const
{
return m_world->isStartPhase();
} // isWaiting
//-----------------------------------------------------------------------------
float SoccerAI::getKartDistance(const AbstractKart* kart) const
{
return m_graph->getDistance(getCurrentNode(),
m_world->getSectorForKart(kart));
} // getKartDistance
//-----------------------------------------------------------------------------
bool SoccerAI::isKartOnRoad() const
{
return m_world->isOnRoad(m_kart->getWorldKartId());
} // isKartOnRoad

View File

@@ -21,13 +21,14 @@
#include "karts/controller/arena_ai.hpp"
#include "LinearMath/btTransform.h"
#undef BALL_AIM_DEBUG
#ifdef BALL_AIM_DEBUG
#include "graphics/irr_driver.hpp"
#endif
class SoccerWorld;
class Vec3;
/** The actual soccer AI.
* \ingroup controller
@@ -54,27 +55,31 @@ private:
bool m_force_brake;
bool m_chasing_ball;
Vec3 determineBallAimingPosition();
bool isOvertakable(const Vec3& ball_lc);
bool determineOvertakePosition(const Vec3& ball_lc, const Vec3& aim_lc,
Vec3* overtake_lc);
btTransform m_front_transform;
Vec3 determineBallAimingPosition();
bool determineOvertakePosition(const Vec3& ball_lc, const Vec3& aim_lc,
Vec3* overtake_lc);
bool isOvertakable(const Vec3& ball_lc);
float rotateSlope(float old_slope, bool rotate_up);
virtual void findClosestKart(bool use_difficulty);
virtual void findTarget();
virtual void resetAfterStop() OVERRIDE { m_overtake_ball = false; }
virtual int getCurrentNode() const;
virtual bool isWaiting() const;
virtual bool canSkid(float steer_fraction) { return false; }
virtual bool forceBraking() OVERRIDE
{ return m_avoiding_banana || m_force_brake; }
virtual bool ignorePathFinding() OVERRIDE
virtual bool canSkid(float steer_fraction) OVERRIDE
{ return m_mini_skid && !(m_overtake_ball || m_chasing_ball); }
virtual void findClosestKart(bool use_difficulty) OVERRIDE;
virtual void findTarget() OVERRIDE;
virtual bool forceBraking() OVERRIDE { return m_force_brake; }
virtual int getCurrentNode() const OVERRIDE;
virtual float getKartDistance(const AbstractKart* kart) const OVERRIDE;
virtual bool ignorePathFinding() OVERRIDE
{ return m_overtake_ball || m_chasing_ball; }
virtual bool isKartOnRoad() const OVERRIDE;
virtual bool isWaiting() const OVERRIDE;
virtual void resetAfterStop() OVERRIDE { m_overtake_ball = false; }
public:
SoccerAI(AbstractKart *kart);
~SoccerAI();
virtual void update (float delta);
virtual void reset ();
virtual void update (float delta) OVERRIDE;
virtual void reset () OVERRIDE;
};
#endif

View File

@@ -41,7 +41,7 @@
#include "modes/linear_world.hpp"
#include "modes/profile_world.hpp"
#include "race/race_manager.hpp"
#include "tracks/quad_graph.hpp"
#include "tracks/drive_graph.hpp"
#include "tracks/track.hpp"
#include "utils/constants.hpp"
#include "utils/log.hpp"
@@ -177,22 +177,23 @@ void SkiddingAI::reset()
m_distance_behind = 0.0f;
m_current_curve_radius = 0.0f;
m_curve_center = Vec3(0,0,0);
m_current_track_direction = GraphNode::DIR_STRAIGHT;
m_current_track_direction = DriveNode::DIR_STRAIGHT;
m_item_to_collect = NULL;
m_last_direction_node = 0;
m_avoid_item_close = false;
m_skid_probability_state = SKID_PROBAB_NOT_YET;
m_last_item_random = NULL;
AIBaseLapController::reset();
m_track_node = QuadGraph::UNKNOWN_SECTOR;
QuadGraph::get()->findRoadSector(m_kart->getXYZ(), &m_track_node);
if(m_track_node==QuadGraph::UNKNOWN_SECTOR)
m_track_node = Graph::UNKNOWN_SECTOR;
DriveGraph::get()->findRoadSector(m_kart->getXYZ(), &m_track_node);
if(m_track_node==Graph::UNKNOWN_SECTOR)
{
Log::error(getControllerName().c_str(),
"Invalid starting position for '%s' - not on track"
" - can be ignored.",
m_kart->getIdent().c_str());
m_track_node = QuadGraph::get()->findOutOfRoadSector(m_kart->getXYZ());
m_track_node = DriveGraph::get()->findOutOfRoadSector(m_kart->getXYZ());
}
AIBaseLapController::reset();
@@ -420,7 +421,7 @@ void SkiddingAI::handleBraking()
// If the kart is not facing roughly in the direction of the track, brake
// so that it is easier for the kart to turn in the right direction.
if(m_current_track_direction==GraphNode::DIR_UNDEFINED &&
if(m_current_track_direction==DriveNode::DIR_UNDEFINED &&
m_kart->getSpeed() > MIN_SPEED)
{
#ifdef DEBUG
@@ -432,8 +433,8 @@ void SkiddingAI::handleBraking()
m_controls->setBrake(true);
return;
}
if(m_current_track_direction==GraphNode::DIR_LEFT ||
m_current_track_direction==GraphNode::DIR_RIGHT )
if(m_current_track_direction==DriveNode::DIR_LEFT ||
m_current_track_direction==DriveNode::DIR_RIGHT )
{
float max_turn_speed =
m_kart->getSpeedForTurnRadius(m_current_curve_radius);
@@ -482,14 +483,14 @@ void SkiddingAI::handleSteering(float dt)
m_world->getDistanceToCenterForKart( m_kart->getWorldKartId() );
if( fabsf(side_dist) >
0.5f* QuadGraph::get()->getNode(m_track_node).getPathWidth()+0.5f )
0.5f* DriveGraph::get()->getNode(m_track_node)->getPathWidth()+0.5f )
{
steer_angle = steerToPoint(QuadGraph::get()->getQuadOfNode(next)
.getCenter());
steer_angle = steerToPoint(DriveGraph::get()->getNode(next)
->getCenter());
#ifdef AI_DEBUG
m_debug_sphere[0]->setPosition(QuadGraph::get()->getQuadOfNode(next)
.getCenter().toIrrVector());
m_debug_sphere[0]->setPosition(DriveGraph::get()->getNode(next)
->getCenter().toIrrVector());
Log::debug(getControllerName().c_str(),
"Outside of road: steer to center point.");
#endif
@@ -536,7 +537,7 @@ void SkiddingAI::handleSteering(float dt)
{
m_start_kart_crash_direction = 0;
Vec3 aim_point;
int last_node = QuadGraph::UNKNOWN_SECTOR;
int last_node = Graph::UNKNOWN_SECTOR;
switch(m_point_selection_algorithm)
{
@@ -625,12 +626,11 @@ void SkiddingAI::handleItemCollectionAndAvoidance(Vec3 *aim_point,
#ifdef AI_DEBUG
m_item_sphere->setVisible(false);
#endif
// Angle of line from kart to aim_point
float kart_aim_angle = atan2(aim_point->getX()-m_kart->getXYZ().getX(),
aim_point->getZ()-m_kart->getXYZ().getZ());
// Angle to aim_point
Vec3 kart_aim_direction = *aim_point - m_kart->getXYZ();
// Make sure we have a valid last_node
if(last_node==QuadGraph::UNKNOWN_SECTOR)
if(last_node==Graph::UNKNOWN_SECTOR)
last_node = m_next_node_index[m_track_node];
int node = m_track_node;
@@ -643,15 +643,15 @@ void SkiddingAI::handleItemCollectionAndAvoidance(Vec3 *aim_point,
const float max_item_lookahead_distance = 30.f;
while(distance < max_item_lookahead_distance)
{
int q_index= QuadGraph::get()->getNode(node).getQuadIndex();
int n_index= DriveGraph::get()->getNode(node)->getIndex();
const std::vector<Item *> &items_ahead =
ItemManager::get()->getItemsInQuads(q_index);
ItemManager::get()->getItemsInQuads(n_index);
for(unsigned int i=0; i<items_ahead.size(); i++)
{
evaluateItems(items_ahead[i], kart_aim_angle,
evaluateItems(items_ahead[i], kart_aim_direction,
&items_to_avoid, &items_to_collect);
} // for i<items_ahead;
distance += QuadGraph::get()->getDistanceToNext(node,
distance += DriveGraph::get()->getDistanceToNext(node,
m_successor_index[node]);
node = m_next_node_index[node];
// Stop when we have reached the last quad
@@ -660,10 +660,8 @@ void SkiddingAI::handleItemCollectionAndAvoidance(Vec3 *aim_point,
m_avoid_item_close = items_to_avoid.size()>0;
core::line2df line_to_target(aim_point->getX(),
aim_point->getZ(),
m_kart->getXYZ().getX(),
m_kart->getXYZ().getZ());
core::line3df line_to_target_3d((*aim_point).toIrrVector(),
m_kart->getXYZ().toIrrVector());
// 2) If the kart is aiming for an item, but (suddenly) detects
// some close-by items to avoid (e.g. behind the item, which was too
@@ -676,7 +674,7 @@ void SkiddingAI::handleItemCollectionAndAvoidance(Vec3 *aim_point,
for(unsigned int i=0; i< items_to_avoid.size(); i++)
{
Vec3 d = items_to_avoid[i]->getXYZ()-m_item_to_collect->getXYZ();
if( d.length2_2d()>m_ai_properties->m_bad_item_closeness_2)
if( d.length2()>m_ai_properties->m_bad_item_closeness_2)
continue;
// It could make sense to also test if the bad item would
// actually be hit, not only if it is close (which can result
@@ -696,7 +694,7 @@ void SkiddingAI::handleItemCollectionAndAvoidance(Vec3 *aim_point,
// -------------------------------------
if(m_item_to_collect)
{
if(handleSelectedItem(kart_aim_angle, aim_point))
if(handleSelectedItem(kart_aim_direction, aim_point))
{
// Still aim at the previsouly selected item.
*aim_point = m_item_to_collect->getXYZ();
@@ -721,7 +719,7 @@ void SkiddingAI::handleItemCollectionAndAvoidance(Vec3 *aim_point,
{
// If we need to steer to avoid an item, this takes priority,
// ignore items to collect and return the new aim_point.
if(steerToAvoid(items_to_avoid, line_to_target, aim_point))
if(steerToAvoid(items_to_avoid, line_to_target_3d, aim_point))
{
#ifdef AI_DEBUG
m_item_sphere->setVisible(true);
@@ -770,7 +768,7 @@ void SkiddingAI::handleItemCollectionAndAvoidance(Vec3 *aim_point,
// it's on a good enough driveline, so make this item a permanent
// target. Otherwise only try to get closer (till hopefully this
// item s on our driveline)
if(item_to_collect->hitLine(line_to_target, m_kart))
if(item_to_collect->hitLine(line_to_target_3d, m_kart))
{
#ifdef AI_DEBUG
m_item_sphere->setVisible(true);
@@ -788,10 +786,10 @@ void SkiddingAI::handleItemCollectionAndAvoidance(Vec3 *aim_point,
{
// Kart will not hit item, try to get closer to this item
// so that it can potentially become a permanent target.
Vec3 xyz = item_to_collect->getXYZ();
float item_angle = atan2(xyz.getX() - m_kart->getXYZ().getX(),
xyz.getZ() - m_kart->getXYZ().getZ());
float angle = normalizeAngle(kart_aim_angle - item_angle);
const Vec3& xyz = item_to_collect->getXYZ();
float angle_to_item = (xyz - m_kart->getXYZ())
.angle(kart_aim_direction);
float angle = normalizeAngle(angle_to_item);
if(fabsf(angle) < 0.3)
{
@@ -832,8 +830,8 @@ void SkiddingAI::handleItemCollectionAndAvoidance(Vec3 *aim_point,
bool SkiddingAI::hitBadItemWhenAimAt(const Item *item,
const std::vector<const Item *> &items_to_avoid)
{
core::line2df to_item(m_kart->getXYZ().getX(), m_kart->getXYZ().getZ(),
item->getXYZ().getX(), item->getXYZ().getZ() );
core::line3df to_item(m_kart->getXYZ().toIrrVector(),
item->getXYZ().toIrrVector());
for(unsigned int i=0; i<items_to_avoid.size(); i++)
{
if(items_to_avoid[i]->hitLine(to_item, m_kart))
@@ -854,7 +852,7 @@ bool SkiddingAI::hitBadItemWhenAimAt(const Item *item,
* \param last_node
* \return True if th AI should still aim for the pre-selected item.
*/
bool SkiddingAI::handleSelectedItem(float kart_aim_angle, Vec3 *aim_point)
bool SkiddingAI::handleSelectedItem(Vec3 kart_aim_direction, Vec3 *aim_point)
{
// If the item is unavailable keep on testing. It is not necessary
// to test if an item has turned bad, this was tested before this
@@ -863,10 +861,9 @@ bool SkiddingAI::handleSelectedItem(float kart_aim_angle, Vec3 *aim_point)
return false;
const Vec3 &xyz = m_item_to_collect->getXYZ();
float item_angle = atan2(xyz.getX() - m_kart->getXYZ().getX(),
xyz.getZ() - m_kart->getXYZ().getZ());
float angle_to_item = (xyz - m_kart->getXYZ()).angle(kart_aim_direction);
float angle = normalizeAngle(angle_to_item);
float angle = normalizeAngle(kart_aim_angle - item_angle);
if(fabsf(angle)>1.5)
{
// We (likely) have passed the item we were aiming for
@@ -891,7 +888,7 @@ bool SkiddingAI::handleSelectedItem(float kart_aim_angle, Vec3 *aim_point)
* \return True if steering is necessary to avoid an item.
*/
bool SkiddingAI::steerToAvoid(const std::vector<const Item *> &items_to_avoid,
const core::line2df &line_to_target,
const core::line3df &line_to_target,
Vec3 *aim_point)
{
// First determine the left-most and right-most item.
@@ -917,14 +914,19 @@ bool SkiddingAI::steerToAvoid(const std::vector<const Item *> &items_to_avoid,
// Check if we would drive left of the leftmost or right of the
// rightmost point - if so, nothing to do.
core::vector2df left(items_to_avoid[index_left_most]->getXYZ().getX(),
items_to_avoid[index_left_most]->getXYZ().getZ());
const Vec3& left = items_to_avoid[index_left_most]->getXYZ();
int node_index = items_to_avoid[index_left_most]->getGraphNode();
const Vec3& normal = DriveGraph::get()->getNode(node_index)->getNormal();
Vec3 p1 = line_to_target.start,
p2 = line_to_target.getMiddle() + normal.toIrrVector(),
p3 = line_to_target.end;
int item_index = -1;
bool is_left = false;
// >=0 means the point is to the right of the line, or the line is
// to the left of the point.
if(line_to_target.getPointOrientation(left)>=0)
if(left.sideofPlane(p1,p2,p3) <= 0)
{
// Left of leftmost point
item_index = index_left_most;
@@ -932,9 +934,14 @@ bool SkiddingAI::steerToAvoid(const std::vector<const Item *> &items_to_avoid,
}
else
{
core::vector2df right(items_to_avoid[index_right_most]->getXYZ().getX(),
items_to_avoid[index_right_most]->getXYZ().getZ());
if(line_to_target.getPointOrientation(right)<=0)
const Vec3& right = items_to_avoid[index_right_most]->getXYZ();
int node_index = items_to_avoid[index_right_most]->getGraphNode();
const Vec3& normal = DriveGraph::get()->getNode(node_index)->getNormal();
Vec3 p1 = line_to_target.start,
p2 = line_to_target.getMiddle() + normal.toIrrVector(),
p3 = line_to_target.end;
if (right.sideofPlane(p1, p2, p3) >= 0)
{
// Right of rightmost point
item_index = index_right_most;
@@ -975,20 +982,19 @@ bool SkiddingAI::steerToAvoid(const std::vector<const Item *> &items_to_avoid,
float min_distance[2] = {99999.9f, 99999.9f};
int index[2] = {-1, -1};
core::vector2df closest2d[2];
core::vector3df closest3d[2];
for(unsigned int i=0; i<items_to_avoid.size(); i++)
{
const Vec3 &xyz = items_to_avoid[i]->getXYZ();
core::vector2df item2d = xyz.toIrrVector2d();
core::vector2df point2d = line_to_target.getClosestPoint(item2d);
float d = (xyz.toIrrVector2d() - point2d).getLengthSQ();
float direction = line_to_target.getPointOrientation(item2d);
int ind = direction<0 ? 0 : 1;
core::vector3df point3d = line_to_target.getClosestPoint(xyz.toIrrVector());
float d = (xyz.toIrrVector() - point3d).getLengthSQ();
float direction = xyz.sideofPlane(p1,p2,p3);
int ind = direction<0 ? 1 : 0;
if(d<min_distance[ind])
{
min_distance[ind] = d;
index[ind] = i;
closest2d[ind] = point2d;
closest3d[ind] = point3d;
}
}
@@ -998,8 +1004,8 @@ bool SkiddingAI::steerToAvoid(const std::vector<const Item *> &items_to_avoid,
// We are driving between item_to_avoid[index[0]] and ...[1].
// If we don't hit any of them, just keep on driving as normal
bool hit_left = items_to_avoid[index[0]]->hitKart(closest2d[0], m_kart);
bool hit_right = items_to_avoid[index[1]]->hitKart(closest2d[1], m_kart);
bool hit_left = items_to_avoid[index[0]]->hitKart(closest3d[0], m_kart);
bool hit_right = items_to_avoid[index[1]]->hitKart(closest3d[1], m_kart);
if( !hit_left && !hit_right)
return false;
@@ -1033,7 +1039,7 @@ bool SkiddingAI::steerToAvoid(const std::vector<const Item *> &items_to_avoid,
* (NULL if no item was avoided so far).
* \param item_to_collect A pointer to a previously selected item to collect.
*/
void SkiddingAI::evaluateItems(const Item *item, float kart_aim_angle,
void SkiddingAI::evaluateItems(const Item *item, Vec3 kart_aim_direction,
std::vector<const Item *> *items_to_avoid,
std::vector<const Item *> *items_to_collect)
{
@@ -1081,13 +1087,10 @@ void SkiddingAI::evaluateItems(const Item *item, float kart_aim_angle,
// to avoid are collected).
if(!avoid)
{
// item_angle The angle of the item (relative to the forward axis,
// so 0 means straight ahead in world coordinates!).
const Vec3 &xyz = item->getXYZ();
float item_angle = atan2(xyz.getX() - m_kart->getXYZ().getX(),
xyz.getZ() - m_kart->getXYZ().getZ());
float diff = normalizeAngle(kart_aim_angle-item_angle);
float angle_to_item =
(xyz - m_kart->getXYZ()).angle(kart_aim_direction);
float diff = normalizeAngle(angle_to_item);
// The kart is driving at high speed, when the current max speed
// is higher than the max speed of the kart (which is caused by
@@ -1116,14 +1119,14 @@ void SkiddingAI::evaluateItems(const Item *item, float kart_aim_angle,
else
list = items_to_collect;
float new_distance = (item->getXYZ() - m_kart->getXYZ()).length2_2d();
float new_distance = (item->getXYZ() - m_kart->getXYZ()).length2();
// This list is usually very short, so use a simple bubble sort
list->push_back(item);
int i;
for(i=(int)list->size()-2; i>=0; i--)
{
float d = ((*list)[i]->getXYZ() - m_kart->getXYZ()).length2_2d();
float d = ((*list)[i]->getXYZ() - m_kart->getXYZ()).length2();
if(d<=new_distance)
{
break;
@@ -1305,15 +1308,19 @@ void SkiddingAI::handleItems(const float dt)
bool straight_ahead = false;
if (m_kart_behind)
{
posData behind_pos = {0};
checkPosition(m_kart_behind->getXYZ(), &behind_pos);
if (behind_pos.angle < 0.2f) straight_behind = true;
Vec3 behind_lc = m_kart->getTrans().inverse()
(m_kart_behind->getXYZ());
const float abs_angle =
atan2f(fabsf(behind_lc.x()), fabsf(behind_lc.z()));
if (abs_angle < 0.2f) straight_behind = true;
}
if (m_kart_ahead)
{
posData ahead_pos = {0};
checkPosition(m_kart_ahead->getXYZ(), &ahead_pos);
if (ahead_pos.angle < 0.2f) straight_ahead = true;
Vec3 ahead_lc = m_kart->getTrans().inverse()
(m_kart_ahead->getXYZ());
const float abs_angle =
atan2f(fabsf(ahead_lc.x()), fabsf(ahead_lc.z()));
if (abs_angle < 0.2f) straight_ahead = true;
}
// Bowling balls are slower, so only fire on closer karts - but when
@@ -1728,14 +1735,14 @@ void SkiddingAI::handleNitroAndZipper()
m_kart->getSpeed()>1.0f &&
m_kart->getSpeedIncreaseTimeLeft(MaxSpeed::MS_INCREASE_ZIPPER)<=0)
{
GraphNode::DirectionType dir;
DriveNode::DirectionType dir;
unsigned int last;
const GraphNode &gn = QuadGraph::get()->getNode(m_track_node);
gn.getDirectionData(m_successor_index[m_track_node], &dir, &last);
if(dir==GraphNode::DIR_STRAIGHT)
const DriveNode* dn = DriveGraph::get()->getNode(m_track_node);
dn->getDirectionData(m_successor_index[m_track_node], &dir, &last);
if(dir==DriveNode::DIR_STRAIGHT)
{
float diff = QuadGraph::get()->getDistanceFromStart(last)
- QuadGraph::get()->getDistanceFromStart(m_track_node);
float diff = DriveGraph::get()->getDistanceFromStart(last)
- DriveGraph::get()->getDistanceFromStart(m_track_node);
if(diff<0) diff+=World::getWorld()->getTrack()->getTrackLength();
if(diff>m_ai_properties->m_straight_length_for_zipper)
m_controls->setFire(true);
@@ -1779,16 +1786,14 @@ void SkiddingAI::checkCrashes(const Vec3& pos )
const size_t NUM_KARTS = m_world->getNumKarts();
//Protection against having vel_normal with nan values
const Vec3 &VEL = m_kart->getVelocity();
Vec3 vel_normal(VEL.getX(), 0.0, VEL.getZ());
float speed=vel_normal.length();
float speed = m_kart->getVelocity().length();
// If the velocity is zero, no sense in checking for crashes in time
if(speed==0) return;
Vec3 vel_normal = m_kart->getVelocity().normalized();
// Time it takes to drive for m_kart_length units.
float dt = m_kart_length / speed;
vel_normal/=speed;
int current_node = m_track_node;
if(steps<1 || steps>1000)
@@ -1818,7 +1823,7 @@ void SkiddingAI::checkCrashes(const Vec3& pos )
continue;
Vec3 other_kart_xyz = other_kart->getXYZ()
+ other_kart->getVelocity()*(i*dt);
float kart_distance = (step_coord - other_kart_xyz).length_2d();
float kart_distance = (step_coord - other_kart_xyz).length();
if( kart_distance < m_kart_length)
m_crashes.m_kart = j;
@@ -1826,12 +1831,12 @@ void SkiddingAI::checkCrashes(const Vec3& pos )
}
/*Find if we crash with the drivelines*/
if(current_node!=QuadGraph::UNKNOWN_SECTOR &&
if(current_node!=Graph::UNKNOWN_SECTOR &&
m_next_node_index[current_node]!=-1)
QuadGraph::get()->findRoadSector(step_coord, &current_node,
DriveGraph::get()->findRoadSector(step_coord, &current_node,
/* sectors to test*/ &m_all_look_aheads[current_node]);
if( current_node == QuadGraph::UNKNOWN_SECTOR)
if( current_node == Graph::UNKNOWN_SECTOR)
{
m_crashes.m_road = true;
return;
@@ -1879,23 +1884,23 @@ void SkiddingAI::findNonCrashingPointNew(Vec3 *result, int *last_node)
*last_node = m_next_node_index[m_track_node];
const core::vector2df xz = m_kart->getXYZ().toIrrVector2d();
const Quad &q = QuadGraph::get()->getQuadOfNode(*last_node);
const DriveNode* dn = DriveGraph::get()->getNode(*last_node);
// Index of the left and right end of a quad.
const unsigned int LEFT_END_POINT = 0;
const unsigned int RIGHT_END_POINT = 1;
core::line2df left (xz, q[LEFT_END_POINT ].toIrrVector2d());
core::line2df right(xz, q[RIGHT_END_POINT].toIrrVector2d());
core::line2df left (xz, (*dn)[LEFT_END_POINT ].toIrrVector2d());
core::line2df right(xz, (*dn)[RIGHT_END_POINT].toIrrVector2d());
#if defined(AI_DEBUG) && defined(AI_DEBUG_NEW_FIND_NON_CRASHING)
const Vec3 eps1(0,0.5f,0);
m_curve[CURVE_LEFT]->clear();
m_curve[CURVE_LEFT]->addPoint(m_kart->getXYZ()+eps1);
m_curve[CURVE_LEFT]->addPoint(q[LEFT_END_POINT]+eps1);
m_curve[CURVE_LEFT]->addPoint((*dn)[LEFT_END_POINT]+eps1);
m_curve[CURVE_LEFT]->addPoint(m_kart->getXYZ()+eps1);
m_curve[CURVE_RIGHT]->clear();
m_curve[CURVE_RIGHT]->addPoint(m_kart->getXYZ()+eps1);
m_curve[CURVE_RIGHT]->addPoint(q[RIGHT_END_POINT]+eps1);
m_curve[CURVE_RIGHT]->addPoint((*dn)[RIGHT_END_POINT]+eps1);
m_curve[CURVE_RIGHT]->addPoint(m_kart->getXYZ()+eps1);
#endif
#if defined(AI_DEBUG_KART_HEADING) || defined(AI_DEBUG_NEW_FIND_NON_CRASHING)
@@ -1908,13 +1913,13 @@ void SkiddingAI::findNonCrashingPointNew(Vec3 *result, int *last_node)
while(1)
{
unsigned int next_sector = m_next_node_index[*last_node];
const Quad &q_next = QuadGraph::get()->getQuadOfNode(next_sector);
const DriveNode* dn_next = DriveGraph::get()->getNode(next_sector);
// Test if the next left point is to the right of the left
// line. If so, a new left line is defined.
if(left.getPointOrientation(q_next[LEFT_END_POINT].toIrrVector2d())
if(left.getPointOrientation((*dn_next)[LEFT_END_POINT].toIrrVector2d())
< 0 )
{
core::vector2df p = q_next[LEFT_END_POINT].toIrrVector2d();
core::vector2df p = (*dn_next)[LEFT_END_POINT].toIrrVector2d();
// Stop if the new point is to the right of the right line
if(right.getPointOrientation(p)<0)
break;
@@ -1930,10 +1935,10 @@ void SkiddingAI::findNonCrashingPointNew(Vec3 *result, int *last_node)
// Test if new right point is to the left of the right line. If
// so, a new right line is defined.
if(right.getPointOrientation(q_next[RIGHT_END_POINT].toIrrVector2d())
if(right.getPointOrientation((*dn_next)[RIGHT_END_POINT].toIrrVector2d())
> 0 )
{
core::vector2df p = q_next[RIGHT_END_POINT].toIrrVector2d();
core::vector2df p = (*dn_next)[RIGHT_END_POINT].toIrrVector2d();
// Break if new point is to the left of left line
if(left.getPointOrientation(p)>0)
break;
@@ -1955,7 +1960,7 @@ void SkiddingAI::findNonCrashingPointNew(Vec3 *result, int *last_node)
// 0.5f*(left.end.Y+right.end.Y));
//*result = ppp;
*result = QuadGraph::get()->getQuadOfNode(*last_node).getCenter();
*result = DriveGraph::get()->getNode(*last_node)->getCenter();
} // findNonCrashingPointNew
//-----------------------------------------------------------------------------
@@ -1992,10 +1997,10 @@ void SkiddingAI::findNonCrashingPointFixed(Vec3 *aim_position, int *last_node)
target_sector = m_next_node_index[*last_node];
//direction is a vector from our kart to the sectors we are testing
direction = QuadGraph::get()->getQuadOfNode(target_sector).getCenter()
direction = DriveGraph::get()->getNode(target_sector)->getCenter()
- m_kart->getXYZ();
float len=direction.length_2d();
float len=direction.length();
unsigned int steps = (unsigned int)( len / m_kart_length );
if( steps < 3 ) steps = 3;
@@ -2015,23 +2020,23 @@ void SkiddingAI::findNonCrashingPointFixed(Vec3 *aim_position, int *last_node)
{
step_coord = m_kart->getXYZ()+direction*m_kart_length * float(i);
QuadGraph::get()->spatialToTrack(&step_track_coord, step_coord,
DriveGraph::get()->spatialToTrack(&step_track_coord, step_coord,
*last_node );
float distance = fabsf(step_track_coord[0]);
//If we are outside, the previous node is what we are looking for
if ( distance + m_kart_width * 0.5f
> QuadGraph::get()->getNode(*last_node).getPathWidth()*0.5f )
> DriveGraph::get()->getNode(*last_node)->getPathWidth()*0.5f )
{
*aim_position = QuadGraph::get()->getQuadOfNode(*last_node)
.getCenter();
*aim_position = DriveGraph::get()->getNode(*last_node)
->getCenter();
return;
}
}
*last_node = target_sector;
} // for i<100
*aim_position = QuadGraph::get()->getQuadOfNode(*last_node).getCenter();
*aim_position = DriveGraph::get()->getNode(*last_node)->getCenter();
} // findNonCrashingPointFixed
//-----------------------------------------------------------------------------
@@ -2039,14 +2044,14 @@ void SkiddingAI::findNonCrashingPointFixed(Vec3 *aim_position, int *last_node)
* 1. the test:
*
* distance + m_kart_width * 0.5f
* > QuadGraph::get()->getNode(*last_node).getPathWidth() )
* > DriveGraph::get()->getNode(*last_node)->getPathWidth() )
*
* is incorrect, it should compare with getPathWith*0.5f (since distance
* is the distance from the center, i.e. it is half the path width if
* the point is at the edge).
* 2. the test:
*
* QuadGraph::get()->spatialToTrack(&step_track_coord, step_coord,
* DriveGraph::get()->spatialToTrack(&step_track_coord, step_coord,
* *last_node );
* in the for loop tests always against distance from the same
* graph node (*last_node), while de-fact the loop will test points
@@ -2071,7 +2076,7 @@ void SkiddingAI::findNonCrashingPointFixed(Vec3 *aim_position, int *last_node)
m_curve[CURVE_KART]->addPoint(m_kart->getTrans()(forw)+eps);
#endif
*last_node = m_next_node_index[m_track_node];
float angle = QuadGraph::get()->getAngleToNext(m_track_node,
float angle = DriveGraph::get()->getAngleToNext(m_track_node,
m_successor_index[m_track_node]);
int target_sector;
@@ -2087,7 +2092,7 @@ void SkiddingAI::findNonCrashingPointFixed(Vec3 *aim_position, int *last_node)
// target_sector is the sector at the longest distance that we can
// drive to without crashing with the track.
target_sector = m_next_node_index[*last_node];
angle1 = QuadGraph::get()->getAngleToNext(target_sector,
angle1 = DriveGraph::get()->getAngleToNext(target_sector,
m_successor_index[target_sector]);
// In very sharp turns this algorithm tends to aim at off track points,
// resulting in hitting a corner. So test for this special case and
@@ -2095,16 +2100,16 @@ void SkiddingAI::findNonCrashingPointFixed(Vec3 *aim_position, int *last_node)
float diff = normalizeAngle(angle1-angle);
if(fabsf(diff)>1.5f)
{
*aim_position = QuadGraph::get()->getQuadOfNode(target_sector)
.getCenter();
*aim_position = DriveGraph::get()->getNode(target_sector)
->getCenter();
return;
}
//direction is a vector from our kart to the sectors we are testing
direction = QuadGraph::get()->getQuadOfNode(target_sector).getCenter()
direction = DriveGraph::get()->getNode(target_sector)->getCenter()
- m_kart->getXYZ();
float len=direction.length_2d();
float len=direction.length();
unsigned int steps = (unsigned int)( len / m_kart_length );
if( steps < 3 ) steps = 3;
@@ -2124,24 +2129,24 @@ void SkiddingAI::findNonCrashingPointFixed(Vec3 *aim_position, int *last_node)
{
step_coord = m_kart->getXYZ()+direction*m_kart_length * float(i);
QuadGraph::get()->spatialToTrack(&step_track_coord, step_coord,
DriveGraph::get()->spatialToTrack(&step_track_coord, step_coord,
*last_node );
float distance = fabsf(step_track_coord[0]);
//If we are outside, the previous node is what we are looking for
if ( distance + m_kart_width * 0.5f
> QuadGraph::get()->getNode(*last_node).getPathWidth() )
> DriveGraph::get()->getNode(*last_node)->getPathWidth() )
{
*aim_position = QuadGraph::get()->getQuadOfNode(*last_node)
.getCenter();
*aim_position = DriveGraph::get()->getNode(*last_node)
->getCenter();
return;
}
}
angle = angle1;
*last_node = target_sector;
} // for i<100
*aim_position = QuadGraph::get()->getQuadOfNode(*last_node).getCenter();
*aim_position = DriveGraph::get()->getNode(*last_node)->getCenter();
} // findNonCrashingPoint
//-----------------------------------------------------------------------------
@@ -2150,10 +2155,17 @@ void SkiddingAI::findNonCrashingPointFixed(Vec3 *aim_position, int *last_node)
*/
void SkiddingAI::determineTrackDirection()
{
const QuadGraph *qg = QuadGraph::get();
unsigned int succ = m_successor_index[m_track_node];
float angle_to_track = qg->getNode(m_track_node).getAngleToSuccessor(succ)
- m_kart->getHeading();
const DriveGraph *dg = DriveGraph::get();
unsigned int succ = m_successor_index[m_track_node];
unsigned int next = dg->getNode(m_track_node)->getSuccessor(succ);
float angle_to_track = 0.0f;
if (m_kart->getVelocity().length() > 0.0f)
{
Vec3 track_direction = -dg->getNode(m_track_node)->getCenter()
+ dg->getNode(next)->getCenter();
angle_to_track =
track_direction.angle(m_kart->getVelocity().normalized());
}
angle_to_track = normalizeAngle(angle_to_track);
// In certain circumstances (esp. S curves) it is possible that the
@@ -2169,26 +2181,24 @@ void SkiddingAI::determineTrackDirection()
// quicker be aligned with the track again).
if(fabsf(angle_to_track) > 0.22222f * M_PI)
{
m_current_track_direction = GraphNode::DIR_UNDEFINED;
m_current_track_direction = DriveNode::DIR_UNDEFINED;
return;
}
unsigned int next = qg->getNode(m_track_node).getSuccessor(succ);
qg->getNode(next).getDirectionData(m_successor_index[next],
&m_current_track_direction,
&m_last_direction_node);
dg->getNode(next)->getDirectionData(m_successor_index[next],
&m_current_track_direction,
&m_last_direction_node);
#ifdef AI_DEBUG
m_curve[CURVE_QG]->clear();
for(unsigned int i=m_track_node; i<=m_last_direction_node; i++)
{
m_curve[CURVE_QG]->addPoint(qg->getNode(i).getCenter());
m_curve[CURVE_QG]->addPoint(dg->getNode(i)->getCenter());
}
#endif
if(m_current_track_direction==GraphNode::DIR_LEFT ||
m_current_track_direction==GraphNode::DIR_RIGHT )
if(m_current_track_direction==DriveNode::DIR_LEFT ||
m_current_track_direction==DriveNode::DIR_RIGHT )
{
handleCurve();
} // if(m_current_track_direction == DIR_LEFT || DIR_RIGHT )
@@ -2213,13 +2223,10 @@ void SkiddingAI::handleCurve()
// kart will already point towards the direction of the circle), and
// the case that the kart is facing wrong was already tested for before
const QuadGraph *qg = QuadGraph::get();
Vec3 xyz = m_kart->getXYZ();
Vec3 tangent = m_kart->getTrans()(Vec3(0,0,1)) - xyz;
Vec3 last_xyz = qg->getNode(m_last_direction_node).getCenter();
const DriveGraph *dg = DriveGraph::get();
const Vec3& last_xyz = dg->getNode(m_last_direction_node)->getCenter();
determineTurnRadius(xyz, tangent, last_xyz,
&m_curve_center, &m_current_curve_radius);
determineTurnRadius(last_xyz, &m_curve_center, &m_current_curve_radius);
assert(!std::isnan(m_curve_center.getX()));
assert(!std::isnan(m_curve_center.getY()));
assert(!std::isnan(m_curve_center.getZ()));
@@ -2233,13 +2240,14 @@ void SkiddingAI::handleCurve()
{
i = m_next_node_index[i];
// Pick either the lower left or right point:
int index = m_current_track_direction==GraphNode::DIR_LEFT
int index = m_current_track_direction==DriveNode::DIR_LEFT
? 0 : 1;
float r = (m_curve_center - qg->getQuadOfNode(i)[index]).length();
Vec3 curve_center_wc = m_kart->getTrans()(m_curve_center);
float r = (curve_center_wc - *(dg->getNode(i))[index]).length();
if(m_current_curve_radius < r)
{
last_xyz = qg->getQuadOfNode(i)[index];
determineTurnRadius(xyz, tangent, last_xyz,
last_xyz = *(dg->getNode(i)[index]);
determineTurnRadius(last_xyz,
&m_curve_center, &m_current_curve_radius);
m_last_direction_node = i;
break;
@@ -2249,11 +2257,11 @@ void SkiddingAI::handleCurve()
}
#endif
#if defined(AI_DEBUG) && defined(AI_DEBUG_CIRCLES)
m_curve[CURVE_PREDICT1]->makeCircle(m_curve_center,
m_curve[CURVE_PREDICT1]->makeCircle(m_kart->getTrans()(m_curve_center),
m_current_curve_radius);
m_curve[CURVE_PREDICT1]->addPoint(last_xyz);
m_curve[CURVE_PREDICT1]->addPoint(m_curve_center);
m_curve[CURVE_PREDICT1]->addPoint(xyz);
m_curve[CURVE_PREDICT1]->addPoint(m_kart->getTrans()(m_curve_center));
m_curve[CURVE_PREDICT1]->addPoint(m_kart->getXYZ());
#endif
} // handleCurve
@@ -2292,8 +2300,8 @@ bool SkiddingAI::canSkid(float steer_fraction)
}
// No skidding on straights
if(m_current_track_direction==GraphNode::DIR_STRAIGHT ||
m_current_track_direction==GraphNode::DIR_UNDEFINED )
if(m_current_track_direction==DriveNode::DIR_STRAIGHT ||
m_current_track_direction==DriveNode::DIR_UNDEFINED )
{
#ifdef DEBUG
if(m_controls->getSkidControl() && m_ai_debug)
@@ -2307,18 +2315,19 @@ bool SkiddingAI::canSkid(float steer_fraction)
}
const float MIN_SKID_SPEED = 5.0f;
const QuadGraph *qg = QuadGraph::get();
Vec3 last_xyz = qg->getNode(m_last_direction_node).getCenter();
const DriveGraph *dg = DriveGraph::get();
Vec3 last_xyz = m_kart->getTrans().inverse()
(dg->getNode(m_last_direction_node)->getCenter());
// Only try skidding when a certain minimum speed is reached.
if(m_kart->getSpeed()<MIN_SKID_SPEED) return false;
// Estimate how long it takes to finish the curve
Vec3 diff_kart = m_kart->getXYZ() - m_curve_center;
Vec3 diff_last = last_xyz - m_curve_center;
Vec3 diff_kart = -m_curve_center;
Vec3 diff_last = last_xyz - m_curve_center;
float angle_kart = atan2(diff_kart.getX(), diff_kart.getZ());
float angle_last = atan2(diff_last.getX(), diff_last.getZ());
float angle = m_current_track_direction == GraphNode::DIR_RIGHT
float angle = m_current_track_direction == DriveNode::DIR_RIGHT
? angle_last - angle_kart
: angle_kart - angle_last;
angle = normalizeAngle(angle);
@@ -2345,9 +2354,9 @@ bool SkiddingAI::canSkid(float steer_fraction)
// left turn steer right to avoid getting too close to the left
// vorder). In this case skidding will be useless.
else if( (steer_fraction > 0 &&
m_current_track_direction==GraphNode::DIR_LEFT) ||
m_current_track_direction==DriveNode::DIR_LEFT) ||
(steer_fraction < 0 &&
m_current_track_direction==GraphNode::DIR_RIGHT) )
m_current_track_direction==DriveNode::DIR_RIGHT) )
{
#ifdef DEBUG
if(m_controls->getSkidControl() && m_ai_debug)
@@ -2499,55 +2508,3 @@ void SkiddingAI::setSteering(float angle, float dt)
} // setSteering
// ----------------------------------------------------------------------------
/** Determine the center point and radius of a circle given two points on
* the ccircle and the tangent at the first point. This is done as follows:
* 1. Determine the line going through the center point start+end, which is
* orthogonal to the vector from start to end. This line goes through the
* center of the circle.
* 2. Determine the line going through the first point and is orthogonal
* to the given tangent.
* 3. The intersection of these two lines is the center of the circle.
* \param[in] start First point.
* \param[in] tangent Tangent at first point.
* \param[in] end Second point on circle.
* \param[out] center Center point of the circle.
* \param[out] radius Radius of the circle.
*/
void SkiddingAI::determineTurnRadius(const Vec3 &start,
const Vec3 &tangent,
const Vec3 &end,
Vec3 *center,
float *radius)
{
// 1) Line through middle of start+end
Vec3 mid = 0.5f*(start+end);
Vec3 direction = end-start;
Vec3 orthogonal(direction.getZ(), 0, -direction.getX());
Vec3 q1 = mid + orthogonal;
irr::core::line2df line1(mid.getX(), mid.getZ(),
q1.getX(), q1.getZ() );
Vec3 ortho_tangent(tangent.getZ(), 0, -tangent.getX());
Vec3 q2 = start + ortho_tangent;
irr::core::line2df line2(start.getX(), start.getZ(),
q2.getX(), q2.getZ());
irr::core::vector2df result;
if(line1.intersectWith(line2, result, /*checkOnlySegments*/false))
{
*center = Vec3(result.X, start.getY(), result.Y);
*radius = (start - *center).length();
}
else
{
// No intersection. In this case assume that the two points are
// on a semicircle, in which case the center is at 0.5*(start+end):
*center = 0.5f*(start+end);
*radius = 0.5f*(end-start).length();
}
return;
} // determineTurnRadius

View File

@@ -46,7 +46,7 @@
#include "karts/controller/ai_base_lap_controller.hpp"
#include "race/race_manager.hpp"
#include "tracks/graph_node.hpp"
#include "tracks/drive_node.hpp"
#include "utils/random_generator.hpp"
#ifdef AI_DEBUG
@@ -111,7 +111,7 @@ private:
int m_start_kart_crash_direction;
/** The direction of the track where the kart is on atm. */
GraphNode::DirectionType m_current_track_direction;
DriveNode::DirectionType m_current_track_direction;
/** The radius of the curve the kart is currently driving. Undefined
* when being on a straigt section. */
@@ -205,13 +205,13 @@ private:
void computeNearestKarts();
void handleItemCollectionAndAvoidance(Vec3 *aim_point,
int last_node);
bool handleSelectedItem(float kart_aim_angle, Vec3 *aim_point);
bool handleSelectedItem(Vec3 kart_aim_direction, Vec3 *aim_point);
bool steerToAvoid(const std::vector<const Item *> &items_to_avoid,
const core::line2df &line_to_target,
const core::line3df &line_to_target,
Vec3 *aim_point);
bool hitBadItemWhenAimAt(const Item *item,
const std::vector<const Item *> &items_to_avoid);
void evaluateItems(const Item *item, float kart_aim_angle,
void evaluateItems(const Item *item, Vec3 kart_aim_direction,
std::vector<const Item *> *items_to_avoid,
std::vector<const Item *> *items_to_collect);
@@ -221,11 +221,6 @@ private:
void findNonCrashingPoint(Vec3 *result, int *last_node);
void determineTrackDirection();
void determineTurnRadius(const Vec3 &start,
const Vec3 &start_direction,
const Vec3 &end,
Vec3 *center,
float *radius);
virtual bool canSkid(float steer_fraction);
virtual void setSteering(float angle, float dt);
void handleCurve();

View File

@@ -76,7 +76,8 @@ ExplosionAnimation::ExplosionAnimation(AbstractKart *kart,
: AbstractKartAnimation(kart, "ExplosionAnimation")
{
m_xyz = m_kart->getXYZ();
m_orig_y = m_xyz.getY();
m_orig_xyz = m_xyz;
m_normal = m_kart->getNormal();
m_kart->playCustomSFX(SFXManager::CUSTOM_EXPLODE);
m_timer = m_kart->getKartProperties()->getExplosionDuration();
@@ -141,13 +142,12 @@ ExplosionAnimation::~ExplosionAnimation()
void ExplosionAnimation::update(float dt)
{
m_velocity -= dt*World::getWorld()->getTrack()->getGravity();
m_xyz.setY(m_xyz.getY() + dt*m_velocity);
m_xyz = m_xyz + dt*m_velocity*m_normal;
// Make sure the kart does not end up under the track
if(m_xyz.getY()<m_orig_y)
if ((m_xyz - m_orig_xyz).dot(m_normal)<0)
{
m_xyz.setY(m_orig_y);
m_xyz = m_orig_xyz;
// This will trigger the end of the animations
m_timer = -1;
}

View File

@@ -37,13 +37,17 @@
class ExplosionAnimation: public AbstractKartAnimation
{
protected:
/** The coordinates where the kart was hit originally. */
/** The coordinates where the kart was hit originally, it will be increased
* later. */
Vec3 m_xyz;
/** The original Y coordinate. The kart needs to be restored accurately
/** The original coordinates. The kart needs to be restored accurately
* otherwise due to floating point errors, time step size variations,
* a kart can be restarted under the track. */
float m_orig_y;
Vec3 m_orig_xyz;
/** The normal of kart when it started to explode. */
Vec3 m_normal;
/** The kart's current rotation. */
Vec3 m_curr_rotation;

View File

@@ -73,8 +73,11 @@
#include "physics/physics.hpp"
#include "race/history.hpp"
#include "tracks/terrain_info.hpp"
#include "tracks/drive_graph.hpp"
#include "tracks/drive_node.hpp"
#include "tracks/track.hpp"
#include "tracks/track_manager.hpp"
#include "tracks/track_sector.hpp"
#include "utils/constants.hpp"
#include "utils/log.hpp" //TODO: remove after debugging is done
#include "utils/vs.hpp"
@@ -310,7 +313,10 @@ void Kart::reset()
// mode) - but only if they actually have a body (e.g. ghost karts
// don't have one).
if(m_body)
{
World::getWorld()->getPhysics()->removeKart(this);
World::getWorld()->getPhysics()->addKart(this);
}
m_min_nitro_time = 0.0f;
@@ -404,7 +410,10 @@ void Kart::reset()
Vec3 front(0, 0, getKartLength()*0.5f);
m_xyz_front = getTrans()(front);
m_terrain_info->update(getTrans().getBasis());
// Base on update() below, require if starting point of kart is not near
// 0, 0, 0 (like in battle arena)
m_terrain_info->update(getTrans().getBasis(),
getTrans().getOrigin() + getTrans().getBasis() * Vec3(0, 0.3f, 0));
// Reset is also called when the kart is created, at which time
// m_controller is not yet defined, so this has to be tested here.
@@ -541,7 +550,7 @@ void Kart::blockViewWithPlunger()
btTransform Kart::getAlignedTransform(const float custom_pitch)
{
btTransform trans = getTrans();
/*
float pitch = (custom_pitch == -1 ? getTerrainPitch(getHeading())
: custom_pitch);
@@ -549,7 +558,12 @@ btTransform Kart::getAlignedTransform(const float custom_pitch)
m.setEulerZYX(pitch, getHeading()+m_skidding->getVisualSkidRotation(),
0.0f);
trans.setBasis(m);
*/
btTransform trans2;
trans2.setIdentity();
trans2.setRotation(btQuaternion(m_skidding->getVisualSkidRotation(), 0, 0));
trans *= trans2;
return trans;
} // getAlignedTransform
@@ -695,10 +709,8 @@ void Kart::createPhysics()
wheel.m_frictionSlip = m_kart_properties->getFrictionSlip();
wheel.m_rollInfluence = m_kart_properties->getStabilityRollInfluence();
}
// Obviously these allocs have to be properly managed/freed
btTransform t;
t.setIdentity();
World::getWorld()->getPhysics()->addKart(this);
// Body to be added in reset() which allows complete reset kart when
// restarting the race
} // createPhysics
@@ -1070,7 +1082,7 @@ bool Kart::isOnGround() const
*/
bool Kart::isNearGround() const
{
if(m_terrain_info->getHoT()==Track::NOHIT)
if((m_terrain_info->getHitPoint() - getXYZ()).length() ==Track::NOHIT)
return false;
else
return ((getXYZ().getY() - m_terrain_info->getHoT())
@@ -1207,6 +1219,8 @@ void Kart::update(float dt)
// Update the position and other data taken from the physics
Moveable::update(dt);
Vec3 front(0, 0, getKartLength()*0.5f);
m_xyz_front = getTrans()(front);
// Update the locally maintained speed of the kart (m_speed), which
// is used furthermore for engine power, camera distance etc
updateSpeed();
@@ -1284,7 +1298,10 @@ void Kart::update(float dt)
m_kart_properties->getStabilityChassisAngularDamping());
}
if(m_kart_animation)
// Used to prevent creating a rescue animation after an explosion animation
// got deleted
const bool has_animation_before = m_kart_animation!= NULL;
if(has_animation_before)
m_kart_animation->update(dt);
m_attachment->update(dt);
@@ -1325,13 +1342,31 @@ void Kart::update(float dt)
// But only do this if auto-rescue is enabled (i.e. it will be disabled in
// battle mode), and the material the kart is driving on does not have
// gravity (which atm affects the roll angle).
if(World::getWorld()->getTrack()->isAutoRescueEnabled() &&
(!m_terrain_info->getMaterial() ||
!m_terrain_info->getMaterial()->hasGravity()) &&
!getKartAnimation() && fabs(getRoll())>60*DEGREE_TO_RAD &&
fabs(getSpeed())<3.0f )
// To be used later
float dist_to_sector = 0.0f;
LinearWorld* lw = dynamic_cast<LinearWorld*>(World::getWorld());
if (lw && DriveGraph::get())
{
new RescueAnimation(this, /*is_auto_rescue*/true);
const int sector =
lw->getTrackSector(getWorldKartId())->getCurrentGraphNode();
dist_to_sector = getXYZ().distance
(DriveGraph::get()->getNode(sector)->getCenter());
const Vec3& quad_normal = DriveGraph::get()->getNode(sector)
->getNormal();
const btQuaternion& q = getTrans().getRotation();
const float roll = quad_normal.angle
((Vec3(0, 1, 0).rotate(q.getAxis(), q.getAngle())));
if (World::getWorld()->getTrack()->isAutoRescueEnabled() &&
(!m_terrain_info->getMaterial() ||
!m_terrain_info->getMaterial()->hasGravity()) &&
!has_animation_before && fabs(roll) > 60 * DEGREE_TO_RAD &&
fabs(getSpeed()) < 3.0f)
{
new RescueAnimation(this, /*is_auto_rescue*/true);
}
}
// Make sure that the ray doesn't hit the kart. This is done by
@@ -1344,12 +1379,9 @@ void Kart::update(float dt)
m_body->getBroadphaseHandle()->m_collisionFilterGroup = 0;
}
Vec3 front(0, 0, getKartLength()*0.5f);
m_xyz_front = getTrans()(front);
// After the physics step was done, the position of the wheels (as stored
// in wheelInfo) is actually outdated, since the chassis was moved
// according to the force acting from the wheels. So the cnter of the
// according to the force acting from the wheels. So the center of the
// chassis is not at the center of the wheels anymore, it is somewhat
// moved forward (depending on speed and fps). In very extreme cases
// (see bug 2246) the center of the chassis can actually be ahead of the
@@ -1371,7 +1403,7 @@ void Kart::update(float dt)
// partly tunnels through the track). While tunneling should not be
// happening (since Z velocity is clamped), the epsilon is left in place
// just to be on the safe side (it will not hit the chassis itself).
from = from/4 + Vec3(0,0.3f,0);
from = from/4 + (getTrans().getBasis() * Vec3(0,0.3f,0));
m_terrain_info->update(getTrans().getBasis(), from);
@@ -1395,7 +1427,8 @@ void Kart::update(float dt)
// let kart fall a bit before rescuing
const Vec3 *min, *max;
World::getWorld()->getTrack()->getAABB(&min, &max);
if(min->getY() - getXYZ().getY() > 17 && !m_flying &&
if((min->getY() - getXYZ().getY() > 17 || dist_to_sector > 25) && !m_flying &&
!getKartAnimation())
new RescueAnimation(this);
}
@@ -1995,13 +2028,13 @@ void Kart::crashed(const Material *m, const Vec3 &normal)
World::getWorld()->getTrack()->isPushBackEnabled())
{
int sector = lw->getSectorForKart(this);
if(sector!=QuadGraph::UNKNOWN_SECTOR)
if(sector!=Graph::UNKNOWN_SECTOR)
{
// Use the first predecessor node, which is the most
// natural one (i.e. the one on the main driveline).
const GraphNode &gn = QuadGraph::get()->getNode(
QuadGraph::get()->getNode(sector).getPredecessor(0));
Vec3 impulse = gn.getCenter() - getXYZ();
const DriveNode* dn = DriveGraph::get()->getNode(
DriveGraph::get()->getNode(sector)->getPredecessor(0));
Vec3 impulse = dn->getCenter() - getXYZ();
impulse.setY(0);
if(impulse.getX() || impulse.getZ())
impulse.normalize();
@@ -2881,4 +2914,13 @@ void Kart::setOnScreenText(const wchar_t *text)
// when the parent is deleted.
} // setOnScreenText
// ------------------------------------------------------------------------
/** Returns the normal of the terrain the kart is over atm. This is
* defined even if the kart is flying.
*/
const Vec3& Kart::getNormal() const
{
return m_terrain_info->getNormal();
} // getNormal
/* EOF */

View File

@@ -401,7 +401,7 @@ public:
// ------------------------------------------------------------------------
/** Returns true if the kart is close to the ground, used to dis/enable
* the upright constraint to allow for more realistic explosions. */
bool isNearGround () const;
bool isNearGround() const;
// ------------------------------------------------------------------------
/** Returns true if the kart is eliminated. */
virtual bool isEliminated() const { return m_eliminated; }
@@ -446,6 +446,10 @@ public:
// ------------------------------------------------------------------------
virtual void setOnScreenText(const wchar_t *text);
// ------------------------------------------------------------------------
/** Returns the normal of the terrain the kart is over atm. This is
* defined even if the kart is flying. */
virtual const Vec3& getNormal() const;
// ------------------------------------------------------------------------
/** For debugging only: check if a kart is flying. */
bool isFlying() const { return m_flying; }
// ------------------------------------------------------------------------

View File

@@ -140,8 +140,8 @@ void Moveable::update(float dt)
*/
void Moveable::updatePosition()
{
Vec3 forw_vec = m_transform.getBasis().getColumn(0);
m_heading = -atan2f(forw_vec.getZ(), forw_vec.getX());
Vec3 forw_vec = m_transform.getBasis().getColumn(2);
m_heading = atan2f(forw_vec.getX(), forw_vec.getZ());
// The pitch in hpr is in between -pi and pi. But for the camera it
// must be restricted to -pi/2 and pi/2 - so recompute it by restricting

View File

@@ -89,15 +89,23 @@ public:
m_transform.setOrigin(a);
if(m_motion_state)
m_motion_state->setWorldTransform(m_transform);
}
} // setXYZ
// ------------------------------------------------------------------------
/** Sets the rotation of this moveable. */
void setRotation(const btQuaternion&a)
/** Sets the rotation of the physical body this moveable. */
void setRotation(const btMatrix3x3 &m)
{
m_transform.setRotation(a);
m_transform.setBasis(m);
if(m_motion_state)
m_motion_state->setWorldTransform(m_transform);
}
} // setRotation(btMatrix3x3)
// ------------------------------------------------------------------------
/** Sets the rotation of the physical body this moveable. */
void setRotation(const btQuaternion &q)
{
m_transform.setRotation(q);
if(m_motion_state)
m_motion_state->setWorldTransform(m_transform);
} // setRotation(btQuaternion)
// ------------------------------------------------------------------------
virtual void updateGraphics(float dt, const Vec3& off_xyz,
const btQuaternion& off_rotation);

View File

@@ -45,11 +45,20 @@ RescueAnimation::RescueAnimation(AbstractKart *kart, bool is_auto_rescue)
m_kart->getAttachment()->clear();
m_curr_rotation.setPitch(m_kart->getPitch());
m_curr_rotation.setRoll(m_kart->getRoll() );
m_curr_rotation.setHeading(0);
m_add_rotation = -m_curr_rotation/m_timer;
m_curr_rotation.setHeading(m_kart->getHeading());
// Get the current rotation of the kart
m_curr_rotation = m_kart->getNode()->getRotation() * DEGREE_TO_RAD;
// Determine the rotation that will rotate the kart from the current
// up direction to the right up direction it should have according to
// the normal at the kart's location
Vec3 up = m_kart->getTrans().getBasis().getColumn(1);
btQuaternion q = shortestArcQuat(up, m_kart->getNormal());
// Store this rotation as 'delta HPR', which is added over time to the
// current rotation to end up (after m_timer seconds) with the right up
// rotation
m_add_rotation.setHPR(q);
m_add_rotation /= m_timer;
// Add a hit unless it was auto-rescue
if(race_manager->getMinorMode()==RaceManager::MINOR_MODE_3_STRIKES &&
@@ -98,13 +107,13 @@ RescueAnimation::~RescueAnimation()
*/
void RescueAnimation::update(float dt)
{
m_xyz.setY(m_xyz.getY() + dt*m_velocity);
m_xyz += dt*m_velocity * m_kart->getNormal();
m_kart->setXYZ(m_xyz);
m_curr_rotation += dt*m_add_rotation;
btQuaternion q(m_curr_rotation.getHeading(), m_curr_rotation.getPitch(),
btMatrix3x3 m;
m.setEulerZYX(m_curr_rotation.getPitch(), m_curr_rotation.getHeading(),
m_curr_rotation.getRoll());
m_kart->setRotation(q);
m_kart->setRotation(m);
AbstractKartAnimation::update(dt);

View File

@@ -195,7 +195,7 @@
#include "states_screens/state_manager.hpp"
#include "states_screens/user_screen.hpp"
#include "states_screens/dialogs/message_dialog.hpp"
#include "tracks/battle_graph.hpp"
#include "tracks/arena_graph.hpp"
#include "tracks/track.hpp"
#include "tracks/track_manager.hpp"
#include "utils/command_line.hpp"
@@ -902,13 +902,16 @@ int handleCmdLine()
}
if(CommandLine::has("--battle-ai-stats"))
{
std::string track;
if (!CommandLine::has("--track", &track))
track = "temple";
UserConfigParams::m_arena_ai_stats=true;
race_manager->setMinorMode(RaceManager::MINOR_MODE_3_STRIKES);
std::vector<std::string> l;
for (int i = 0; i < 8; i++)
l.push_back("tux");
race_manager->setDefaultAIKartList(l);
race_manager->setTrack("temple");
race_manager->setTrack(track);
race_manager->setNumKarts(8);
race_manager->setDifficulty(RaceManager::Difficulty(3));
UserConfigParams::m_no_start_screen = true;
@@ -1887,8 +1890,8 @@ void runUnitTests()
Log::info("UnitTest", "Kart characteristics");
CombinedCharacteristic::unitTesting();
Log::info("UnitTest", "Battle Graph");
BattleGraph::unitTesting();
Log::info("UnitTest", "Arena Graph");
ArenaGraph::unitTesting();
Log::info("UnitTest", "Fonts for translation");
font_manager->unitTesting();

View File

@@ -26,9 +26,12 @@
#include "karts/abstract_kart.hpp"
#include "karts/controller/controller.hpp"
#include "karts/kart_properties.hpp"
#include "graphics/material.hpp"
#include "physics/physics.hpp"
#include "race/history.hpp"
#include "states_screens/race_gui_base.hpp"
#include "tracks/drive_graph.hpp"
#include "tracks/drive_node.hpp"
#include "tracks/track_sector.hpp"
#include "tracks/track.hpp"
#include "utils/constants.hpp"
@@ -91,7 +94,6 @@ void LinearWorld::reset()
for(unsigned int i=0; i<kart_amount; i++)
{
m_kart_info[i].reset();
m_kart_info[i].getTrackSector()->update(m_karts[i]->getXYZ());
m_karts[i]->setWrongwayCounter(0);
} // next kart
@@ -178,12 +180,12 @@ void LinearWorld::update(float dt)
// in the position of the kart (e.g. while falling the kart
// might get too close to another part of the track, shortly
// jump to position one, then on reset fall back to last)
if ((!kart_info.getTrackSector()->isOnRoad() &&
if ((!getTrackSector(n)->isOnRoad() &&
(!kart->getMaterial() ||
kart->getMaterial()->isDriveReset())) &&
!kart->isGhostKart())
continue;
kart_info.getTrackSector()->update(kart->getFrontXYZ());
getTrackSector(n)->update(kart->getFrontXYZ());
kart_info.m_overall_distance = kart_info.m_race_lap
* m_track->getTrackLength()
+ getDistanceDownTrackForKart(kart->getWorldKartId());
@@ -377,24 +379,6 @@ void LinearWorld::newLap(unsigned int kart_index)
kart->getController()->newLap(kart_info.m_race_lap);
} // newLap
//-----------------------------------------------------------------------------
/** Gets the sector a kart is on. This function returns UNKNOWN_SECTOR if the
* kart_id is larger than the current kart info. This is necessary in the case
* that a collision with the track happens during resetAllKarts: at this time
* m_kart_info is not initialised (and has size 0), so it would trigger this
* assert. While this normally does not happen, it is useful for track
* designers that STK does not crash.
* \param kart_id The world kart id of the kart for which to return
* the sector.
*/
int LinearWorld::getSectorForKart(const AbstractKart *kart) const
{
if(kart->getWorldKartId()>=m_kart_info.size())
return QuadGraph::UNKNOWN_SECTOR;
return m_kart_info[kart->getWorldKartId()].getTrackSector()
->getCurrentGraphNode();
} // getSectorForKart
//-----------------------------------------------------------------------------
/** Returns the distance the kart has travelled along the track since
* crossing the start line..
@@ -402,8 +386,7 @@ int LinearWorld::getSectorForKart(const AbstractKart *kart) const
*/
float LinearWorld::getDistanceDownTrackForKart(const int kart_id) const
{
assert(kart_id < (int)m_kart_info.size());
return m_kart_info[kart_id].getTrackSector()->getDistanceFromStart();
return getTrackSector(kart_id)->getDistanceFromStart();
} // getDistanceDownTrackForKart
//-----------------------------------------------------------------------------
@@ -413,8 +396,7 @@ float LinearWorld::getDistanceDownTrackForKart(const int kart_id) const
*/
float LinearWorld::getDistanceToCenterForKart(const int kart_id) const
{
assert(kart_id < (int)m_kart_info.size());
return m_kart_info[kart_id].getTrackSector()->getDistanceToCenter();
return getTrackSector(kart_id)->getDistanceToCenter();
} // getDistanceToCenterForKart
//-----------------------------------------------------------------------------
@@ -626,29 +608,41 @@ float LinearWorld::estimateFinishTimeForKart(AbstractKart* kart)
*/
unsigned int LinearWorld::getNumberOfRescuePositions() const
{
return QuadGraph::get()->getNumNodes();
return DriveGraph::get()->getNumNodes();
} // getNumberOfRescuePositions
// ------------------------------------------------------------------------
unsigned int LinearWorld::getRescuePositionIndex(AbstractKart *kart)
{
KartInfo& info = m_kart_info[kart->getWorldKartId()];
const unsigned int kart_id = kart->getWorldKartId();
info.getTrackSector()->rescue();
getTrackSector(kart_id)->rescue();
// Setting XYZ for the kart is important since otherwise the kart
// will not detect the right material again when doing the next
// raycast to detect where it is driving on (--> potential rescue loop)
return info.getTrackSector()->getCurrentGraphNode();
return getTrackSector(kart_id)->getCurrentGraphNode();
} // getRescuePositionIndex
// ------------------------------------------------------------------------
btTransform LinearWorld::getRescueTransform(unsigned int index) const
{
const Vec3 &xyz = QuadGraph::get()->getQuadOfNode(index).getCenter();
const Vec3 &xyz = DriveGraph::get()->getNode(index)->getCenter();
const Vec3 &normal = DriveGraph::get()->getNode(index)->getNormal();
btTransform pos;
pos.setOrigin(xyz);
pos.setRotation(btQuaternion(btVector3(0.0f, 1.0f, 0.0f),
m_track->getAngle(index)));
// First rotate into the quad's plane (q1), then rotate so that the kart points in the
// right direction (q2).
btQuaternion q1;
if (normal.cross(Vec3(0, 1, 0)).length() > 0)
{
q1 = btQuaternion(-normal.cross(Vec3(0, 1, 0)), normal.angle(Vec3(0, 1, 0)));
}
else q1 = btQuaternion(Vec3(0,1,0),0);
// First apply the heading change, than the 'parallelisation' to the plane
btQuaternion q2(btVector3(0,1,0), m_track->getAngle(index));
pos.setRotation(q1*q2);
return pos;
} // getRescueTransform
@@ -849,19 +843,21 @@ void LinearWorld::checkForWrongDirection(unsigned int i, float dt)
// If the kart can go in more than one directions from the current track
// don't do any reverse message handling, since it is likely that there
// will be one direction in which it isn't going backwards anyway.
int sector = m_kart_info[i].getTrackSector()->getCurrentGraphNode();
int sector = getTrackSector(i)->getCurrentGraphNode();
if (QuadGraph::get()->getNumberOfSuccessors(sector) > 1)
if (DriveGraph::get()->getNumberOfSuccessors(sector) > 1)
return;
// check if the player is going in the wrong direction
float angle_diff = kart->getHeading() - m_track->getAngle(sector);
if (angle_diff > M_PI)
const DriveNode* node = DriveGraph::get()->getNode(sector);
Vec3 center_line = node->getUpperCenter() - node->getLowerCenter();
float angle_diff = kart->getVelocity().angle(center_line);
if (angle_diff > M_PI)
angle_diff -= 2*M_PI;
else if (angle_diff < -M_PI)
else if (angle_diff < -M_PI)
angle_diff += 2*M_PI;
// Display a warning message if the kart is going back way (unless
// the kart has already finished the race).
if ((angle_diff > DEGREE_TO_RAD * 120.0f ||
@@ -898,3 +894,8 @@ void LinearWorld::checkForWrongDirection(unsigned int i, float dt)
} // checkForWrongDirection
//-----------------------------------------------------------------------------
void LinearWorld::setLastTriggeredCheckline(unsigned int kart_index, int index)
{
if (m_kart_info.size() == 0) return;
getTrackSector(kart_index)->setLastTriggeredCheckline(index);
} // setLastTriggeredCheckline

View File

@@ -21,7 +21,6 @@
#include <vector>
#include "modes/world_with_rank.hpp"
#include "tracks/track_sector.hpp"
#include "utils/aligned_array.hpp"
class SFXBase;
@@ -78,9 +77,6 @@ private:
* track-length plus distance-along-track). */
float m_overall_distance;
/** Stores the current graph node and track coordinates etc. */
TrackSector m_track_sector;
/** Initialises all fields. */
KartInfo() { reset(); }
// --------------------------------------------------------------------
@@ -92,14 +88,7 @@ private:
m_time_at_last_lap = 99999.9f;
m_estimated_finish = -1.0f;
m_overall_distance = 0.0f;
m_track_sector.reset();
} // reset
// --------------------------------------------------------------------
/** Returns a pointer to the current node object. */
TrackSector *getTrackSector() {return &m_track_sector; }
// --------------------------------------------------------------------
/** Returns a pointer to the current node object. */
const TrackSector *getTrackSector() const {return &m_track_sector; }
};
// ------------------------------------------------------------------------
@@ -124,7 +113,6 @@ public:
virtual ~LinearWorld();
virtual void update(float delta) OVERRIDE;
int getSectorForKart(const AbstractKart *kart) const;
float getDistanceDownTrackForKart(const int kart_id) const;
float getDistanceToCenterForKart(const int kart_id) const;
float getEstimatedFinishTime(const int kart_id) const;
@@ -149,14 +137,6 @@ public:
// ------------------------------------------------------------------------
/** Override settings from base class */
virtual bool useChecklineRequirements() const OVERRIDE { return true; }
// ------------------------------------------------------------------------
/** Returns true if the kart is on a valid driveline quad.
* \param kart_index Index of the kart. */
bool isOnRoad(unsigned int kart_index) const
{
return m_kart_info[kart_index].getTrackSector()->isOnRoad();
} // isOnRoad
// ------------------------------------------------------------------------
/** Returns the number of laps a kart has completed.
* \param kart_index World index of the kart. */
@@ -165,21 +145,8 @@ public:
assert(kart_index < m_kart_info.size());
return m_kart_info[kart_index].m_race_lap;
} // getkartLap
// ------------------------------------------------------------------------
/** Returns the track_sector object for the specified kart.
* \param kart_index World index of the kart. */
TrackSector& getTrackSector(unsigned int kart_index)
{
return m_kart_info[kart_index].m_track_sector;
} // getTrackSector
// ------------------------------------------------------------------------
void setLastTriggeredCheckline(unsigned int kart_index, int index)
{
if (m_kart_info.size() == 0)
return;
m_kart_info[kart_index].m_track_sector.setLastTriggeredCheckline(index);
}
void setLastTriggeredCheckline(unsigned int kart_index, int index);
// ------------------------------------------------------------------------
/** Returns how far the kart has driven so far (i.e.
* number-of-laps-finished times track-length plus distance-on-track.

View File

@@ -39,7 +39,7 @@
#include "tracks/track_object_manager.hpp"
//-----------------------------------------------------------------------------
OverWorld::OverWorld() : WorldWithRank()
OverWorld::OverWorld() : World()
{
m_return_to_garage = false;
m_stop_music_when_dialog_open = false;
@@ -118,8 +118,8 @@ void OverWorld::update(float dt)
music_manager->startMusic();
m_karts[0]->startEngineSFX();
}
WorldWithRank::update(dt);
WorldWithRank::updateTrack(dt);
World::update(dt);
World::updateTrack(dt);
const unsigned int kart_amount = (unsigned int)m_karts.size();
// isn't it cool, on the overworld nitro is free!
@@ -269,7 +269,7 @@ void OverWorld::onMouseClick(int x, int y)
if(challenge)
{
// Use the 'get closest start point' rescue function
// from WorldWithRank by setting the kart's position to
// from World by setting the kart's position to
// be the location of the challenge bubble.
AbstractKart* kart = getKart(0);
kart->setXYZ(challenge->m_position);

View File

@@ -20,19 +20,18 @@
#include <vector>
#include "modes/world_with_rank.hpp"
#include "modes/world.hpp"
#include "utils/aligned_array.hpp"
#include "LinearMath/btTransform.h"
/*
* The overworld map where challenges are played.
* \note This mode derives from LinearWorld to get support for drivelines,
* minimap and rescue, even though this world is not technically
* linear.
* \note Extends world to make a simple world where karts can drive around,
* it adds challenges and starting of races.
* \ingroup modes
*/
class OverWorld : public WorldWithRank
class OverWorld : public World
{
protected:

View File

@@ -25,18 +25,16 @@
#include "graphics/central_settings.hpp"
#include "graphics/irr_driver.hpp"
#include "graphics/render_info.hpp"
#include "karts/abstract_kart.hpp"
#include "karts/kart.hpp"
#include "karts/kart_model.hpp"
#include "karts/kart_properties.hpp"
#include "karts/rescue_animation.hpp"
#include "karts/controller/local_player_controller.hpp"
#include "karts/controller/soccer_ai.hpp"
#include "physics/physics.hpp"
#include "states_screens/race_gui_base.hpp"
#include "tracks/battle_graph.hpp"
#include "tracks/track.hpp"
#include "tracks/track_object_manager.hpp"
#include "tracks/track_sector.hpp"
#include "utils/constants.hpp"
#include <IMeshSceneNode.h>
@@ -61,6 +59,7 @@ SoccerWorld::SoccerWorld() : WorldWithRank()
m_use_highscores = false;
m_red_ai = 0;
m_blue_ai = 0;
m_ball_track_sector = NULL;
} // SoccerWorld
//-----------------------------------------------------------------------------
@@ -69,6 +68,9 @@ SoccerWorld::SoccerWorld() : WorldWithRank()
SoccerWorld::~SoccerWorld()
{
m_goal_sound->deleteSFX();
delete m_ball_track_sector;
m_ball_track_sector = NULL;
} // ~SoccerWorld
//-----------------------------------------------------------------------------
@@ -88,6 +90,12 @@ void SoccerWorld::init()
m_goal_target = race_manager->getMaxGoal();
m_goal_sound = SFXManager::get()->createSoundSource("goal_scored");
if (m_track->hasNavMesh())
{
// Init track sector for ball if navmesh is found
m_ball_track_sector = new TrackSector();
}
TrackObjectManager* tom = getTrack()->getTrackObjectManager();
assert(tom);
PtrVector<TrackObject>& objects = tom->getObjects();
@@ -141,10 +149,15 @@ void SoccerWorld::reset()
m_goal_sound->stop();
}
if (m_track->hasNavMesh())
{
m_ball_track_sector->reset();
}
initKartList();
resetAllPosition();
m_ball->reset();
m_bgd.reset();
// Make the player kart in profiling mode up
// ie make this kart less likely to affect gaming result
if (UserConfigParams::m_arena_ai_stats)
@@ -169,7 +182,7 @@ void SoccerWorld::update(float dt)
updateBallPosition(dt);
if (m_track->hasNavMesh())
{
updateKartNodes();
updateSectorForKarts();
updateAIData();
}
@@ -427,24 +440,6 @@ AbstractKart *SoccerWorld::createKart(const std::string &kart_ident, int index,
return new_kart;
} // createKart
//-----------------------------------------------------------------------------
/** Updates the m_kart_on_node value of each kart to localize it
* on the navigation mesh.
*/
void SoccerWorld::updateKartNodes()
{
if (isRaceOver()) return;
const unsigned int n = getNumKarts();
for (unsigned int i = 0; i < n; i++)
{
if (m_karts[i]->isEliminated()) continue;
m_kart_on_node[i] = BattleGraph::get()->pointToNode(m_kart_on_node[i],
m_karts[i]->getXYZ(), false/*ignore_vertical*/);
}
} // updateKartNodes
//-----------------------------------------------------------------------------
/** Localize the ball on the navigation mesh.
*/
@@ -461,11 +456,9 @@ void SoccerWorld::updateBallPosition(float dt)
if (m_track->hasNavMesh())
{
m_ball_on_node = BattleGraph::get()->pointToNode(m_ball_on_node,
getBallPosition(), true/*ignore_vertical*/);
if (m_ball_on_node == BattleGraph::UNKNOWN_POLY &&
getPhase() == RACE_PHASE)
m_ball_track_sector
->update(getBallPosition(), true/*ignore_vertical*/);
if (!m_ball_track_sector->isOnRoad() && getPhase() == RACE_PHASE)
{
m_ball_invalid_timer += dt;
// Reset the ball and karts if out of navmesh after 2 seconds
@@ -486,12 +479,12 @@ void SoccerWorld::updateBallPosition(float dt)
} // updateBallPosition
//-----------------------------------------------------------------------------
void SoccerWorld::resetAllPosition()
int SoccerWorld::getBallNode() const
{
m_kart_on_node.clear();
m_kart_on_node.resize(m_karts.size(), BattleGraph::UNKNOWN_POLY);
m_ball_on_node = BattleGraph::UNKNOWN_POLY;
} // resetAllPosition
assert(m_ball_track_sector != NULL);
return m_ball_track_sector->getCurrentGraphNode();
} // getBallNode
//-----------------------------------------------------------------------------
SoccerTeam SoccerWorld::getKartTeam(unsigned int kart_id) const
{

View File

@@ -31,8 +31,9 @@
class AbstractKart;
class Controller;
class TrackObject;
class TrackSector;
/** An implementation of World, to provide the soccer game mode
/** \brief An implementation of WorldWithRank, to provide the soccer game mode
* Notice: In soccer world, true goal means blue, false means red.
* \ingroup modes
*/
@@ -279,8 +280,7 @@ private:
std::map<int, unsigned int> m_kart_position_map;
/** Data generated from navmesh */
std::vector<int> m_kart_on_node;
int m_ball_on_node;
TrackSector* m_ball_track_sector;
int m_red_ai;
int m_blue_ai;
@@ -289,12 +289,8 @@ private:
/** Set the team for the karts */
void initKartList();
/** Function to update the locations of all karts on the polygon map */
void updateKartNodes();
/** Function to update the location the ball on the polygon map */
void updateBallPosition(float dt);
/** Clean up */
void resetAllPosition();
/** Function to update data for AI usage. */
void updateAIData();
/** Get number of teammates in a team, used by starting position assign. */
@@ -357,11 +353,7 @@ public:
m_blue_score_times : m_red_score_times);
}
// ------------------------------------------------------------------------
int getKartNode(unsigned int kart_id) const
{ return m_kart_on_node[kart_id]; }
// ------------------------------------------------------------------------
int getBallNode() const
{ return m_ball_on_node; }
int getBallNode() const;
// ------------------------------------------------------------------------
const Vec3& getBallPosition() const
{ return (Vec3&)m_ball_body->getCenterOfMassTransform().getOrigin(); }

View File

@@ -28,7 +28,6 @@
#include "karts/kart_properties.hpp"
#include "physics/physics.hpp"
#include "states_screens/race_gui_base.hpp"
#include "tracks/battle_graph.hpp"
#include "tracks/track.hpp"
#include "tracks/track_object_manager.hpp"
#include "utils/constants.hpp"
@@ -93,8 +92,7 @@ void ThreeStrikesBattle::reset()
for(unsigned int n=0; n<kart_amount; n++)
{
m_kart_info[n].m_lives = 3;
m_kart_info[n].m_on_node = BattleGraph::UNKNOWN_POLY;
m_kart_info[n].m_lives = 3;
// no positions in this mode
m_karts[n]->setPosition(-1);
@@ -308,7 +306,7 @@ void ThreeStrikesBattle::update(float dt)
WorldWithRank::updateTrack(dt);
if (m_track->hasNavMesh())
updateKartNodes();
updateSectorForKarts();
// insert blown away tire(s) now if was requested
while (m_insert_tire > 0)
@@ -458,33 +456,6 @@ bool ThreeStrikesBattle::isRaceOver()
return getCurrentNumKarts()==1 || getCurrentNumPlayers()==0;
} // isRaceOver
//-----------------------------------------------------------------------------
/** Updates the m_on_node value of each kart to localize it
* on the navigation mesh.
*/
void ThreeStrikesBattle::updateKartNodes()
{
if (isRaceOver()) return;
const unsigned int n = getNumKarts();
for (unsigned int i = 0; i < n; i++)
{
if (m_karts[i]->isEliminated()) continue;
m_kart_info[i].m_on_node = BattleGraph::get()
->pointToNode(m_kart_info[i].m_on_node,
m_karts[i]->getXYZ(), false/*ignore_vertical*/);
}
}
//-----------------------------------------------------------------------------
/** Get the which node the kart located in navigation mesh.
*/
int ThreeStrikesBattle::getKartNode(unsigned int kart_id) const
{
return m_kart_info[kart_id].m_on_node;
} // getKartNode
//-----------------------------------------------------------------------------
/** Called when the race finishes, i.e. after playing (if necessary) an
* end of race animation. It updates the time for all karts still racing,

View File

@@ -31,7 +31,8 @@
class PhysicalObject;
/**
* \brief An implementation of World, to provide the 3 strikes battle game mode
* \brief An implementation of WorldWithRank, to provide the 3 strikes battle
* game mode
* \ingroup modes
*/
class ThreeStrikesBattle : public WorldWithRank
@@ -40,7 +41,6 @@ private:
struct BattleInfo
{
int m_lives;
int m_on_node;
};
/** This vector contains an 'BattleInfo' struct for every kart in the race.
@@ -71,9 +71,6 @@ private:
PtrVector<TrackObject, REF> m_tires;
/** Function to update the locations of all karts on the polygon map */
void updateKartNodes();
/** Profiling usage */
int m_total_rescue;
int m_frame_count;
@@ -93,31 +90,29 @@ public:
ThreeStrikesBattle();
virtual ~ThreeStrikesBattle();
virtual void init();
virtual void init() OVERRIDE;
// clock events
virtual bool isRaceOver();
virtual void terminateRace();
virtual bool isRaceOver() OVERRIDE;
virtual void terminateRace() OVERRIDE;
// overriding World methods
virtual void reset();
virtual void reset() OVERRIDE;
//virtual void getDefaultCollectibles(int& collectible_type, int& amount);
virtual bool useFastMusicNearEnd() const { return false; }
virtual bool useFastMusicNearEnd() const OVERRIDE { return false; }
virtual void getKartsDisplayInfo(
std::vector<RaceGUIBase::KartIconDisplayInfo> *info);
virtual bool raceHasLaps(){ return false; }
std::vector<RaceGUIBase::KartIconDisplayInfo> *info) OVERRIDE;
virtual bool raceHasLaps() OVERRIDE { return false; }
virtual const std::string& getIdent() const;
virtual const std::string& getIdent() const OVERRIDE;
virtual void kartHit(const unsigned int kart_id);
virtual void update(float dt);
virtual void kartHit(const unsigned int kart_id) OVERRIDE;
virtual void update(float dt) OVERRIDE;
virtual void kartAdded(AbstractKart* kart, scene::ISceneNode* node);
virtual void kartAdded(AbstractKart* kart, scene::ISceneNode* node) OVERRIDE;
virtual void enterRaceOverState() OVERRIDE;
int getKartNode(unsigned int kart_id) const;
void updateKartRanks();
void increaseRescueCount() { m_total_rescue++; }
}; // ThreeStrikesBattles

View File

@@ -27,6 +27,7 @@
#include "config/user_config.hpp"
#include "graphics/camera.hpp"
#include "graphics/irr_driver.hpp"
#include "graphics/material.hpp"
#include "graphics/render_info.hpp"
#include "io/file_manager.hpp"
#include "input/device_manager.hpp"
@@ -164,11 +165,11 @@ void World::init()
// constructor is called, so the wrong race gui would be created.
createRaceGUI();
RewindManager::create();
RewindManager::create();
// Grab the track file
m_track = track_manager->getTrack(race_manager->getTrackName());
m_script_engine = new Scripting::ScriptEngine();
m_script_engine = new Scripting::ScriptEngine();
if(!m_track)
{
std::ostringstream msg;
@@ -241,7 +242,7 @@ void World::init()
*/
void World::reset()
{
RewindManager::get()->reset();
RewindManager::get()->reset();
// If m_saved_race_gui is set, it means that the restart was done
// when the race result gui was being shown. In this case restore the
@@ -706,69 +707,16 @@ void World::resetAllKarts()
}
}
bool all_finished=false;
// kart->isInRest() is not fully correct, since it only takes the
// velocity in count, which might be close to zero when the kart
// is just hitting the floor, before being pushed up again by
// the suspension. So we just do a longer initial simulation,
// which should be long enough for all karts to be firmly on ground.
for(int i=0; i<60; i++) m_physics->update(1.f/60.f);
// Stil wait will all karts are in rest (and handle the case that a kart
// fell through the ground, which can happen if a kart falls for a long
// time, therefore having a high speed when hitting the ground.
int count = 0;
while(!all_finished)
// Do a longer initial simulation, which should be long enough for all
// karts to be firmly on ground.
float g = World::getWorld()->getTrack()->getGravity();
for (KartList::iterator i = m_karts.begin(); i != m_karts.end(); i++)
{
if (count++ > 100)
{
Log::error("World", "Infinite loop waiting for all_finished?");
break;
}
m_physics->update(1.f/60.f);
all_finished=true;
for ( KartList::iterator i=m_karts.begin(); i!=m_karts.end(); i++)
{
if ((*i)->isGhostKart()) continue;
if(!(*i)->isInRest())
{
Vec3 normal;
Vec3 hit_point;
const Material *material;
// We can't use (*i)->getXYZ(), since this is only defined
// after update() was called. Instead we have to get the
// real position of the rigid body.
btTransform t;
(*i)->getBody()->getMotionState()->getWorldTransform(t);
// This test can not be done only once before the loop, since
// it can happen that the kart falls through the track later!
Vec3 to = t.getOrigin()+Vec3(0, -10000, 0);
m_track->getTriangleMesh().castRay(t.getOrigin(), to,
&hit_point, &material,
&normal);
if(!material)
{
Log::error("World",
"No valid starting position for kart %d "
"on track %s.",
(int)(i-m_karts.begin()),
m_track->getIdent().c_str());
if (UserConfigParams::m_artist_debug_mode)
{
Log::warn("World", "Activating fly mode.");
(*i)->flyUp();
continue;
}
else
{
exit(-1);
}
}
all_finished=false;
break;
}
}
} // while
if ((*i)->isGhostKart()) continue;
(*i)->getBody()->setGravity((*i)->getMaterial()->hasGravity() ?
(*i)->getNormal() * -g : Vec3(0, -g, 0));
}
for(int i=0; i<60; i++) m_physics->update(1.f/60.f);
for ( KartList::iterator i=m_karts.begin(); i!=m_karts.end(); i++)
{
@@ -806,8 +754,8 @@ void World::moveKartTo(AbstractKart* kart, const btTransform &transform)
btTransform pos(transform);
// Move the kart
Vec3 xyz = pos.getOrigin() + btVector3(0, 0.5f*kart->getKartHeight(),0.0f);
Vec3 xyz = pos.getOrigin() +
pos.getBasis() * Vec3(0, 0.5f*kart->getKartHeight(), 0);
pos.setOrigin(xyz);
kart->setXYZ(xyz);
kart->setRotation(pos.getRotation());
@@ -1321,20 +1269,36 @@ void World::unpause()
void World::delayedSelfDestruct()
{
m_self_destruct = true;
}
} // delayedSelfDestruct
//-----------------------------------------------------------------------------
void World::escapePressed()
{
new RacePausedDialog(0.8f, 0.6f);
}
} // escapePressed
//-----------------------------------------------------------------------------
bool World::isFogEnabled() const
{
return !m_force_disable_fog && (m_track != NULL && m_track->isFogEnabled());
}
} // isFogEnabled
// ----------------------------------------------------------------------------
/** Returns the start transform with the give index.
* \param rescue_pos Index of the start position to be returned.
* \returns The transform of the corresponding start position.
*/
btTransform World::getRescueTransform(unsigned int rescue_pos) const
{
return m_track->getStartTransform(rescue_pos);
} // getRescueTransform
//-----------------------------------------------------------------------------
/** Uses the start position as rescue positions, override if necessary
*/
unsigned int World::getNumberOfRescuePositions() const
{
return m_track->getNumberOfStartPositions();
} // getNumberOfRescuePositions
/* EOF */

View File

@@ -221,13 +221,13 @@ public:
// ------------------------------------------------------------------------
/** Returns the number of rescue positions on a given track and game
* mode. */
virtual unsigned int getNumberOfRescuePositions() const = 0;
virtual unsigned int getNumberOfRescuePositions() const;
// ------------------------------------------------------------------------
/** Determines the rescue position index of the specified kart. */
virtual unsigned int getRescuePositionIndex(AbstractKart *kart) = 0;
// ------------------------------------------------------------------------
/** Returns the bullet transformation for the specified rescue index. */
virtual btTransform getRescueTransform(unsigned int index) const = 0;
virtual btTransform getRescueTransform(unsigned int index) const;
// ------------------------------------------------------------------------
virtual void moveKartAfterRescue(AbstractKart* kart);
// ------------------------------------------------------------------------

View File

@@ -20,11 +20,23 @@
#include "karts/abstract_kart.hpp"
#include "karts/kart_properties.hpp"
#include "race/history.hpp"
#include "tracks/graph.hpp"
#include "tracks/track.hpp"
#include "tracks/track_sector.hpp"
#include "utils/log.hpp"
#include <iostream>
//-----------------------------------------------------------------------------
WorldWithRank::~WorldWithRank()
{
for (unsigned int i = 0; i < m_kart_track_sector.size(); i++)
{
delete m_kart_track_sector[i];
}
m_kart_track_sector.clear();
} // ~WorldWithRank
//-----------------------------------------------------------------------------
void WorldWithRank::init()
{
@@ -39,8 +51,26 @@ void WorldWithRank::init()
#endif
stk_config->getAllScores(&m_score_for_position, getNumKarts());
// Don't init track sector if navmesh is not found in arena
if ((m_track->isArena() || m_track->isSoccer()) && !m_track->hasNavMesh())
return;
for (unsigned int i = 0; i < m_karts.size(); i++)
m_kart_track_sector.push_back(new TrackSector());
} // init
//-----------------------------------------------------------------------------
void WorldWithRank::reset()
{
World::reset();
for (unsigned int i = 0; i < m_kart_track_sector.size(); i++)
{
getTrackSector(i)->reset();
getTrackSector(i)->update(m_karts[i]->getXYZ());
}
} // reset
//-----------------------------------------------------------------------------
/** Returns the kart with a given position.
* \param p The position of the kart, 1<=p<=num_karts).
@@ -124,16 +154,6 @@ void WorldWithRank::endSetKartPositions()
#endif
} // endSetKartPositions
//-----------------------------------------------------------------------------
/** WorldWithRank uses the start position as rescue positions. So return
* the number of start positions.
*/
unsigned int WorldWithRank::getNumberOfRescuePositions() const
{
return getTrack()->getNumberOfStartPositions();
} // getNumberOfRescuePositions
//-----------------------------------------------------------------------------
/** Determines the rescue position for a kart. The rescue position is the
* start position which is has the biggest accumulated distance to all other
@@ -162,7 +182,7 @@ unsigned int WorldWithRank::getRescuePositionIndex(AbstractKart *kart)
for(unsigned int k=0; k<getCurrentNumKarts(); k++)
{
if(kart->getWorldKartId()==k) continue;
float abs_distance2 = (getKart(k)->getXYZ()-v).length2_2d();
float abs_distance2 = (getKart(k)->getXYZ()-v).length2();
const float CLEAR_SPAWN_RANGE2 = 5*5;
if( abs_distance2 < CLEAR_SPAWN_RANGE2)
{
@@ -184,16 +204,6 @@ unsigned int WorldWithRank::getRescuePositionIndex(AbstractKart *kart)
return furthest_id_found;
} // getRescuePositionIndex
// ----------------------------------------------------------------------------
/** Returns the start transform with the give index.
* \param rescue_pos Index of the start position to be returned.
* \returns The transform of the corresponding start position.
*/
btTransform WorldWithRank::getRescueTransform(unsigned int rescue_pos) const
{
return getTrack()->getStartTransform(rescue_pos);
} // getRescueTransform
//-----------------------------------------------------------------------------
/** Returns the number of points for a kart at a specified position.
* \param p Position (starting with 1).
@@ -204,3 +214,44 @@ int WorldWithRank::getScoreForPosition(int p)
assert(p - 1 <(int) m_score_for_position.size());
return m_score_for_position[p - 1];
} // getScoreForPosition
//-----------------------------------------------------------------------------
/** Returns true if the kart is on a valid graph quad.
* \param kart_index Index of the kart.
*/
bool WorldWithRank::isOnRoad(unsigned int kart_index) const
{
return getTrackSector(kart_index)->isOnRoad();
} // isOnRoad
//-----------------------------------------------------------------------------
/** Gets the sector a kart is on. This function returns UNKNOWN_SECTOR if the
* kart_id is larger than the current kart sector. This is necessary in the
* case that a collision with the track happens during resetAllKarts: at this
* time m_kart_track_sector is not initialised (and has size 0), so it would
* trigger this assert. While this normally does not happen, it is useful for
* track designers that STK does not crash.
* \param kart Kart for which to return the sector.
*/
int WorldWithRank::getSectorForKart(const AbstractKart *kart) const
{
if (kart->getWorldKartId() >= m_kart_track_sector.size())
return Graph::UNKNOWN_SECTOR;
return getTrackSector(kart->getWorldKartId())->getCurrentGraphNode();
} // getSectorForKart
//-----------------------------------------------------------------------------
/** Localize each kart on the graph using its center xyz.
*/
void WorldWithRank::updateSectorForKarts()
{
if (isRaceOver()) return;
const unsigned int n = getNumKarts();
assert(n == m_kart_track_sector.size());
for (unsigned int i = 0; i < n; i++)
{
if (m_karts[i]->isEliminated()) continue;
getTrackSector(i)->update(m_karts[i]->getXYZ());
}
} // updateSectorForKarts

View File

@@ -23,6 +23,7 @@
#include "modes/world.hpp"
class AbstractKart;
class TrackSector;
/**
* A WorldWithRank is a world where the karts are ranked. This is the base
@@ -60,28 +61,42 @@ protected:
unsigned int getClosestStartPoint(AbstractKart *kart);
/** Stores the current graph node and track coordinates for each kart. */
std::vector<TrackSector*> m_kart_track_sector;
// ------------------------------------------------------------------------
void updateSectorForKarts();
public:
WorldWithRank() : World() {}
virtual ~WorldWithRank();
/** call just after instanciating. can't be moved to the contructor as child
classes must be instanciated, otherwise polymorphism will fail and the
results will be incorrect */
virtual void init() OVERRIDE;
virtual void reset() OVERRIDE;
bool displayRank() const { return m_display_rank; }
void beginSetKartPositions();
bool setKartPosition(unsigned int kart_id,
unsigned int position);
unsigned int position);
void endSetKartPositions();
AbstractKart* getKartAtPosition(unsigned int p) const;
virtual int getScoreForPosition(int p);
virtual unsigned int getNumberOfRescuePositions() const OVERRIDE;
virtual unsigned int getRescuePositionIndex(AbstractKart *kart) OVERRIDE;
virtual btTransform getRescueTransform(unsigned int index) const OVERRIDE;
// ------------------------------------------------------------------------
/** Returns the track_sector object for the specified kart.
* \param kart_index World index of the kart. */
TrackSector* getTrackSector(unsigned int kart_index) const
{
assert(kart_index < m_kart_track_sector.size());
return m_kart_track_sector[kart_index];
} // getTrackSector
// ------------------------------------------------------------------------
bool isOnRoad(unsigned int kart_index) const;
// ------------------------------------------------------------------------
int getSectorForKart(const AbstractKart *kart) const;
}; // WorldWithRank

View File

@@ -22,6 +22,7 @@
#include "LinearMath/btIDebugDraw.h"
#include "BulletDynamics/ConstraintSolver/btContactConstraint.h"
#include "graphics/material.hpp"
#include "karts/kart.hpp"
#include "modes/world.hpp"
#include "physics/triangle_mesh.hpp"
@@ -444,7 +445,9 @@ void btKart::updateVehicle( btScalar step )
if(m_num_wheels_on_ground==0)
{
btVector3 kart_up = getChassisWorldTransform().getBasis().getColumn(1);
btVector3 terrain_up(0,1,0);
btVector3 terrain_up =
m_kart->getMaterial() && m_kart->getMaterial()->hasGravity() ?
m_kart->getNormal() : Vec3(0, 1, 0);
// Length of axis depends on the angle - i.e. the further awat
// the kart is from being upright, the larger the applied impulse
// will be, resulting in fast changes when the kart is on its
@@ -521,9 +524,9 @@ void btKart::updateVehicle( btScalar step )
for (int i=0;i<m_wheelInfo.size();i++)
{
btWheelInfo& wheel = m_wheelInfo[i];
btVector3 relpos = wheel.m_raycastInfo.m_hardPointWS
- getRigidBody()->getCenterOfMassPosition();
btVector3 vel = getRigidBody()->getVelocityInLocalPoint(relpos);
//btVector3 relpos = wheel.m_raycastInfo.m_hardPointWS
// - getRigidBody()->getCenterOfMassPosition();
//btVector3 vel = getRigidBody()->getVelocityInLocalPoint(relpos);
if (wheel.m_raycastInfo.m_isInContact)
{

View File

@@ -24,6 +24,7 @@
using namespace irr;
#include "graphics/material.hpp"
#include "graphics/material_manager.hpp"
#include "graphics/mesh_tools.hpp"
#include "io/file_manager.hpp"

View File

@@ -22,8 +22,10 @@
#include <string>
#include <angelscript.h>
#include <functional>
#include <map>
#include "scriptengine/script_utils.hpp"
#include "utils/no_copy.hpp"
#include "utils/ptr_vector.hpp"
class TrackObjectPresentation;

View File

@@ -23,13 +23,13 @@
#include "input/input_device.hpp"
#include "input/input_manager.hpp"
#include "modes/world.hpp"
#include "scriptengine/script_engine.hpp"
#include "states_screens/dialogs/tutorial_message_dialog.hpp"
#include "tracks/track.hpp"
#include "tracks/track_object.hpp"
#include "tracks/track_object_manager.hpp"
#include <angelscript.h>
#include "scriptarray.hpp"
#include <assert.h>
#include <iostream> //debug

View File

@@ -137,11 +137,14 @@ void RaceGUIBase::reset()
{
const AbstractKart *kart = World::getWorld()->getKart(i);
m_referee_pos[i] = kart->getTrans()(Referee::getStartOffset());
m_referee_rotation[i] = Referee::getStartRotation()
+ Vec3(0, kart->getHeading()*RAD_TO_DEGREE, 0);
Vec3 hpr;
btQuaternion q = btQuaternion(kart->getTrans().getBasis().getColumn(1),
Referee::getStartRotation().getY() * DEGREE_TO_RAD) *
kart->getTrans().getRotation();
hpr.setHPR(q);
m_referee_rotation[i] = hpr.toIrrHPR();
}
m_referee_height = 10.0f;
m_referee->attachToSceneNode();
m_plunger_move_time = 0;
@@ -419,8 +422,8 @@ void RaceGUIBase::preRenderCallback(const Camera *camera)
if(m_referee && camera->getKart())
{
unsigned int world_id = camera->getKart()->getWorldKartId();
Vec3 xyz = m_referee_pos[world_id];
xyz.setY(xyz.getY()+m_referee_height);
Vec3 xyz = m_referee_pos[world_id] +
camera->getKart()->getNormal() * m_referee_height;
m_referee->setPosition(xyz);
m_referee->setRotation(m_referee_rotation[world_id]);
}

441
src/tracks/arena_graph.cpp Normal file
View File

@@ -0,0 +1,441 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2016 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.
#include "tracks/arena_graph.hpp"
#include "config/user_config.hpp"
#include "io/file_manager.hpp"
#include "io/xml_node.hpp"
#include "race/race_manager.hpp"
#include "tracks/arena_node.hpp"
#include "tracks/track.hpp"
#include "tracks/track_manager.hpp"
#include "utils/log.hpp"
#include <algorithm>
#include <queue>
// -----------------------------------------------------------------------------
ArenaGraph::ArenaGraph(const std::string &navmesh, const XMLNode *node)
: Graph()
{
loadNavmesh(navmesh);
buildGraph();
// Compute shortest distance from all nodes
for (unsigned int i = 0; i < getNumNodes(); i++)
computeDijkstra(i);
setNearbyNodesOfAllNodes();
if (node && race_manager->getMinorMode() == RaceManager::MINOR_MODE_SOCCER)
loadGoalNodes(node);
} // ArenaGraph
// -----------------------------------------------------------------------------
ArenaNode* ArenaGraph::getNode(unsigned int i) const
{
assert(i < m_all_nodes.size());
ArenaNode* n = dynamic_cast<ArenaNode*>(m_all_nodes[i]);
assert(n != NULL);
return n;
} // getNode
// -----------------------------------------------------------------------------
void ArenaGraph::differentNodeColor(int n, video::SColor* c) const
{
std::set<int>::iterator it;
it = m_red_node.find(n);
if (it != m_red_node.end())
{
*c = video::SColor(255, 255, 0, 0);
return;
}
it = m_blue_node.find(n);
if (it != m_blue_node.end())
{
*c = video::SColor(255, 0, 0, 255);
return;
}
if (UserConfigParams::m_track_debug)
{
if (m_all_nodes[n]->is3DQuad())
*c = video::SColor(255, 0, 255, 0);
else
*c = video::SColor(255, 255, 255, 0);
}
} // differentNodeColor
// -----------------------------------------------------------------------------
void ArenaGraph::loadNavmesh(const std::string &navmesh)
{
XMLNode *xml = file_manager->createXMLTree(navmesh);
if (xml->getName() != "navmesh")
{
Log::error("ArenaGraph", "NavMesh is invalid.");
delete xml;
return;
}
std::vector<Vec3> all_vertices;
for (unsigned int i = 0; i < xml->getNumNodes(); i++)
{
const XMLNode *xml_node = xml->getNode(i);
if (xml_node->getName() == "vertices")
{
for (unsigned int i = 0; i < xml_node->getNumNodes(); i++)
{
const XMLNode *xml_node_node = xml_node->getNode(i);
if (!(xml_node_node->getName() == "vertex"))
{
Log::error("ArenaGraph", "Unsupported type '%s' found"
"in '%s' - ignored.",
xml_node_node->getName().c_str(), navmesh.c_str());
continue;
}
// Reading vertices
float x, y, z;
xml_node_node->get("x", &x);
xml_node_node->get("y", &y);
xml_node_node->get("z", &z);
Vec3 p(x, y, z);
all_vertices.push_back(p);
}
}
if (xml_node->getName() == "faces")
{
for (unsigned int i = 0; i < xml_node->getNumNodes(); i++)
{
const XMLNode *xml_node_node = xml_node->getNode(i);
if (xml_node_node->getName() != "face")
{
Log::error("ArenaGraph", "Unsupported type '%s'"
" found in '%s' - ignored.",
xml_node_node->getName().c_str(), navmesh.c_str());
continue;
}
// Reading quads
std::vector<int> quad_index;
std::vector<int> adjacent_quad_index;
xml_node_node->get("indices", &quad_index);
xml_node_node->get("adjacents", &adjacent_quad_index);
if (quad_index.size() != 4)
{
Log::error("ArenaGraph", "A Node in navmesh is not made"
" of quad, will only use the first 4 vertices");
}
createQuad(all_vertices[quad_index[0]],
all_vertices[quad_index[1]], all_vertices[quad_index[2]],
all_vertices[quad_index[3]], m_all_nodes.size(),
false/*invisible*/, false/*ai_ignore*/, true/*is_arena*/);
ArenaNode* cur_node = getNode(m_all_nodes.size() - 1);
cur_node->setAdjacentNodes(adjacent_quad_index);
}
}
}
delete xml;
} // loadNavmesh
// ----------------------------------------------------------------------------
void ArenaGraph::buildGraph()
{
const unsigned int n_nodes = getNumNodes();
m_distance_matrix = std::vector<std::vector<float>>
(n_nodes, std::vector<float>(n_nodes, 9999.9f));
for (unsigned int i = 0; i < n_nodes; i++)
{
ArenaNode* cur_node = getNode(i);
for (const int& adjacent : cur_node->getAdjacentNodes())
{
Vec3 diff = getNode(adjacent)->getCenter() - cur_node->getCenter();
float distance = diff.length();
m_distance_matrix[i][adjacent] = distance;
}
m_distance_matrix[i][i] = 0.0f;
}
// Allocate and initialise the previous node data structure:
m_parent_node = std::vector<std::vector<int16_t>>
(n_nodes, std::vector<int16_t>(n_nodes, Graph::UNKNOWN_SECTOR));
for (unsigned int i = 0; i < n_nodes; i++)
{
for (unsigned int j = 0; j < n_nodes; j++)
{
if (i == j || m_distance_matrix[i][j] >= 9899.9f)
m_parent_node[i][j] = -1;
else
m_parent_node[i][j] = i;
} // for j
} // for i
} // buildGraph
// ----------------------------------------------------------------------------
/** Dijkstra shortest path computation. It computes the shortest distance from
* the specified node 'source' to all other nodes. At the end of the
* computation, m_distance_matrix[i][j] stores the shortest path distance from
* source to j and m_parent_node[source][j] stores the last vertex visited on
* the shortest path from i to j before visiting j. Suppose the shortest path
* from i to j is i->......->k->j then m_parent_node[i][j] = k
*/
void ArenaGraph::computeDijkstra(int source)
{
// Stores the distance (float) to 'source' from a specified node (int)
typedef std::pair<int, float> IndDistPair;
class Shortest
{
public:
bool operator()(const IndDistPair &p1, const IndDistPair &p2)
{
return p1.second > p2.second;
}
};
std::priority_queue<IndDistPair, std::vector<IndDistPair>, Shortest> queue;
IndDistPair begin(source, 0.0f);
queue.push(begin);
const unsigned int n = getNumNodes();
std::vector<bool> visited;
visited.resize(n, false);
while (!queue.empty())
{
// Get element with shortest path
IndDistPair current = queue.top();
queue.pop();
int cur_index = current.first;
if (visited[cur_index]) continue;
visited[cur_index] = true;
for (const int& adjacent : getNode(cur_index)->getAdjacentNodes())
{
// Distance already computed, can be ignored
if (visited[adjacent]) continue;
float new_dist =
current.second + m_distance_matrix[cur_index][adjacent];
if (new_dist < m_distance_matrix[source][adjacent])
{
m_distance_matrix[source][adjacent] = new_dist;
m_parent_node[source][adjacent] = cur_index;
}
IndDistPair pair(adjacent, new_dist);
queue.push(pair);
}
}
} // computeDijkstra
// ----------------------------------------------------------------------------
/** THIS FUNCTION IS ONLY USED FOR UNIT-TESTING, to verify that the new
* Dijkstra algorithm gives the same results.
* computeFloydWarshall() computes the shortest distance between any two
* nodes. At the end of the computation, m_distance_matrix[i][j] stores the
* shortest path distance from i to j and m_parent_node[i][j] stores the last
* vertex visited on the shortest path from i to j before visiting j. Suppose
* the shortest path from i to j is i->......->k->j then
* m_parent_node[i][j] = k
*/
void ArenaGraph::computeFloydWarshall()
{
unsigned int n = getNumNodes();
for (unsigned int k = 0; k < n; k++)
{
for (unsigned int i = 0; i < n; i++)
{
for (unsigned int j = 0; j < n; j++)
{
if ((m_distance_matrix[i][k] + m_distance_matrix[k][j]) <
m_distance_matrix[i][j])
{
m_distance_matrix[i][j] =
m_distance_matrix[i][k] + m_distance_matrix[k][j];
m_parent_node[i][j] = m_parent_node[k][j];
}
}
}
}
} // computeFloydWarshall
// -----------------------------------------------------------------------------
void ArenaGraph::loadGoalNodes(const XMLNode *node)
{
const XMLNode *check_node = node->getNode("checks");
for (unsigned int i = 0; i < check_node->getNumNodes(); i++)
{
const XMLNode *goal = check_node->getNode(i);
if (goal->getName() =="goal")
{
Vec3 p1, p2;
bool first_goal = false;
goal->get("first_goal", &first_goal);
goal->get("p1", &p1);
goal->get("p2", &p2);
int first = Graph::UNKNOWN_SECTOR;
findRoadSector(p1, &first, NULL, true);
int last = Graph::UNKNOWN_SECTOR;
findRoadSector(p2, &last, NULL, true);
first_goal ? m_blue_node.insert(first) : m_red_node.insert(first);
first_goal ? m_blue_node.insert(last) : m_red_node.insert(last);
while (first != last)
{
// Find all the nodes which connect the two points of
// goal, notice: only work if it's a straight line
first = getNextNode(first, last);
first_goal ? m_blue_node.insert(first) :
m_red_node.insert(first);
}
}
}
} // loadGoalNodes
// ----------------------------------------------------------------------------
void ArenaGraph::setNearbyNodesOfAllNodes()
{
// Only save the nearby 8 nodes
const unsigned int try_count = 8;
for (unsigned int i = 0; i < getNumNodes(); i++)
{
// Get the distance to all nodes at i
ArenaNode* cur_node = getNode(i);
std::vector<int> nearby_nodes;
std::vector<float> dist = m_distance_matrix[i];
// Skip the same node
dist[i] = 999999.0f;
for (unsigned int j = 0; j < try_count; j++)
{
std::vector<float>::iterator it =
std::min_element(dist.begin(), dist.end());
const int pos = it - dist.begin();
nearby_nodes.push_back(pos);
dist[pos] = 999999.0f;
}
cur_node->setNearbyNodes(nearby_nodes);
}
} // setNearbyNodesOfAllNodes
// ----------------------------------------------------------------------------
/** Determines the full path from 'from' to 'to' and returns it in a
* std::vector (in reverse order). Used only for unit testing.
*/
std::vector<int16_t> ArenaGraph::getPathFromTo(int from, int to,
const std::vector< std::vector< int16_t > >& parent_node)
{
std::vector<int16_t> path;
path.push_back(to);
while(from!=to)
{
to = parent_node[from][to];
path.push_back(to);
}
return path;
} // getPathFromTo
// ============================================================================
/** Unit testing for arena graph distance and parent node computation.
* Instead of using hand-tuned test cases we use the tested, verified and
* easier to understand Floyd-Warshall algorithm to compute the distances,
* and check if the (significanty faster) Dijkstra algorithm gives the same
* results. For now we use the cave mesh as test case.
*/
void ArenaGraph::unitTesting()
{
Track *track = track_manager->getTrack("cave");
std::string navmesh_file_name=track->getTrackFile("navmesh.xml");
double s = StkTime::getRealTime();
ArenaGraph* ag = new ArenaGraph(navmesh_file_name);
double e = StkTime::getRealTime();
Log::error("Time", "Dijkstra %lf", e-s);
// Save the Dijkstra results
std::vector< std::vector< float > > distance_matrix = ag->m_distance_matrix;
std::vector< std::vector< int16_t > > parent_node = ag->m_parent_node;
ag->buildGraph();
// Now compute results with Floyd-Warshall
s = StkTime::getRealTime();
ag->computeFloydWarshall();
e = StkTime::getRealTime();
Log::error("Time", "Floyd-Warshall %lf", e-s);
int error_count = 0;
for(unsigned int i=0; i<ag->m_distance_matrix.size(); i++)
{
for(unsigned int j=0; j<ag->m_distance_matrix[i].size(); j++)
{
if(ag->m_distance_matrix[i][j] - distance_matrix[i][j] > 0.001f)
{
Log::error("ArenaGraph",
"Incorrect distance %d, %d: Dijkstra: %f F.W.: %f",
i, j, distance_matrix[i][j], ag->m_distance_matrix[i][j]);
error_count++;
} // if distance is too different
// Unortunately it happens frequently that there are different
// shortest path with the same length. And Dijkstra might find
// a different path then Floyd-Warshall. So the test for parent
// polygon often results in false positives, so it is disabled,
// but I leave the code in place in case it is useful for some
// debugging in the feature
#undef TEST_PARENT_POLY_EVEN_THOUGH_MANY_FALSE_POSITIVES
#ifdef TEST_PARENT_POLY_EVEN_THOUGH_MANY_FALSE_POSITIVES
if(ag->m_parent_node[i][j] != parent_node[i][j])
{
error_count++;
std::vector<int16_t> dijkstra_path = getPathFromTo(i, j, parent_node);
std::vector<int16_t> floyd_path = getPathFromTo(i, j, ag->m_parent_node);
if(dijkstra_path.size()!=floyd_path.size())
{
Log::error("ArenaGraph",
"Incorrect path length %d, %d: Dijkstra: %d F.W.: %d",
i, j, parent_node[i][j], ag->m_parent_node[i][j]);
continue;
}
Log::error("ArenaGraph", "Path problems from %d to %d:",
i, j);
for (unsigned k = 0; k < dijkstra_path.size(); k++)
{
if(dijkstra_path[k]!=floyd_path[k])
Log::error("ArenaGraph", "%d/%d dijkstra: %d floyd %d",
k, dijkstra_path.size(), dijkstra_path[k],
floyd_path[k]);
} // for k<dijkstra_path.size()
} // if dijkstra parent_node != floyd parent node
#endif
} // for j
} // for i
delete ag;
} // unitTesting

100
src/tracks/arena_graph.hpp Normal file
View File

@@ -0,0 +1,100 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2016 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.
#ifndef HEADER_ARENA_GRAPH_HPP
#define HEADER_ARENA_GRAPH_HPP
#include "tracks/graph.hpp"
#include "utils/cpp2011.hpp"
#include <set>
class ArenaNode;
class XMLNode;
/**
* \brief A graph made from navmesh
* \ingroup tracks
*/
class ArenaGraph : public Graph
{
private:
/** The actual graph data structure, it is an adjacency matrix. */
std::vector<std::vector<float>> m_distance_matrix;
/** The matrix that is used to store computed shortest paths. */
std::vector<std::vector<int16_t>> m_parent_node;
/** Used in soccer mode to colorize the goal lines in minimap. */
std::set<int> m_red_node;
std::set<int> m_blue_node;
// ------------------------------------------------------------------------
void loadGoalNodes(const XMLNode *node);
// ------------------------------------------------------------------------
void loadNavmesh(const std::string &navmesh);
// ------------------------------------------------------------------------
void buildGraph();
// ------------------------------------------------------------------------
void setNearbyNodesOfAllNodes();
// ------------------------------------------------------------------------
void computeDijkstra(int n);
// ------------------------------------------------------------------------
void computeFloydWarshall();
// ------------------------------------------------------------------------
static std::vector<int16_t> getPathFromTo(int from, int to,
const std::vector< std::vector< int16_t > >& parent_node);
// ------------------------------------------------------------------------
virtual bool hasLapLine() const OVERRIDE { return false; }
// ------------------------------------------------------------------------
virtual void differentNodeColor(int n, video::SColor* c) const OVERRIDE;
public:
static ArenaGraph* get() { return dynamic_cast<ArenaGraph*>(m_graph); }
// ------------------------------------------------------------------------
static void unitTesting();
// ------------------------------------------------------------------------
ArenaGraph(const std::string &navmesh, const XMLNode *node = NULL);
// ------------------------------------------------------------------------
virtual ~ArenaGraph() {}
// ------------------------------------------------------------------------
ArenaNode* getNode(unsigned int i) const;
// ------------------------------------------------------------------------
/** Returns the next node on the shortest path from i to j.
* Note: m_parent_node[j][i] contains the parent of i on path from j to i,
* which is the next node on the path from i to j (undirected graph)
*/
int getNextNode(int i, int j) const
{
if (i == Graph::UNKNOWN_SECTOR || j == Graph::UNKNOWN_SECTOR)
return Graph::UNKNOWN_SECTOR;
return (int)(m_parent_node[j][i]);
}
// ------------------------------------------------------------------------
/** Returns the distance between any two nodes */
float getDistance(int from, int to) const
{
if (from == Graph::UNKNOWN_SECTOR || to == Graph::UNKNOWN_SECTOR)
return 99999.0f;
return m_distance_matrix[from][to];
}
}; // ArenaGraph
#endif

45
src/tracks/arena_node.cpp Normal file
View File

@@ -0,0 +1,45 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2016 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.
#include "tracks/arena_node.hpp"
// ----------------------------------------------------------------------------
ArenaNode::ArenaNode(const Vec3 &p0, const Vec3 &p1, const Vec3 &p2,
const Vec3 &p3, const Vec3 &normal,
unsigned int node_index)
: Quad(p0, p1, p2, p3, normal, node_index)
{
Vec3 lower_center = (p0 + p1) * 0.5f;
Vec3 upper_center = (p2 + p3) * 0.5f;
m_line = core::line3df(lower_center.toIrrVector(),
upper_center.toIrrVector());
} // ArenaNode
// ----------------------------------------------------------------------------
/** Returns the square of the distance between the given point and any point
* on the 'centre' line, i.e. the finite line from the middle point of the
* lower end of the node to the middle point of the upper end of the node
* which belongs to this node.
* \param xyz The point for which the distance to the line is computed.
*/
float ArenaNode::getDistance2FromPoint(const Vec3 &xyz) const
{
core::vector3df closest = m_line.getClosestPoint(xyz.toIrrVector());
return (closest-xyz.toIrrVector()).getLengthSQ();
} // getDistance2FromPoint

68
src/tracks/arena_node.hpp Normal file
View File

@@ -0,0 +1,68 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2016 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.
#ifndef HEADER_ARENA_NODE_HPP
#define HEADER_ARENA_NODE_HPP
#include "tracks/quad.hpp"
#include "utils/cpp2011.hpp"
#include <vector>
/**
* \ingroup tracks
*/
class ArenaNode : public Quad
{
private:
core::line3df m_line;
std::vector<int> m_adjacent_nodes;
std::vector<int> m_nearby_nodes;
public:
ArenaNode(const Vec3 &p0, const Vec3 &p1, const Vec3 &p2, const Vec3 &p3,
const Vec3 &normal, unsigned int node_index);
// ------------------------------------------------------------------------
virtual ~ArenaNode() {}
// ------------------------------------------------------------------------
const std::vector<int>& getAdjacentNodes() { return m_adjacent_nodes; }
// ------------------------------------------------------------------------
std::vector<int>* getNearbyNodes() { return &m_nearby_nodes; }
// ------------------------------------------------------------------------
void setAdjacentNodes(const std::vector<int>& nodes)
{
m_adjacent_nodes = nodes;
}
// ------------------------------------------------------------------------
void setNearbyNodes(const std::vector<int>& nodes)
{
m_nearby_nodes = nodes;
}
// ------------------------------------------------------------------------
/** Returns true if the quad lies near the edge, which means it doesn't
* have 4 adjacent quads.
*/
bool isNearEdge() const { return m_adjacent_nodes.size() != 4; }
// ------------------------------------------------------------------------
virtual float getDistance2FromPoint(const Vec3 &xyz) const OVERRIDE;
};
#endif

View File

@@ -0,0 +1,47 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2016 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.
#ifndef HEADER_ARENA_NODE_3D_HPP
#define HEADER_ARENA_NODE_3D_HPP
#include "tracks/arena_node.hpp"
#include "tracks/bounding_box_3d.hpp"
/**
* \ingroup tracks
*/
class ArenaNode3D : public ArenaNode,
public BoundingBox3D
{
public:
ArenaNode3D(const Vec3 &p0, const Vec3 &p1, const Vec3 &p2, const Vec3 &p3,
const Vec3 &normal, unsigned int node_index)
: ArenaNode(p0, p1, p2, p3, normal, node_index),
BoundingBox3D(p0, p1, p2, p3, normal) {}
// ------------------------------------------------------------------------
virtual bool pointInside(const Vec3& p,
bool ignore_vertical = false) const OVERRIDE
{
return BoundingBox3D::pointInside(p);
}
// ------------------------------------------------------------------------
virtual bool is3DQuad() const OVERRIDE { return true; }
};
#endif

View File

@@ -1,473 +0,0 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2009-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, B
#include "tracks/battle_graph.hpp"
#include <IMesh.h>
#include <ICameraSceneNode.h>
#include <IMeshSceneNode.h>
#include "config/user_config.hpp"
#include "io/file_manager.hpp"
#include "io/xml_node.hpp"
#include "items/item_manager.hpp"
#include "race/race_manager.hpp"
#include "tracks/navmesh.hpp"
#include "tracks/quad.hpp"
#include "tracks/track.hpp"
#include "tracks/track_manager.hpp"
#include "utils/log.hpp"
#include <algorithm>
#include <queue>
const int BattleGraph::UNKNOWN_POLY = -1;
BattleGraph * BattleGraph::m_battle_graph = NULL;
/** Constructor, Creates a navmesh, builds a graph from the navmesh and
* then runs shortest path algorithm to find and store paths to be used
* by the AI. */
BattleGraph::BattleGraph(const std::string &navmesh_file_name,
const XMLNode *node)
{
m_items_on_graph.clear();
NavMesh::create(navmesh_file_name);
m_navmesh_file = navmesh_file_name;
buildGraph(NavMesh::get());
// Compute shortest distance from all nodes
for(unsigned int i=0; i < NavMesh::get()->getNumberOfQuads(); i++)
computeDijkstra(i);
sortNearbyQuad();
if (node && race_manager->getMinorMode() == RaceManager::MINOR_MODE_SOCCER)
loadGoalNodes(node);
} // BattleGraph
// -----------------------------------------------------------------------------
/** Destructor, destroys NavMesh and the debug mesh if it exists */
BattleGraph::~BattleGraph(void)
{
NavMesh::destroy();
if(UserConfigParams::m_track_debug)
cleanupDebugMesh();
GraphStructure::destroyRTT();
} // ~BattleGraph
// ----------------------------------------------------------------------------
/** Builds a graph from an existing NavMesh. The graph is stored as an
* adjacency matrix. */
void BattleGraph::buildGraph(NavMesh* navmesh)
{
const unsigned int n_quads = navmesh->getNumberOfQuads();
m_distance_matrix = std::vector<std::vector<float>>
(n_quads, std::vector<float>(n_quads, 9999.9f));
for(unsigned int i = 0; i < n_quads; i++)
{
const Quad& cur_quad = navmesh->getQuad(i);
for (const int& adjacent : navmesh->getAdjacentQuads(i))
{
Vec3 diff = navmesh->getQuad(adjacent).getCenter()
- cur_quad.getCenter();
float distance = diff.length();
m_distance_matrix[i][adjacent] = distance;
}
m_distance_matrix[i][i] = 0.0f;
}
// Allocate and initialise the previous node data structure:
m_parent_poly = std::vector<std::vector<int>>
(n_quads, std::vector<int>(n_quads, BattleGraph::UNKNOWN_POLY));
for (unsigned int i = 0; i < n_quads; i++)
{
for (unsigned int j = 0; j < n_quads; j++)
{
if(i == j || m_distance_matrix[i][j] >= 9899.9f)
m_parent_poly[i][j] = -1;
else
m_parent_poly[i][j] = i;
} // for j
} // for i
} // buildGraph
// ----------------------------------------------------------------------------
/** Dijkstra shortest path computation. It computes the shortest distance from
* the specified node 'source' to all other nodes. At the end of the
* computation, m_distance_matrix[i][j] stores the shortest path distance from
* source to j and m_parent_poly[source][j] stores the last vertex visited on
* the shortest path from i to j before visiting j. Suppose the shortest path
* from i to j is i->......->k->j then m_parent_poly[i][j] = k
*/
void BattleGraph::computeDijkstra(int source)
{
// Stores the distance (float) to 'source' from a specified node (int)
typedef std::pair<int, float> IndDistPair;
class Shortest
{
public:
bool operator()(const IndDistPair &p1, const IndDistPair &p2)
{
return p1.second > p2.second;
}
};
std::priority_queue<IndDistPair, std::vector<IndDistPair>, Shortest> queue;
IndDistPair begin(source, 0.0f);
queue.push(begin);
const unsigned int n=getNumNodes();
std::vector<bool> visited;
visited.resize(n, false);
NavMesh *navmesh = NavMesh::get();
while(!queue.empty())
{
// Get element with shortest path
IndDistPair current = queue.top();
queue.pop();
int cur_index = current.first;
if(visited[cur_index]) continue;
visited[cur_index] = true;
for (const int& adjacent : navmesh->getAdjacentQuads(cur_index))
{
// Distance already computed, can be ignored
if(visited[adjacent]) continue;
float new_dist = current.second + m_distance_matrix[cur_index][adjacent];
if(new_dist < m_distance_matrix[source][adjacent])
{
m_distance_matrix[source][adjacent] = new_dist;
m_parent_poly[source][adjacent] = cur_index;
}
IndDistPair pair(adjacent, new_dist);
queue.push(pair);
}
}
} // computeDijkstra
// ----------------------------------------------------------------------------
/** THIS FUNCTION IS ONLY USED FOR UNIT-TESTING, to verify that the new
* Dijkstra algorithm gives the same results.
* computeFloydWarshall() computes the shortest distance between any two
* nodes. At the end of the computation, m_distance_matrix[i][j] stores the
* shortest path distance from i to j and m_parent_poly[i][j] stores the last
* vertex visited on the shortest path from i to j before visiting j. Suppose
* the shortest path from i to j is i->......->k->j then
* m_parent_poly[i][j] = k
*/
void BattleGraph::computeFloydWarshall()
{
unsigned int n = getNumNodes();
// initialize m_parent_poly with unknown_poly so that if no path is found b/w i and j
// then m_parent_poly[i][j] = -1 (UNKNOWN_POLY)
// AI must check this
m_parent_poly = std::vector< std::vector<int> > (n, std::vector<int>(n,BattleGraph::UNKNOWN_POLY));
for(unsigned int i=0; i<n; i++)
{
for(unsigned int j=0; j<n; j++)
{
if(i == j || m_distance_matrix[i][j]>=9899.9f) m_parent_poly[i][j]=-1;
else m_parent_poly[i][j] = i;
}
}
for(unsigned int k=0; k<n; k++)
{
for(unsigned int i=0; i<n; i++)
{
for(unsigned int j=0; j<n; j++)
{
if( (m_distance_matrix[i][k] + m_distance_matrix[k][j]) < m_distance_matrix[i][j])
{
m_distance_matrix[i][j] = m_distance_matrix[i][k] + m_distance_matrix[k][j];
m_parent_poly[i][j] = m_parent_poly[k][j];
}
}
}
}
} // computeFloydWarshall
// -----------------------------------------------------------------------------
/** Maps items on battle graph */
void BattleGraph::findItemsOnGraphNodes()
{
const ItemManager* item_manager = ItemManager::get();
unsigned int item_count = item_manager->getNumberOfItems();
for (unsigned int i = 0; i < item_count; ++i)
{
const Item* item = item_manager->getItem(i);
Vec3 xyz = item->getXYZ();
int polygon = BattleGraph::UNKNOWN_POLY;
for (unsigned int j = 0; j < this->getNumNodes(); ++j)
{
if (getQuadOfNode(j).pointInQuad(xyz, false))
polygon = j;
}
if (polygon != BattleGraph::UNKNOWN_POLY)
{
m_items_on_graph.push_back(std::make_pair(item, polygon));
Log::debug("BattleGraph","item number %d is on polygon %d", i, polygon);
}
else
Log::debug("BattleGraph","Can't map item number %d with a suitable polygon", i);
}
} // findItemsOnGraphNodes
// -----------------------------------------------------------------------------
int BattleGraph::pointToNode(const int cur_node,
const Vec3& cur_point,
bool ignore_vertical) const
{
if (cur_node == BattleGraph::UNKNOWN_POLY)
{
// Try all nodes in the battle graph
for (unsigned int node = 0; node < this->getNumNodes(); node++)
{
const Quad& quad = this->getQuadOfNode(node);
if (quad.pointInQuad(cur_point, ignore_vertical))
{
return node;
}
}
}
else
{
// Check if the point is still on the same node
const Quad& cur_quad = this->getQuadOfNode(cur_node);
if (cur_quad.pointInQuad(cur_point, ignore_vertical)) return cur_node;
// If not then check all nearby quads (8 quads)
// Skip the same node
assert(cur_node == m_nearby_quads[cur_node][0]);
for (unsigned int i = 1; i < m_nearby_quads[0].size(); i++)
{
const int test_node = m_nearby_quads[cur_node][i];
const Quad& quad = this->getQuadOfNode(test_node);
if (quad.pointInQuad(cur_point, ignore_vertical))
{
return test_node;
}
}
// Current node is still unkown:
// Calculated distance from saved node to current position,
// if it's close enough than use the saved node anyway, it
// may happen when the kart stays on the edge of obstacles
Vec3 diff = (cur_quad.getCenter() - cur_point);
float dist = diff.length();
if (dist < 3.0f)
return cur_node;
}
return BattleGraph::UNKNOWN_POLY;
} // pointToNode
// -----------------------------------------------------------------------------
const bool BattleGraph::differentNodeColor(int n, NodeColor* c) const
{
std::set<int>::iterator it;
it = m_red_node.find(n);
if (it != m_red_node.end())
{
*c = COLOR_RED;
return true;
}
it = m_blue_node.find(n);
if (it != m_blue_node.end())
{
*c = COLOR_BLUE;
return true;
}
return false;
} // differentNodeColor
// -----------------------------------------------------------------------------
void BattleGraph::loadGoalNodes(const XMLNode *node)
{
m_red_node.clear();
m_blue_node.clear();
const XMLNode *check_node = node->getNode("checks");
for (unsigned int i = 0; i < check_node->getNumNodes(); i++)
{
const XMLNode *goal = check_node->getNode(i);
if (goal->getName() =="goal")
{
Vec3 p1, p2;
bool first_goal = false;
goal->get("first_goal", &first_goal);
goal->get("p1", &p1);
goal->get("p2", &p2);
int first = pointToNode(/*cur_node*/-1, p1, true);
int last = pointToNode(/*cur_node*/-1, p2, true);
first_goal ? m_blue_node.insert(first) : m_red_node.insert(first);
first_goal ? m_blue_node.insert(last) : m_red_node.insert(last);
while (first != last)
{
// Find all the nodes which connect the two points of
// goal, notice: only work if it's a straight line
first = getNextShortestPathPoly(first, last);
first_goal ? m_blue_node.insert(first) :
m_red_node.insert(first);
}
}
}
} // loadGoalNodes
// ============================================================================
/** Unit testing for battle graph distance and parent node computation.
* Instead of using hand-tuned test cases we use the tested, verified and
* easier to understand Floyd-Warshall algorithm to compute the distances,
* and check if the (significanty faster) Dijkstra algorithm gives the same
* results. For now we use the cave mesh as test case.
*/
void BattleGraph::unitTesting()
{
Track *track = track_manager->getTrack("cave");
std::string navmesh_file_name=track->getTrackFile("navmesh.xml");
double s = StkTime::getRealTime();
BattleGraph *bg = new BattleGraph(navmesh_file_name);
double e = StkTime::getRealTime();
Log::error("Time", "Dijkstra %lf", e-s);
// Save the Dijkstra results
std::vector< std::vector< float > > distance_matrix = bg->m_distance_matrix;
std::vector< std::vector< int > > parent_poly = bg->m_parent_poly;
bg->buildGraph(NavMesh::get());
// Now compute results with Floyd-Warshall
s = StkTime::getRealTime();
bg->computeFloydWarshall();
e = StkTime::getRealTime();
Log::error("Time", "Floyd-Warshall %lf", e-s);
int error_count = 0;
for(unsigned int i=0; i<bg->m_distance_matrix.size(); i++)
{
for(unsigned int j=0; j<bg->m_distance_matrix[i].size(); j++)
{
if(bg->m_distance_matrix[i][j] - distance_matrix[i][j] > 0.001f)
{
Log::error("BattleGraph",
"Incorrect distance %d, %d: Dijkstra: %f F.W.: %f",
i, j, distance_matrix[i][j], bg->m_distance_matrix[i][j]);
error_count++;
} // if distance is too different
// Unortunately it happens frequently that there are different
// shortest path with the same length. And Dijkstra might find
// a different path then Floyd-Warshall. So the test for parent
// polygon often results in false positives, so it is disabled,
// but I leave the code in place in case it is useful for some
// debugging in the feature
#undef TEST_PARENT_POLY_EVEN_THOUGH_MANY_FALSE_POSITIVES
#ifdef TEST_PARENT_POLY_EVEN_THOUGH_MANY_FALSE_POSITIVES
if(bg->m_parent_poly[i][j] != parent_poly[i][j])
{
error_count++;
std::vector<int> dijkstra_path = getPathFromTo(i, j, parent_poly);
std::vector<int> floyd_path = getPathFromTo(i, j, bg->m_parent_poly);
if(dijkstra_path.size()!=floyd_path.size())
{
Log::error("BattleGraph",
"Incorrect path length %d, %d: Dijkstra: %d F.W.: %d",
i, j, parent_poly[i][j], bg->m_parent_poly[i][j]);
continue;
}
Log::error("BattleGraph", "Path problems from %d to %d:",
i, j);
for (unsigned k = 0; k < dijkstra_path.size(); k++)
{
if(dijkstra_path[k]!=floyd_path[k])
Log::error("BattleGraph", "%d/%d dijkstra: %d floyd %d",
k, dijkstra_path.size(), dijkstra_path[k],
floyd_path[k]);
} // for k<dijkstra_path.size()
} // if dijkstra parent_poly != floyd parent poly
#endif
} // for j
} // for i
} // unitTesting
// ----------------------------------------------------------------------------
/** Determines the full path from 'from' to 'to' and returns it in a
* std::vector (in reverse order). Used only for unit testing.
*/
std::vector<int> BattleGraph::getPathFromTo(int from, int to,
const std::vector< std::vector< int > > parent_poly)
{
std::vector<int> path;
path.push_back(to);
while(from!=to)
{
to = parent_poly[from][to];
path.push_back(to);
}
return path;
} // getPathFromTo
// ----------------------------------------------------------------------------
void BattleGraph::sortNearbyQuad()
{
// Only try the nearby 8 quads
const unsigned int n = 8;
m_nearby_quads = std::vector< std::vector<int> >
(this->getNumNodes(), std::vector<int>(n, BattleGraph::UNKNOWN_POLY));
for (unsigned int i = 0; i < this->getNumNodes(); i++)
{
// Get the distance to all nodes at i
std::vector<float> dist = m_distance_matrix[i];
for (unsigned int j = 0; j < n; j++)
{
std::vector<float>::iterator it =
std::min_element(dist.begin(), dist.end());
const int pos = it - dist.begin();
m_nearby_quads[i][j] = pos;
dist[pos] = 999999.0f;
}
}
} // sortNearbyQuad
// ----------------------------------------------------------------------------
void BattleGraph::set3DVerticesOfGraph(int i, video::S3DVertex *v,
const video::SColor &color) const
{
NavMesh::get()->getQuad(i).getVertices(v, color);
} // set3DVerticesOfGraph
// ----------------------------------------------------------------------------
const bool BattleGraph::isNodeInvisible(int n) const
{
return NavMesh::get()->getQuad(n).isInvisible();
} // isNodeInvisible

View File

@@ -1,166 +0,0 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2009-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, B
#ifndef HEADER_BATTLE_GRAPH_HPP
#define HEADER_BATTLE_GRAPH_HPP
#include <string>
#include <set>
#include <vector>
#include "tracks/graph_structure.hpp"
#include "tracks/navmesh.hpp"
class Item;
class ItemManager;
class XMLNode;
/**
* \ingroup tracks
*
* \brief This class stores a graph constructed from the navigatoin mesh.
* It uses a 'simplified singleton' design pattern: it has a static create
* function to create exactly one instance, a destroy function, and a get
* function (that does not have the side effect of the 'normal singleton'
* design pattern to create an instance).
\ingroup tracks
*/
class BattleGraph : public GraphStructure
{
private:
static BattleGraph *m_battle_graph;
/** The actual graph data structure, it is an adjacency matrix */
std::vector< std::vector< float > > m_distance_matrix;
/** The matrix that is used to store computed shortest paths */
std::vector< std::vector< int > > m_parent_poly;
std::vector< std::vector< int > > m_nearby_quads;
/** Stores the name of the file containing the NavMesh data */
std::string m_navmesh_file;
std::vector< std::pair<const Item*, int> > m_items_on_graph;
std::set<int> m_red_node;
std::set<int> m_blue_node;
void buildGraph(NavMesh*);
void computeFloydWarshall();
void loadGoalNodes(const XMLNode *node);
void sortNearbyQuad();
BattleGraph(const std::string &navmesh_file_name, const XMLNode *node=NULL);
~BattleGraph(void);
// ------------------------------------------------------------------------
virtual void set3DVerticesOfGraph(int i, video::S3DVertex *v,
const video::SColor &color) const;
// ------------------------------------------------------------------------
virtual void getGraphBoundingBox(Vec3 *min, Vec3 *max) const
{ NavMesh::get()->getBoundingBox(min, max); }
// ------------------------------------------------------------------------
virtual const bool isNodeInvisible(int n) const;
// ------------------------------------------------------------------------
virtual const bool hasLapLine() const
{ return false; }
// ------------------------------------------------------------------------
virtual const bool differentNodeColor(int n, NodeColor* c) const;
void computeDijkstra(int n);
static std::vector<int> getPathFromTo(int from, int to,
const std::vector< std::vector< int > > parent_poly);
public:
static const int UNKNOWN_POLY;
void findItemsOnGraphNodes();
// ----------------------------------------------------------------------
int pointToNode(const int cur_node,
const Vec3& cur_point,
bool ignore_vertical) const;
// ------------------------------------------------------------------------
static void unitTesting();
// ------------------------------------------------------------------------
/** Returns the one instance of this object. */
static BattleGraph *get() { return m_battle_graph; }
// ------------------------------------------------------------------------
/** Asserts that no BattleGraph instance exists. Then
* creates a BattleGraph instance. */
static void create(const std::string &navmesh_file_name,
const XMLNode *node)
{
assert(m_battle_graph==NULL);
m_battle_graph = new BattleGraph(navmesh_file_name, node);
} // create
// ------------------------------------------------------------------------
/** Cleans up the BattleGraph instance if it exists */
static void destroy()
{
if(m_battle_graph)
{
delete m_battle_graph;
m_battle_graph = NULL;
}
} // destroy
// ------------------------------------------------------------------------
/** Returns the number of nodes in the BattleGraph (equal to the number
* of quads in the NavMesh
*/
virtual const unsigned int getNumNodes() const
{ return NavMesh::get()->getNumberOfQuads(); }
// ------------------------------------------------------------------------
/** Returns the distance between any two nodes */
float getDistance(int from, int to) const
{
if (from == BattleGraph::UNKNOWN_POLY ||
to == BattleGraph::UNKNOWN_POLY)
return 0.0f;
return m_distance_matrix[from][to];
}
// ------------------------------------------------------------------------
/** Returns the next polygon on the shortest path from i to j.
* Note: m_parent_poly[j][i] contains the parent of i on path from j to i,
* which is the next node on the path from i to j (undirected graph)
*/
int getNextShortestPathPoly(int i, int j) const
{
if (i == BattleGraph::UNKNOWN_POLY || j == BattleGraph::UNKNOWN_POLY)
return BattleGraph::UNKNOWN_POLY;
return m_parent_poly[j][i];
}
// ------------------------------------------------------------------------
std::vector<std::pair<const Item*, int>>& getItemList()
{ return m_items_on_graph; }
// ------------------------------------------------------------------------
void insertItems(Item* item, int polygon)
{ m_items_on_graph.push_back(std::make_pair(item, polygon)); }
// ------------------------------------------------------------------------
/** Returns the quad that belongs to a node. */
const Quad& getQuadOfNode(unsigned int n) const
{ return NavMesh::get()->getQuad(n); }
// ------------------------------------------------------------------------
/** Returns true if the quad lies near the edge, which means it doesn't
* have 4 adjacent quads.
*/
bool isNearEdge(unsigned int n) const
{ return NavMesh::get()->getAdjacentQuads(n).size() != 4; }
// ------------------------------------------------------------------------
}; //BattleGraph
#endif

View File

@@ -0,0 +1,84 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2016 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.
#ifndef HEADER_NODE_BOUNDING_BOX_3D_HPP
#define HEADER_NODE_BOUNDING_BOX_3D_HPP
#include "utils/vec3.hpp"
/**
* \ingroup tracks
*/
class BoundingBox3D
{
private:
/** A 3D box using to check if a point lies inside a quad.
*/
Vec3 m_box_faces[6][4];
public:
// ------------------------------------------------------------------------
BoundingBox3D(const Vec3& p0, const Vec3& p1, const Vec3& p2,
const Vec3& p3, const Vec3& normal)
{
// Compute the node bounding box used by pointInside
Vec3 box_corners[8];
float box_high = 5.0f;
float box_low = 1.0f;
box_corners[0] = p0 + box_high * normal;
box_corners[1] = p1 + box_high * normal;
box_corners[2] = p2 + box_high * normal;
box_corners[3] = p3 + box_high * normal;
box_corners[4] = p0 - box_low * normal;
box_corners[5] = p1 - box_low * normal;
box_corners[6] = p2 - box_low * normal;
box_corners[7] = p3 - box_low * normal;
Vec3 box_faces[6][4] =
{
{ box_corners[0], box_corners[1], box_corners[2], box_corners[3] },
{ box_corners[3], box_corners[2], box_corners[6], box_corners[7] },
{ box_corners[7], box_corners[6], box_corners[5], box_corners[4] },
{ box_corners[1], box_corners[0], box_corners[4], box_corners[5] },
{ box_corners[4], box_corners[0], box_corners[3], box_corners[7] },
{ box_corners[1], box_corners[5], box_corners[6], box_corners[2] }
};
for (unsigned int i = 0; i < 6 ; i++)
{
for (unsigned int j = 0; j < 4; j++)
m_box_faces[i][j] = box_faces[i][j];
}
}
// ------------------------------------------------------------------------
bool pointInside(const Vec3& p, bool ignore_vertical = false) const
{
float side = p.sideofPlane(m_box_faces[0][0], m_box_faces[0][1],
m_box_faces[0][2]);
for (int i = 1; i < 6; i++)
{
if (side * p.sideofPlane(m_box_faces[i][0], m_box_faces[i][1],
m_box_faces[i][2]) < 0)
return false;
}
return true;
}
};
#endif

View File

@@ -28,7 +28,8 @@
#include "tracks/check_lap.hpp"
#include "tracks/check_line.hpp"
#include "tracks/check_structure.hpp"
#include "tracks/track.hpp"
#include "tracks/drive_graph.hpp"
#include "utils/log.hpp"
CheckManager *CheckManager::m_check_manager = NULL;
@@ -84,7 +85,7 @@ void CheckManager::load(const XMLNode &node)
for(it=check_structures_to_change_state.begin();
it != check_structures_to_change_state.end(); it++)
{
if(QuadGraph::get()->isReverse())
if(DriveGraph::get()->isReverse())
m_all_checks[*it]->addSuccessor(i);
else
m_all_checks[i]->addSuccessor(*it);

View File

@@ -16,98 +16,133 @@
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, B
#include "tracks/quad_graph.hpp"
#include "tracks/drive_graph.hpp"
#include "LinearMath/btTransform.h"
#include <IMesh.h>
#include <ICameraSceneNode.h>
#include "graphics/central_settings.hpp"
#include "config/user_config.hpp"
#include "graphics/callbacks.hpp"
#include "graphics/irr_driver.hpp"
#include "graphics/screen_quad.hpp"
#include "graphics/shaders.hpp"
#include "graphics/rtts.hpp"
#include "io/file_manager.hpp"
#include "io/xml_node.hpp"
#include "modes/world.hpp"
#include "tracks/check_lap.hpp"
#include "tracks/check_line.hpp"
#include "tracks/check_manager.hpp"
#include "tracks/quad_set.hpp"
#include "tracks/drive_node.hpp"
#include "tracks/track.hpp"
#include "graphics/glwrap.hpp"
const int QuadGraph::UNKNOWN_SECTOR = -1;
QuadGraph *QuadGraph::m_quad_graph = NULL;
// ----------------------------------------------------------------------------
/** Constructor, loads the graph information for a given set of quads
* from a graph file.
* \param quad_file_name Name of the file of all quads
* \param graph_file_name Name of the file describing the actual graph
*/
QuadGraph::QuadGraph(const std::string &quad_file_name,
const std::string &graph_file_name,
const bool reverse) : m_reverse(reverse)
DriveGraph::DriveGraph(const std::string &quad_file_name,
const std::string &graph_file_name,
const bool reverse) : m_reverse(reverse)
{
m_lap_length = 0;
QuadSet::create();
QuadSet::get()->init(quad_file_name);
m_quad_filename = quad_file_name;
m_quad_graph = this;
load(graph_file_name);
} // QuadGraph
m_lap_length = 0;
m_quad_filename = quad_file_name;
Graph::setGraph(this);
load(quad_file_name, graph_file_name);
} // DriveGraph
// -----------------------------------------------------------------------------
/** Destructor, removes all nodes of the graph. */
QuadGraph::~QuadGraph()
// ----------------------------------------------------------------------------
void DriveGraph::addSuccessor(unsigned int from, unsigned int to)
{
QuadSet::destroy();
for(unsigned int i=0; i<m_all_nodes.size(); i++) {
delete m_all_nodes[i];
}
if(UserConfigParams::m_track_debug)
cleanupDebugMesh();
GraphStructure::destroyRTT();
} // ~QuadGraph
// -----------------------------------------------------------------------------
void QuadGraph::addSuccessor(unsigned int from, unsigned int to) {
if(m_reverse)
m_all_nodes[to]->addSuccessor(from);
getNode(to)->addSuccessor(from);
else
m_all_nodes[from]->addSuccessor(to);
getNode(from)->addSuccessor(to);
} // addSuccessor
// -----------------------------------------------------------------------------
/** Loads a quad graph from a file.
* \param filename Name of the file to load.
*/
void QuadGraph::load(const std::string &filename)
// ----------------------------------------------------------------------------
/** This function interprets a point specification as an attribute in the
xml quad file. It understands two different specifications:
p1="n:p" : get point p from square n (n, p integers)
p1="p1,p2,p3" : make a 3d point out of these 3 floating point values
*/
void DriveGraph::getPoint(const XMLNode *xml,
const std::string &attribute_name,
Vec3* result) const
{
std::string s;
xml->get(attribute_name, &s);
int pos=(int)s.find_first_of(":");
if(pos>0) // n:p specification
{
std::vector<std::string> l = StringUtils::split(s, ':');
int n=atoi(l[0].c_str());
int p=atoi(l[1].c_str());
*result=(*m_all_nodes[n])[p];
}
else
{
xml->get(attribute_name, result);
}
} // getPoint
// ----------------------------------------------------------------------------
/** Loads a drive graph from a file.
* \param filename Name of the quad file to load.
* \param filename Name of the graph file to load.
*/
void DriveGraph::load(const std::string &quad_file_name,
const std::string &filename)
{
XMLNode *quad = file_manager->createXMLTree(quad_file_name);
if (!quad || quad->getName() != "quads")
{
Log::error("DriveGraph : Quad xml '%s' not found.", filename.c_str());
delete quad;
return;
}
// Each quad is part of the graph exactly once now.
for(unsigned int i=0; i<quad->getNumNodes(); i++)
{
const XMLNode *xml_node = quad->getNode(i);
if(xml_node->getName()!="quad")
{
Log::warn("DriveGraph: Unsupported node type '%s' found in '%s' - ignored.",
xml_node->getName().c_str(), filename.c_str());
continue;
}
// Note that it's not easy to do the reading of the parameters here
// in quad, since the specification in the xml can contain references
// to previous points. E.g.:
// <quad p0="40:3" p1="40:2" p2="25.396030 0.770338 64.796539" ...
Vec3 p0, p1, p2, p3;
getPoint(xml_node, "p0", &p0);
getPoint(xml_node, "p1", &p1);
getPoint(xml_node, "p2", &p2);
getPoint(xml_node, "p3", &p3);
bool invisible=false;
xml_node->get("invisible", &invisible);
bool ai_ignore=false;
xml_node->get("ai-ignore", &ai_ignore);
createQuad(p0, p1, p2, p3, m_all_nodes.size(), invisible, ai_ignore,
false/*is_arena*/);
}
delete quad;
const XMLNode *xml = file_manager->createXMLTree(filename);
if(!xml)
{
// No graph file exist, assume a default loop X -> X+1
// i.e. each quad is part of the graph exactly once.
// First create an empty graph node for each quad:
for(unsigned int i=0; i<QuadSet::get()->getNumberOfQuads(); i++)
m_all_nodes.push_back(new GraphNode(i, (unsigned int) m_all_nodes.size()));
// Then set the default loop:
// Set the default loop:
setDefaultSuccessors();
computeDirectionData();
if (m_all_nodes.size() > 0)
{
m_lap_length = m_all_nodes[m_all_nodes.size()-1]->getDistanceFromStart()
+ m_all_nodes[m_all_nodes.size()-1]->getDistanceToSuccessor(0);
m_lap_length = getNode(m_all_nodes.size()-1)->getDistanceFromStart()
+ getNode(m_all_nodes.size()-1)->getDistanceToSuccessor(0);
}
else
{
Log::error("Quad Graph", "No node in driveline graph.");
Log::error("DriveGraph", "No node in driveline graph.");
m_lap_length = 10.0f;
}
@@ -119,29 +154,16 @@ void QuadGraph::load(const std::string &filename)
for(unsigned int node_index=0; node_index<xml->getNumNodes(); node_index++)
{
const XMLNode *xml_node = xml->getNode(node_index);
// First graph node definitions:
// -----------------------------
// Load the definition of edges between the graph nodes:
// -----------------------------------------------------
if(xml_node->getName()=="node-list")
{
// A list of quads is connected to a list of graph nodes:
unsigned int from, to;
xml_node->get("from-quad", &from);
// Each quad is part of the graph exactly once now.
unsigned int to = 0;
xml_node->get("to-quad", &to);
for(unsigned int i=from; i<=to; i++)
{
m_all_nodes.push_back(new GraphNode(i, (unsigned int) m_all_nodes.size()));
}
assert(to + 1 == m_all_nodes.size());
continue;
}
else if(xml_node->getName()=="node")
{
// A single quad is connected to a single graph node.
unsigned int id;
xml_node->get("quad", &id);
m_all_nodes.push_back(new GraphNode(id, (unsigned int) m_all_nodes.size()));
}
// Then the definition of edges between the graph nodes:
// -----------------------------------------------------
else if(xml_node->getName()=="edge-loop")
{
// A closed loop:
@@ -179,7 +201,7 @@ void QuadGraph::load(const std::string &filename)
} // edge
else
{
Log::error("Quad Graph", "Incorrect specification in '%s': '%s' ignored.",
Log::error("DriveGraph", "Incorrect specification in '%s': '%s' ignored.",
filename.c_str(), xml_node->getName().c_str());
continue;
} // incorrect specification
@@ -195,11 +217,12 @@ void QuadGraph::load(const std::string &filename)
m_lap_length = -1;
for(unsigned int i=0; i<m_all_nodes.size(); i++)
{
float l = m_all_nodes[i]->getDistanceFromStart()
+ m_all_nodes[i]->getDistanceToSuccessor(0);
float l = getNode(i)->getDistanceFromStart()
+ getNode(i)->getDistanceToSuccessor(0);
if(l > m_lap_length)
m_lap_length = l;
}
} // load
// ----------------------------------------------------------------------------
@@ -209,18 +232,18 @@ void QuadGraph::load(const std::string &filename)
* but in reverse mode (where node 0 is actually the end of the track)
* this is 0's successor.
*/
unsigned int QuadGraph::getStartNode() const
unsigned int DriveGraph::getStartNode() const
{
return m_reverse ? m_all_nodes[0]->getSuccessor(0)
return m_reverse ? getNode(0)->getSuccessor(0)
: 0;
} // getStartNode
// ----------------------------------------------------------------------------
/** Sets the checkline requirements for all nodes in the graph.
*/
void QuadGraph::computeChecklineRequirements()
void DriveGraph::computeChecklineRequirements()
{
computeChecklineRequirements(m_all_nodes[0],
computeChecklineRequirements(getNode(0),
CheckManager::get()->getLapLineIndex());
} // computeChecklineRequirements
@@ -228,8 +251,8 @@ void QuadGraph::computeChecklineRequirements()
/** Finds which checklines must be visited before driving on this quad
* (useful for rescue)
*/
void QuadGraph::computeChecklineRequirements(GraphNode* node,
int latest_checkline)
void DriveGraph::computeChecklineRequirements(DriveNode* node,
int latest_checkline)
{
for (unsigned int n=0; n<node->getNumberOfSuccessors(); n++)
{
@@ -238,7 +261,7 @@ void QuadGraph::computeChecklineRequirements(GraphNode* node,
// warp-around
if (succ_id == 0) break;
GraphNode* succ = m_all_nodes[succ_id];
DriveNode* succ = getNode(succ_id);
int new_latest_checkline =
CheckManager::get()->getChecklineTriggering(node->getCenter(),
succ->getCenter() );
@@ -276,11 +299,11 @@ void QuadGraph::computeChecklineRequirements(GraphNode* node,
* (since on other graph nodes only one path can be used anyway, this
* saves some memory).
*/
void QuadGraph::setupPaths()
void DriveGraph::setupPaths()
{
for(unsigned int i=0; i<getNumNodes(); i++)
{
m_all_nodes[i]->setupPathsToNode();
getNode(i)->setupPathsToNode();
}
} // setupPaths
@@ -288,18 +311,18 @@ void QuadGraph::setupPaths()
/** This function sets a default successor for all graph nodes that currently
* don't have a successor defined. The default successor of node X is X+1.
*/
void QuadGraph::setDefaultSuccessors()
void DriveGraph::setDefaultSuccessors()
{
for(unsigned int i=0; i<m_all_nodes.size(); i++) {
if(m_all_nodes[i]->getNumberOfSuccessors()==0) {
if(getNode(i)->getNumberOfSuccessors()==0) {
addSuccessor(i,i+1>=m_all_nodes.size() ? 0 : i+1);
//~ m_all_nodes[i]->addSuccessor(i+1>=m_all_nodes.size() ? 0 : i+1);
//~ getNode(i)->addSuccessor(i+1>=m_all_nodes.size() ? 0 : i+1);
} // if size==0
} // for i<m_allNodes.size()
} // setDefaultSuccessors
// -----------------------------------------------------------------------------
/** Sets all start positions depending on the quad graph. The number of
/** Sets all start positions depending on the drive graph. The number of
* entries needed is defined by the size of the start_transform (though all
* entries will be overwritten).
* E.g. the karts will be placed as:
@@ -319,17 +342,17 @@ void QuadGraph::setDefaultSuccessors()
* \param sidewards_distance Distance in sidewards (X) direction between
* karts.
*/
void QuadGraph::setDefaultStartPositions(AlignedArray<btTransform>
void DriveGraph::setDefaultStartPositions(AlignedArray<btTransform>
*start_transforms,
unsigned int karts_per_row,
float forwards_distance,
float sidewards_distance,
float upwards_distance) const
unsigned int karts_per_row,
float forwards_distance,
float sidewards_distance,
float upwards_distance) const
{
// We start just before the start node (which will trigger lap
// counting when reached). The first predecessor is the one on
// the main driveline.
int current_node = m_all_nodes[getStartNode()]->getPredecessor(0);
int current_node = getNode(getStartNode())->getPredecessor(0);
float distance_from_start = 0.1f+forwards_distance;
@@ -350,20 +373,20 @@ void QuadGraph::setDefaultStartPositions(AlignedArray<btTransform>
else
{
// First find on which segment we have to start
while(distance_from_start > getNode(current_node).getNodeLength())
while(distance_from_start > getNode(current_node)->getNodeLength())
{
distance_from_start -= getNode(current_node).getNodeLength();
distance_from_start -= getNode(current_node)->getNodeLength();
// Only follow the main driveline, i.e. first predecessor
current_node = getNode(current_node).getPredecessor(0);
current_node = getNode(current_node)->getPredecessor(0);
}
const GraphNode &gn = getNode(current_node);
Vec3 center_line = gn.getLowerCenter() - gn.getUpperCenter();
const DriveNode* dn = getNode(current_node);
Vec3 center_line = dn->getLowerCenter() - dn->getUpperCenter();
center_line.normalize();
Vec3 horizontal_line = gn[2] - gn[3];
Vec3 horizontal_line = (*dn)[2] - (*dn)[3];
horizontal_line.normalize();
Vec3 start = gn.getUpperCenter()
Vec3 start = dn->getUpperCenter()
+ center_line * distance_from_start
+ horizontal_line * x_pos;
// Add a certain epsilon to the height in case that the
@@ -371,7 +394,7 @@ void QuadGraph::setDefaultStartPositions(AlignedArray<btTransform>
(*start_transforms)[i].setOrigin(start+Vec3(0,upwards_distance,0));
(*start_transforms)[i].setRotation(
btQuaternion(btVector3(0, 1, 0),
gn.getAngleToSuccessor(0)));
dn->getAngleToSuccessor(0)));
if(x_pos >= max_x_dist-sidewards_distance*0.5f)
{
x_pos = -max_x_dist;
@@ -395,17 +418,17 @@ void QuadGraph::setDefaultStartPositions(AlignedArray<btTransform>
* \param succ A vector of ints to which the successors are added.
* \param for_ai true if only quads accessible by the AI should be returned.
*/
void QuadGraph::getSuccessors(int node_number,
std::vector<unsigned int>& succ,
bool for_ai) const
void DriveGraph::getSuccessors(int node_number,
std::vector<unsigned int>& succ,
bool for_ai) const
{
const GraphNode *gn=m_all_nodes[node_number];
for(unsigned int i=0; i<gn->getNumberOfSuccessors(); i++)
const DriveNode *dn=getNode(node_number);
for(unsigned int i=0; i<dn->getNumberOfSuccessors(); i++)
{
// If getSuccessor is called for the AI, only add
// quads that are meant for the AI to be used.
if(!for_ai || !gn->ignoreSuccessorForAI(i))
succ.push_back(gn->getSuccessor(i));
if(!for_ai || !dn->ignoreSuccessorForAI(i))
succ.push_back(dn->getSuccessor(i));
}
} // getSuccessors
@@ -415,10 +438,10 @@ void QuadGraph::getSuccessors(int node_number,
* \param node The node index for which to set the distance from start.
* \param new_distance The new distance for the specified graph node.
*/
void QuadGraph::computeDistanceFromStart(unsigned int node, float new_distance)
void DriveGraph::computeDistanceFromStart(unsigned int node, float new_distance)
{
GraphNode *gn = m_all_nodes[node];
float current_distance = gn->getDistanceFromStart();
DriveNode *dn = getNode(node);
float current_distance = dn->getDistanceFromStart();
// If this node already has a distance defined, check if the new distance
// is longer, and if so adjust all following nodes. Without this the
@@ -432,25 +455,25 @@ void QuadGraph::computeDistanceFromStart(unsigned int node, float new_distance)
if(current_distance<new_distance)
{
float delta = new_distance - current_distance;
updateDistancesForAllSuccessors(gn->getQuadIndex(), delta, 0);
updateDistancesForAllSuccessors(dn->getIndex(), delta, 0);
}
return;
}
// Otherwise this node has no distance defined yet. Set the new
// distance, and recursively update all following nodes.
gn->setDistanceFromStart(new_distance);
dn->setDistanceFromStart(new_distance);
for(unsigned int i=0; i<gn->getNumberOfSuccessors(); i++)
for(unsigned int i=0; i<dn->getNumberOfSuccessors(); i++)
{
GraphNode *gn_next = m_all_nodes[gn->getSuccessor(i)];
DriveNode *dn_next = getNode(dn->getSuccessor(i));
// The start node (only node with distance 0) is reached again,
// recursion can stop now
if(gn_next->getDistanceFromStart()==0)
if(dn_next->getDistanceFromStart()==0)
continue;
computeDistanceFromStart(gn_next->getQuadIndex(),
new_distance + gn->getDistanceToSuccessor(i));
computeDistanceFromStart(dn_next->getIndex(),
new_distance + dn->getDistanceToSuccessor(i));
} // for i
} // computeDistanceFromStart
@@ -464,35 +487,35 @@ void QuadGraph::computeDistanceFromStart(unsigned int node, float new_distance)
* \param recursive_count Counts how often this function was called
* recursively in order to catch incorrect graphs that contain loops.
*/
void QuadGraph::updateDistancesForAllSuccessors(unsigned int indx, float delta,
void DriveGraph::updateDistancesForAllSuccessors(unsigned int indx, float delta,
unsigned int recursive_count)
{
if(recursive_count>getNumNodes())
{
Log::error("QuadGraph",
"Quad graph contains a loop (without start node).");
Log::fatal("QuadGraph",
Log::error("DriveGraph",
"DriveGraph contains a loop (without start node).");
Log::fatal("DriveGraph",
"Fix graph, check for directions of all shortcuts etc.");
}
recursive_count++;
GraphNode &g=getNode(indx);
g.setDistanceFromStart(g.getDistanceFromStart()+delta);
for(unsigned int i=0; i<g.getNumberOfSuccessors(); i++)
DriveNode* dn = getNode(indx);
dn->setDistanceFromStart(dn->getDistanceFromStart()+delta);
for(unsigned int i=0; i<dn->getNumberOfSuccessors(); i++)
{
GraphNode &g_next = getNode(g.getSuccessor(i));
DriveNode* dn_next = getNode(dn->getSuccessor(i));
// Stop when we reach the start node, i.e. the only node with a
// distance of 0
if(g_next.getDistanceFromStart()==0)
if(dn_next->getDistanceFromStart()==0)
continue;
// Only increase the distance from start of a successor node, if
// this successor has a distance from start that is smaller then
// the increased amount.
if(g.getDistanceFromStart()+g.getDistanceToSuccessor(i) >
g_next.getDistanceFromStart())
if(dn->getDistanceFromStart()+dn->getDistanceToSuccessor(i) >
dn_next->getDistanceFromStart())
{
updateDistancesForAllSuccessors(g.getSuccessor(i), delta,
updateDistancesForAllSuccessors(dn->getSuccessor(i), delta,
recursive_count);
}
}
@@ -513,12 +536,12 @@ void QuadGraph::updateDistancesForAllSuccessors(unsigned int indx, float delta,
* its data constantly, i.e. if it takes a different turn, it will be using
* the new data).
*/
void QuadGraph::computeDirectionData()
void DriveGraph::computeDirectionData()
{
for(unsigned int i=0; i<m_all_nodes.size(); i++)
{
for(unsigned int succ_index=0;
succ_index<m_all_nodes[i]->getNumberOfSuccessors();
succ_index<getNode(i)->getNumberOfSuccessors();
succ_index++)
{
determineDirection(i, succ_index);
@@ -530,14 +553,14 @@ void QuadGraph::computeDirectionData()
//-----------------------------------------------------------------------------
/** Adjust the given angle to be in [-PI, PI].
*/
float QuadGraph::normalizeAngle(float f)
float DriveGraph::normalizeAngle(float f)
{
if(f>M_PI) f -= 2*M_PI;
else if(f<-M_PI) f += 2*M_PI;
return f;
} // normalizeAngle
//-----------------------------------------------------------------------------
/** Determines the direction of the quad graph when driving to the specified
/** Determines the direction of the drive graph when driving to the specified
* successor. It also determines the last graph node that is still following
* the given direction. The computed data is saved in the corresponding
* graph node.
@@ -553,22 +576,22 @@ float QuadGraph::normalizeAngle(float f)
* If there should be any other branches later, successor
* 0 will always be tetsed.
*/
void QuadGraph::determineDirection(unsigned int current,
unsigned int succ_index)
void DriveGraph::determineDirection(unsigned int current,
unsigned int succ_index)
{
// The maximum angle which is still considered to be straight
const float max_straight_angle=0.1f;
// Compute the angle from n (=current) to n+1 (=next)
float angle_current = getAngleToNext(current, succ_index);
unsigned int next = getNode(current).getSuccessor(succ_index);
unsigned int next = getNode(current)->getSuccessor(succ_index);
float angle_next = getAngleToNext(next, 0);
float rel_angle = normalizeAngle(angle_next-angle_current);
// Small angles are considered to be straight
if(fabsf(rel_angle)<max_straight_angle)
rel_angle = 0;
next = getNode(next).getSuccessor(0); // next is now n+2
next = getNode(next)->getSuccessor(0); // next is now n+2
// If the direction is still the same during a lap the last node
// in the same direction is the previous node;
@@ -591,14 +614,14 @@ void QuadGraph::determineDirection(unsigned int current,
break;
rel_angle = new_rel_angle;
next = getNode(next).getSuccessor(0);
next = getNode(next)->getSuccessor(0);
} // while(1)
GraphNode::DirectionType dir =
rel_angle==0 ? GraphNode::DIR_STRAIGHT
: (rel_angle>0) ? GraphNode::DIR_RIGHT
: GraphNode::DIR_LEFT;
m_all_nodes[current]->setDirectionData(succ_index, dir, next);
DriveNode::DirectionType dir =
rel_angle==0 ? DriveNode::DIR_STRAIGHT
: (rel_angle>0) ? DriveNode::DIR_RIGHT
: DriveNode::DIR_LEFT;
getNode(current)->setDirectionData(succ_index, dir, next);
} // determineDirection
@@ -612,180 +635,60 @@ void QuadGraph::determineDirection(unsigned int current,
* \param xyz The position of the kart.
* \param sector The graph node the position is on.
*/
void QuadGraph::spatialToTrack(Vec3 *dst, const Vec3& xyz,
void DriveGraph::spatialToTrack(Vec3 *dst, const Vec3& xyz,
const int sector) const
{
if(sector == UNKNOWN_SECTOR )
{
Log::warn("Quad Graph", "UNKNOWN_SECTOR in spatialToTrack().");
Log::warn("Drive Graph", "UNKNOWN_SECTOR in spatialToTrack().");
return;
}
getNode(sector).getDistances(xyz, dst);
getNode(sector)->getDistances(xyz, dst);
} // spatialToTrack
//-----------------------------------------------------------------------------
/** findRoadSector returns in which sector on the road the position
* xyz is. If xyz is not on top of the road, it sets UNKNOWN_SECTOR as sector.
*
* \param xyz Position for which the segment should be determined.
* \param sector Contains the previous sector (as a shortcut, since usually
* the sector is the same as the last one), and on return the result
* \param all_sectors If this is not NULL, it is a list of all sectors to
* test. This is used by the AI to make sure that it ends up on the
* selected way in case of a branch, and also to make sure that it
* doesn't skip e.g. a loop (see explanation below for details).
*/
void QuadGraph::findRoadSector(const Vec3& xyz, int *sector,
std::vector<int> *all_sectors) const
float DriveGraph::getDistanceToNext(int n, int j) const
{
// Most likely the kart will still be on the sector it was before,
// so this simple case is tested first.
if(*sector!=UNKNOWN_SECTOR && getQuadOfNode(*sector).pointInQuad(xyz) )
{
return;
} // if still on same quad
// Now we search through all graph nodes, starting with
// the current one
int indx = *sector;
float min_dist = 999999.9f;
// If a current sector is given, and max_lookahead is specify, only test
// the next max_lookahead graph nodes instead of testing the whole graph.
// This is necessary for the AI: if the track contains a loop, e.g.:
// -A--+---B---+----F--------
// E C
// +---D---+
// and the track is supposed to be driven: ABCDEBF, the AI might find
// the node on F, and then keep on going straight ahead instead of
// using the loop at all.
unsigned int max_count = (*sector!=UNKNOWN_SECTOR && all_sectors!=NULL)
? (unsigned int)all_sectors->size()
: (unsigned int)m_all_nodes.size();
*sector = UNKNOWN_SECTOR;
for(unsigned int i=0; i<max_count; i++)
{
if(all_sectors)
indx = (*all_sectors)[i];
else
indx = indx<(int)m_all_nodes.size()-1 ? indx +1 : 0;
const Quad &q = getQuadOfNode(indx);
float dist = xyz.getY() - q.getMinHeight();
// While negative distances are unlikely, we allow some small negative
// numbers in case that the kart is partly in the track.
if(q.pointInQuad(xyz) && dist < min_dist && dist>-1.0f)
{
min_dist = dist;
*sector = indx;
}
} // for i<m_all_nodes.size()
return;
} // findRoadSector
return getNode(n)->getDistanceToSuccessor(j);
} // getDistanceToNext
//-----------------------------------------------------------------------------
/** findOutOfRoadSector finds the sector where XYZ is, but as it name
implies, it is more accurate for the outside of the track than the
inside, and for STK's needs the accuracy on top of the track is
unacceptable; but if this was a 2D function, the accuracy for out
of road sectors would be perfect.
To find the sector we look for the closest line segment from the
right and left drivelines, and the number of that segment will be
the sector.
The SIDE argument is used to speed up the function only; if we know
that XYZ is on the left or right side of the track, we know that
the closest driveline must be the one that matches that condition.
In reality, the side used in STK is the one from the previous frame,
but in order to move from one side to another a point would go
through the middle, that is handled by findRoadSector() which doesn't
has speed ups based on the side.
NOTE: This method of finding the sector outside of the road is *not*
perfect: if two line segments have a similar altitude (but enough to
let a kart get through) and they are very close on a 2D system,
if a kart is on the air it could be closer to the top line segment
even if it is supposed to be on the sector of the lower line segment.
Probably the best solution would be to construct a quad that reaches
until the next higher overlapping line segment, and find the closest
one to XYZ.
*/
int QuadGraph::findOutOfRoadSector(const Vec3& xyz,
const int curr_sector,
std::vector<int> *all_sectors) const
float DriveGraph::getAngleToNext(int n, int j) const
{
int count = (all_sectors!=NULL) ? (int) all_sectors->size() : getNumNodes();
int current_sector = 0;
if(curr_sector != UNKNOWN_SECTOR && !all_sectors)
return getNode(n)->getAngleToSuccessor(j);
} // getAngleToNext
//-----------------------------------------------------------------------------
int DriveGraph::getNumberOfSuccessors(int n) const
{
return getNode(n)->getNumberOfSuccessors();
} // getNumberOfSuccessors
//-----------------------------------------------------------------------------
float DriveGraph::getDistanceFromStart(int j) const
{
return getNode(j)->getDistanceFromStart();
} // getDistanceFromStart
// -----------------------------------------------------------------------------
void DriveGraph::differentNodeColor(int n, video::SColor* c) const
{
if (UserConfigParams::m_track_debug)
{
// We have to test all nodes here: reason is that on track with
// shortcuts the n quads of the main drivelines is followed by
// the quads of the shortcuts. So after quad n-1 (the last one
// before the lap counting line) quad n will not be 0 (the first
// quad after the lap counting line), but one of the quads on a
// shortcut. If we only tested a limited number of quads to
// improve the performance the crossing of a lap might not be
// detected (because quad 0 is not tested, only quads on the
// shortcuts are tested). If this should become a performance
// bottleneck, we need to set up a graph of 'next' quads for each
// quad (similar to what the AI does), and only test the quads
// in this graph.
const int LIMIT = getNumNodes();
count = LIMIT;
// Start 10 quads before the current quad, so the quads closest
// to the current position are tested first.
current_sector = curr_sector -10;
if(current_sector<0) current_sector += getNumNodes();
if (getNode(n)->is3DQuad())
*c = video::SColor(255, 0, 255, 0);
else
*c = video::SColor(255, 255, 255, 0);
}
int min_sector = UNKNOWN_SECTOR;
float min_dist_2 = 999999.0f*999999.0f;
} // differentNodeColor
// If a kart is falling and in between (or too far below)
// a driveline point it might not fulfill
// the height condition. So we run the test twice: first with height
// condition, then again without the height condition - just to make sure
// it always comes back with some kind of quad.
for(int phase=0; phase<2; phase++)
{
for(int j=0; j<count; j++)
{
int next_sector;
if(all_sectors)
next_sector = (*all_sectors)[j];
else
next_sector = current_sector+1 == (int)getNumNodes()
? 0
: current_sector+1;
// A first simple test uses the 2d distance to the center of the quad.
float dist_2 = m_all_nodes[next_sector]->getDistance2FromPoint(xyz);
if(dist_2<min_dist_2)
{
const Quad &q = getQuadOfNode(next_sector);
float dist = xyz.getY() - q.getMinHeight();
// While negative distances are unlikely, we allow some small
// negative numbers in case that the kart is partly in the
// track. Only do the height test in phase==0, in phase==1
// accept any point, independent of height.
if(phase==1 || (dist < 5.0f && dist>-1.0f) )
{
min_dist_2 = dist_2;
min_sector = next_sector;
}
}
current_sector = next_sector;
} // for j
// Leave in phase 0 if any sector was found.
if(min_sector!=UNKNOWN_SECTOR)
return min_sector;
} // phase
if(min_sector==UNKNOWN_SECTOR )
{
Log::info("Quad Grap", "unknown sector found.");
}
return min_sector;
} // findOutOfRoadSector
// -----------------------------------------------------------------------------
DriveNode* DriveGraph::getNode(unsigned int j) const
{
assert(j < m_all_nodes.size());
DriveNode* n = dynamic_cast<DriveNode*>(m_all_nodes[j]);
assert(n != NULL);
return n;
} // getNode

125
src/tracks/drive_graph.hpp Normal file
View File

@@ -0,0 +1,125 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2009-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, B
#ifndef HEADER_DRIVE_GRAPH_HPP
#define HEADER_DRIVE_GRAPH_HPP
#include <vector>
#include <string>
#include "tracks/graph.hpp"
#include "utils/aligned_array.hpp"
#include "utils/cpp2011.hpp"
#include "LinearMath/btTransform.h"
class DriveNode;
class XMLNode;
/**
* \brief A graph made from driveline
* \ingroup tracks
*/
class DriveGraph : public Graph
{
private:
/** The length of the first loop. */
float m_lap_length;
/** Stores the filename - just used for error messages. */
std::string m_quad_filename;
/** Wether the graph should be reverted or not */
bool m_reverse;
// ------------------------------------------------------------------------
void setDefaultSuccessors();
// ------------------------------------------------------------------------
void computeChecklineRequirements(DriveNode* node, int latest_checkline);
// ------------------------------------------------------------------------
void computeDirectionData();
// ------------------------------------------------------------------------
void determineDirection(unsigned int current, unsigned int succ_index);
// ------------------------------------------------------------------------
float normalizeAngle(float f);
// ------------------------------------------------------------------------
void addSuccessor(unsigned int from, unsigned int to);
// ------------------------------------------------------------------------
void load(const std::string &quad_file_name, const std::string &filename);
// ------------------------------------------------------------------------
void getPoint(const XMLNode *xml, const std::string &attribute_name,
Vec3 *result) const;
// ------------------------------------------------------------------------
void computeDistanceFromStart(unsigned int start_node, float distance);
// ------------------------------------------------------------------------
unsigned int getStartNode() const;
// ------------------------------------------------------------------------
virtual bool hasLapLine() const OVERRIDE { return true; }
// ------------------------------------------------------------------------
virtual void differentNodeColor(int n, video::SColor* c) const OVERRIDE;
public:
static DriveGraph* get() { return dynamic_cast<DriveGraph*>(m_graph); }
// ------------------------------------------------------------------------
DriveGraph(const std::string &quad_file_name,
const std::string &graph_file_name, const bool reverse);
// ------------------------------------------------------------------------
virtual ~DriveGraph() {}
// ------------------------------------------------------------------------
void getSuccessors(int node_number, std::vector<unsigned int>& succ,
bool for_ai=false) const;
// ------------------------------------------------------------------------
void spatialToTrack(Vec3 *dst, const Vec3& xyz, const int sector) const;
// ------------------------------------------------------------------------
void setDefaultStartPositions(AlignedArray<btTransform> *start_transforms,
unsigned int karts_per_row,
float forwards_distance = 1.5f,
float sidewards_distance = 1.5f,
float upwards_distance=0.0f) const;
// ------------------------------------------------------------------------
void updateDistancesForAllSuccessors(unsigned int indx, float delta,
unsigned int count);
// ------------------------------------------------------------------------
void setupPaths();
// ------------------------------------------------------------------------
void computeChecklineRequirements();
// ------------------------------------------------------------------------
/** Return the distance to the j-th successor of node n. */
float getDistanceToNext(int n, int j) const;
// ------------------------------------------------------------------------
/** Returns the angle of the line between node n and its j-th.
* successor. */
float getAngleToNext(int n, int j) const;
// ------------------------------------------------------------------------
/** Returns the number of successors of a node n. */
int getNumberOfSuccessors(int n) const;
// ------------------------------------------------------------------------
/** Returns the quad that belongs to a graph node. */
DriveNode* getNode(unsigned int j) const;
// ------------------------------------------------------------------------
/** Returns the distance from the start to the beginning of a quad. */
float getDistanceFromStart(int j) const;
// ------------------------------------------------------------------------
/** Returns the length of the main driveline. */
float getLapLength() const { return m_lap_length; }
// ------------------------------------------------------------------------
bool isReverse() const { return m_reverse; }
}; // DriveGraph
#endif

186
src/tracks/drive_node.cpp Normal file
View File

@@ -0,0 +1,186 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2009-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, B
#include "tracks/drive_node.hpp"
#include "io/file_manager.hpp"
#include "io/xml_node.hpp"
#include "matrix4.h"
#include "tracks/drive_graph.hpp"
#include "utils/log.hpp"
// ----------------------------------------------------------------------------
DriveNode::DriveNode(const Vec3 &p0, const Vec3 &p1, const Vec3 &p2,
const Vec3 &p3, const Vec3 &normal,
unsigned int node_index, bool invisible,
bool ai_ignore)
:Quad(p0, p1, p2, p3, normal, node_index, invisible)
{
m_ai_ignore = ai_ignore;
m_distance_from_start = -1.0f;
// The following values should depend on the actual orientation
// of the quad. ATM we always assume that indices 0,1 are the lower end,
// and 2,3 are the upper end (or the reverse if reverse mode is selected).
// The width is the average width at the beginning and at the end.
m_right_unit_vector = ( m_p[0]-m_p[1]
+m_p[3]-m_p[2]) * 0.5f;
m_right_unit_vector.normalize();
m_width = ( (m_p[1]-m_p[0]).length()
+ (m_p[3]-m_p[2]).length() ) * 0.5f;
if(DriveGraph::get()->isReverse())
{
m_lower_center = (m_p[2]+m_p[3]) * 0.5f;
m_upper_center = (m_p[0]+m_p[1]) * 0.5f;
m_right_unit_vector *= -1.0f;
}
else
{
m_lower_center = (m_p[0]+m_p[1]) * 0.5f;
m_upper_center = (m_p[2]+m_p[3]) * 0.5f;
}
} // DriveNode
// ----------------------------------------------------------------------------
/** Adds a successor to a node. This function will also pre-compute certain
* values (like distance from this node to the successor, angle (in world)
* between this node and the successor.
* \param to The index of the drive node of the successor.
*/
void DriveNode::addSuccessor(unsigned int to)
{
m_successor_nodes.push_back(to);
// to is the drive node
DriveNode* dn_to = DriveGraph::get()->getNode(to);
// Note that the first predecessor is (because of the way the drive graph
// is exported) the most 'natural' one, i.e. the one on the main
// driveline.
dn_to->m_predecessor_nodes.push_back(m_index);
Vec3 d = m_lower_center - dn_to->m_lower_center;
m_distance_to_next.push_back(d.length());
Vec3 diff = dn_to->getCenter() - getCenter();
core::CMatrix4<float> m;
m.buildRotateFromTo(getNormal().toIrrVector(),
Vec3(0, 1, 0).toIrrVector());
core::vector3df diff_rotated;
m.rotateVect(diff_rotated, diff.toIrrVector());
m_angle_to_next.push_back(atan2(diff_rotated.X, diff_rotated.Z));
} // addSuccessor
// ----------------------------------------------------------------------------
/** If this node has more than one successor, it will set up a vector that
* contains the direction to use when a certain drive node X should be
* reached.
*/
void DriveNode::setupPathsToNode()
{
if(m_successor_nodes.size()<2) return;
const unsigned int num_nodes = DriveGraph::get()->getNumNodes();
m_path_to_node.resize(num_nodes);
// Initialise each drive node with -1, indicating that
// it hasn't been reached yet.
for(unsigned int i=0; i<num_nodes; i++)
m_path_to_node[i] = -1;
// Indicate that this node can be reached from this node by following
// successor 0 - just a dummy value that might only be used during the
// recursion below.
m_path_to_node[m_index] = 0;
// A simple depth first search is used to determine which successor to
// use to reach a certain drive node. Using Dijkstra's algorithm would
// give the shortest way to reach a certain node, but the shortest way
// might involve some shortcuts which are hidden, and should therefore
// not be used.
for(unsigned int i=0; i<getNumberOfSuccessors(); i++)
{
DriveNode* dn = DriveGraph::get()->getNode(getSuccessor(i));
dn->markAllSuccessorsToUse(i, &m_path_to_node);
}
#ifdef DEBUG
for(unsigned int i = 0; i < m_path_to_node.size(); ++i)
{
if(m_path_to_node[i] == -1)
Log::warn("DriveNode", "No path to node %d found on drive node %d.",
i, m_index);
}
#endif
} // setupPathsToNode
// ----------------------------------------------------------------------------
/** This function marks that the successor n should be used to reach this
* node. It then recursively (depth first) does the same for all its
* successors. Depth-first
* \param n The successor which should be used in m_path_node to reach
* this node.
* \param path_to_node The path-to-node data structure of the node for
* which the paths are currently determined.
*/
void DriveNode::markAllSuccessorsToUse(unsigned int n,
PathToNodeVector *path_to_node)
{
// End recursion if the path to this node has already been found.
if( (*path_to_node)[m_index] >-1) return;
(*path_to_node)[m_index] = n;
for(unsigned int i=0; i<getNumberOfSuccessors(); i++)
{
DriveNode* dn = DriveGraph::get()->getNode(getSuccessor(i));
dn->markAllSuccessorsToUse(n, path_to_node);
}
} // markAllSuccesorsToUse
// ----------------------------------------------------------------------------
void DriveNode::setDirectionData(unsigned int successor, DirectionType dir,
unsigned int last_node_index)
{
if(m_direction.size()<successor+1)
{
m_direction.resize(successor+1);
m_last_index_same_direction.resize(successor+1);
}
m_direction[successor] = dir;
m_last_index_same_direction[successor] = last_node_index;
} // setDirectionData
// ----------------------------------------------------------------------------
void DriveNode::setChecklineRequirements(int latest_checkline)
{
m_checkline_requirements.push_back(latest_checkline);
} // setChecklineRequirements
// ----------------------------------------------------------------------------
/** Returns true if the index-successor of this node is one that the AI
* is allowed to use.
* \param index Index of the successor.
*/
bool DriveNode::ignoreSuccessorForAI(unsigned int i) const
{
return DriveGraph::get()->getNode(m_successor_nodes[i])->letAIIgnore();
} // ignoreSuccessorForAI

View File

@@ -16,27 +16,19 @@
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, B
#ifndef HEADER_GRAPH_NODE_HPP
#define HEADER_GRAPH_NODE_HPP
#ifndef HEADER_DRIVE_NODE_HPP
#define HEADER_DRIVE_NODE_HPP
#include <vector>
#include <vector2d.h>
#include <dimension2d.h>
#include <line2d.h>
#include "tracks/quad.hpp"
#include "tracks/quad_set.hpp"
#include "utils/vec3.hpp"
class QuadGraph;
/**
* \brief This class stores a node of the graph, i.e. a list of successor
* edges.
* \brief This class stores a node of the drive graph, i.e. a list of
* successor edges, it can either be 2d or 3d.
* \ingroup tracks
*/
class GraphNode
class DriveNode : public Quad
{
public:
/** To indiciate in which direction the track is going:
@@ -44,17 +36,21 @@ public:
* AI only. */
enum DirectionType {DIR_STRAIGHT, DIR_LEFT, DIR_RIGHT,
DIR_UNDEFINED};
protected:
/** Lower center point of the drive node. */
Vec3 m_lower_center;
/** Upper center point of the drive node. */
Vec3 m_upper_center;
/** Distance from the start to the beginning of the drive node. */
float m_distance_from_start;
private:
/** Index of this node in the set of quads. Several graph nodes can use
* the same quad, meaning it is possible to use a quad more than once,
* e.g. a figure 8 like track. */
unsigned int m_quad_index;
/** Set to true if this drive node should not be used by the AI. */
bool m_ai_ignore;
/** Index of this graph node. */
unsigned int m_node_index;
/** The list of successor graph nodes. */
/** The list of successor drive nodes. */
std::vector<int> m_successor_nodes;
/** The list of predecessors of a node. */
@@ -66,74 +62,58 @@ private:
/** The angle of the line from this node to each neighbour. */
std::vector<float> m_angle_to_next;
/** Distance from the start to the beginning of this quad. */
float m_distance_from_start;
/** Width of the track, which is the average of the width at the
* beginning and at the end. FIXME: for now the width is independent
* of the orientation (e.g. a quad used more than once might once
* be used from top to bottom, one from left to right, so it should
* have a different width then). */
float m_width;
* beginning and at the end. */
float m_width;
/** The center point of the lower two points (e.g. points 0 and 1).
* This saves some computations in getDistances later. Only the
* start point is needed, and only in 2d. */
core::vector2df m_lower_center_2d;
/** A vector from the center of the quad to the right edge. */
Vec3 m_center_to_right;
/** Lower center point of the graph node. */
Vec3 m_lower_center;
typedef std::vector<int> PathToNodeVector;
/** This vector is only used if the drive node has more than one
* successor. In this case m_path_to_node[X] will contain the index
* of the successor to use in order to reach drive node X for this
* drive nodes. */
PathToNodeVector m_path_to_node;
/** Upper center point of the graph node. */
Vec3 m_upper_center;
/** The direction for each of the successors. */
std::vector<DirectionType> m_direction;
/** A vector from the center of the quad to the right edge. */
Vec3 m_center_to_right;
/** Stores for each successor the index of the last drive node that
* has the same direction (i.e. if index 0 curves left, this vector
* will store the index of the last drive node that is still turning
* left. */
std::vector<unsigned int> m_last_index_same_direction;
/** Line between lower and upper center, saves computation in
* getDistanceFromLine() later. The line is 2d only since otherwise
* taller karts would have a larger distance from the center. It also
* saves computation, and it is only needed to determine the distance
* from the center of the drivelines anyway. */
core::line2df m_line;
typedef std::vector<int> PathToNodeVector;
/** This vector is only used if the graph node has more than one
* successor. In this case m_path_to_node[X] will contain the index
* of the successor to use in order to reach graph node X for this
* graph nodes. */
PathToNodeVector m_path_to_node;
/** The direction for each of the successors. */
std::vector<DirectionType> m_direction;
/** Stores for each successor the index of the last graph node that
* has the same direction (i.e. if index 0 curves left, this vector
* will store the index of the last graph node that is still turning
* left. */
std::vector<unsigned int> m_last_index_same_direction;
/** A unit vector pointing from the center to the right side, orthogonal
* to the driving direction. */
Vec3 m_right_unit_vector;
/** A unit vector pointing from the center to the right side, orthogonal
* to the driving direction. */
Vec3 m_right_unit_vector;
/**
* Sets of checklines you should have activated when you are driving on
* this node (there is a possibility of more than one set because of
* alternate ways)
*/
std::vector< int > m_checkline_requirements;
* Sets of checklines you should have activated when you are driving on
* this node (there is a possibility of more than one set because of
* alternate ways)
*/
std::vector< int > m_checkline_requirements;
void markAllSuccessorsToUse(unsigned int n,
// ------------------------------------------------------------------------
void markAllSuccessorsToUse(unsigned int n,
PathToNodeVector *m_path_to_node);
public:
GraphNode(unsigned int quad_index, unsigned int node_index);
DriveNode(const Vec3 &p0, const Vec3 &p1, const Vec3 &p2,
const Vec3 &p3, const Vec3 &normal,
unsigned int node_index, bool invisible,
bool ai_ignore);
// ------------------------------------------------------------------------
virtual ~DriveNode() {}
// ------------------------------------------------------------------------
void addSuccessor (unsigned int to);
void getDistances(const Vec3 &xyz, Vec3 *result);
float getDistance2FromPoint(const Vec3 &xyz);
// ------------------------------------------------------------------------
void setupPathsToNode();
// ------------------------------------------------------------------------
void setChecklineRequirements(int latest_checkline);
// ------------------------------------------------------------------------
void setDirectionData(unsigned int successor, DirectionType dir,
unsigned int last_node_index);
// ------------------------------------------------------------------------
@@ -143,7 +123,7 @@ public:
// ------------------------------------------------------------------------
/** Returns the i-th successor node. */
unsigned int getSuccessor(unsigned int i) const
{ return m_successor_nodes[i]; }
{ return m_successor_nodes[i]; }
// ------------------------------------------------------------------------
/** Returns the number of predecessors. */
unsigned int getNumberOfPredecessors() const
@@ -152,65 +132,41 @@ public:
/** Returns a predecessor for this node. Note that the first predecessor
* is the most 'natural' one, i.e. the one on the main driveline.
*/
int getPredecessor(unsigned int i) const {return m_predecessor_nodes[i]; }
// ------------------------------------------------------------------------
/** Returns the quad_index in the quad_set of this node. */
int getQuadIndex() const { return m_quad_index; }
// ------------------------------------------------------------------------
/** Returns the quad of this graph node. */
const Quad& getQuad() const {return QuadSet::get()->getQuad(m_quad_index);}
// ------------------------------------------------------------------------
/** Returns the i-th. point of a quad. ATM this just returns the vertices
* from the quads, but if necessary this method will also consider
* rotated quads. So index 0 will always be lower left point, then
* counterclockwise. */
const Vec3& operator[](int i) const
{return QuadSet::get()->getQuad(m_quad_index)[i];}
int getPredecessor(unsigned int i) const { return m_predecessor_nodes[i]; }
// ------------------------------------------------------------------------
/** Returns the distance to the j-th. successor. */
float getDistanceToSuccessor(unsigned int j) const
{ return m_distance_to_next[j]; }
{ return m_distance_to_next[j]; }
// ------------------------------------------------------------------------
/** Returns the angle from this node to the j-th. successor. */
float getAngleToSuccessor(unsigned int j) const
{ return m_angle_to_next[j]; }
{ return m_angle_to_next[j]; }
// ------------------------------------------------------------------------
/** Returns the distance from start. */
float getDistanceFromStart() const
{ return m_distance_from_start; }
{ return m_distance_from_start; }
// ------------------------------------------------------------------------
/** Sets the distance from start for this node. */
void setDistanceFromStart(float d) {m_distance_from_start = d; }
void setDistanceFromStart(float d) { m_distance_from_start = d; }
// ------------------------------------------------------------------------
/** Returns the width of the part for this quad. */
float getPathWidth() const { return m_width; }
float getPathWidth() const { return m_width; }
// ------------------------------------------------------------------------
/** Returns the center point of the lower edge of this graph node. */
const Vec3& getLowerCenter() const {return m_lower_center;}
/** Returns the center point of the lower edge of this drive node. */
const Vec3& getLowerCenter() const { return m_lower_center; }
// ------------------------------------------------------------------------
/** Returns the center point of the upper edge of this graph node. */
const Vec3& getUpperCenter() const {return m_upper_center;}
// ------------------------------------------------------------------------
/** Returns the center point of this graph node. */
const Vec3 getCenter() const
{return (m_upper_center + m_lower_center) / 2.0f;}
/** Returns the center point of the upper edge of this drive node. */
const Vec3& getUpperCenter() const { return m_upper_center; }
// ------------------------------------------------------------------------
/** Returns the length of the quad of this node. */
float getNodeLength() const
{return (m_lower_center-m_upper_center).length();}
{ return (m_lower_center-m_upper_center).length(); }
// ------------------------------------------------------------------------
/** Returns true if the index-successor of this node is one that the AI
* is allowed to use.
* \param index Index of the successor. */
bool ignoreSuccessorForAI(unsigned int i) const
{
return QuadSet::get()->getQuad(m_successor_nodes[i]).letAIIgnore();
};
bool ignoreSuccessorForAI(unsigned int i) const;
// ------------------------------------------------------------------------
/** Returns which successor node to use in order to be able to reach the
* given node n.
* \param n Index of the graph node to reach.
* \param n Index of the drive node to reach.
*/
int getSuccessorToReach(unsigned int n)
{
@@ -219,7 +175,7 @@ public:
return m_path_to_node.size()>0 ? m_path_to_node[n] : 0;
} // getSuccesorToReach
// ------------------------------------------------------------------------
/** Returns the checkline requirements of this graph node. */
/** Returns the checkline requirements of this drive node. */
const std::vector<int>& getChecklineRequirements() const
{ return m_checkline_requirements; }
// ------------------------------------------------------------------------
@@ -231,7 +187,13 @@ public:
}
// ------------------------------------------------------------------------
/** Returns a unit vector pointing to the right side of the quad. */
const Vec3 &getRightUnitVector() const { return m_right_unit_vector; }
}; // GraphNode
const Vec3 &getRightUnitVector() const { return m_right_unit_vector; }
// ------------------------------------------------------------------------
/** True if this node should be ignored by the AI. */
bool letAIIgnore() const { return m_ai_ignore; }
// ------------------------------------------------------------------------
virtual void getDistances(const Vec3 &xyz, Vec3 *result) const = 0;
}; // DriveNode
#endif

View File

@@ -0,0 +1,71 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2016 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.
#include "tracks/drive_node_2d.hpp"
// ----------------------------------------------------------------------------
DriveNode2D::DriveNode2D(const Vec3 &p0, const Vec3 &p1, const Vec3 &p2,
const Vec3 &p3, const Vec3 &normal,
unsigned int node_index, bool invisible,
bool ai_ignore)
: DriveNode(p0, p1, p2, p3, normal, node_index, invisible,
ai_ignore)
{
m_line = core::line2df(m_upper_center.getX(), m_upper_center.getZ(),
m_lower_center.getX(), m_lower_center.getZ());
// Only this 2d point is needed later
m_lower_center_2d = core::vector2df(m_lower_center.getX(),
m_lower_center.getZ());
} // DriveNode2D
// ----------------------------------------------------------------------------
/** Returns the distance a point has from this node in forward and sidewards
* direction, i.e. how far forwards the point is from the beginning of the
* node, and how far to the side from the line connecting the center points
* is it. All these computations are done in 2D only.
* \param xyz The coordinates of the point.
* \param result The X coordinate contains the sidewards distance, the
* Z coordinate the forward distance.
*/
void DriveNode2D::getDistances(const Vec3 &xyz, Vec3 *result) const
{
core::vector2df xyz2d(xyz.getX(), xyz.getZ());
core::vector2df closest = m_line.getClosestPoint(xyz2d);
if (m_line.getPointOrientation(xyz2d) > 0)
result->setX( (closest-xyz2d).getLength()); // to the right
else
result->setX(-(closest-xyz2d).getLength()); // to the left
result->setZ(m_distance_from_start +
(closest-m_lower_center_2d).getLength());
} // getDistances
// ----------------------------------------------------------------------------
/** Returns the square of the distance between the given point and any point
* on the 'centre' line, i.e. the finite line from the middle point of the
* lower end of the node to the middle point of the upper end of the node
* which belongs to this graph node. The value is computed in 2d only!
* \param xyz The point for which the distance to the line is computed.
*/
float DriveNode2D::getDistance2FromPoint(const Vec3 &xyz) const
{
core::vector2df xyz2d(xyz.getX(), xyz.getZ());
core::vector2df closest = m_line.getClosestPoint(xyz2d);
return (closest-xyz2d).getLengthSQ();
} // getDistance2FromPoint

View File

@@ -0,0 +1,55 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2016 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.
#ifndef HEADER_DRIVE_NODE_2D_HPP
#define HEADER_DRIVE_NODE_2D_HPP
#include "tracks/drive_node.hpp"
#include "utils/cpp2011.hpp"
#include <line2d.h>
/**
* \ingroup tracks
*/
class DriveNode2D : public DriveNode
{
private:
/** The center point of the lower two points (e.g. points 0 and 1).
* This saves some computations in getDistances later. Only the
* start point is needed, and only in 2d. */
core::vector2df m_lower_center_2d;
/** Line between lower and upper center, saves computation in
* getDistance() later. The line is 2d only since otherwise taller karts
* would have a larger distance from the center. It also saves
* computation, and it is only needed to determine the distance from the
* center of the drivelines anyway. */
core::line2df m_line;
public:
DriveNode2D(const Vec3 &p0, const Vec3 &p1, const Vec3 &p2, const Vec3 &p3,
const Vec3 &normal, unsigned int node_index, bool invisible,
bool ai_ignore);
// ------------------------------------------------------------------------
virtual void getDistances(const Vec3 &xyz, Vec3 *result) const OVERRIDE;
// ------------------------------------------------------------------------
virtual float getDistance2FromPoint(const Vec3 &xyz) const OVERRIDE;
};
#endif

View File

@@ -0,0 +1,67 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2016 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.
#include "tracks/drive_node_3d.hpp"
// ----------------------------------------------------------------------------
DriveNode3D::DriveNode3D(const Vec3 &p0, const Vec3 &p1, const Vec3 &p2,
const Vec3 &p3, const Vec3 &normal,
unsigned int node_index, bool invisible,
bool ai_ignore)
: DriveNode(p0, p1, p2, p3, normal, node_index, invisible,
ai_ignore), BoundingBox3D(p0, p1, p2, p3, normal)
{
m_line = core::line3df(m_lower_center.toIrrVector(),
m_upper_center.toIrrVector());
} // DriveNode3D
// ----------------------------------------------------------------------------
/** Returns the distance a point has from this node in forward and sidewards
* direction, i.e. how far forwards the point is from the beginning of the
* node, and how far to the side from the line connecting the center points
* is it.
* \param xyz The coordinates of the point.
* \param result The X coordinate contains the sidewards distance, the
* Z coordinate the forward distance.
*/
void DriveNode3D::getDistances(const Vec3 &xyz, Vec3 *result) const
{
core::vector3df xyz_irr = xyz.toIrrVector();
core::vector3df closest = m_line.getClosestPoint(xyz.toIrrVector());
core::vector3df normal = getNormal().toIrrVector();
if (xyz.sideofPlane(closest, closest + normal, m_line.end) < 0)
result->setX( (closest-xyz_irr).getLength()); // to the right
else
result->setX(-(closest-xyz_irr).getLength()); // to the left
result->setZ(m_distance_from_start +
(closest-m_lower_center.toIrrVector()).getLength());
} // getDistances
// ----------------------------------------------------------------------------
/** Returns the square of the distance between the given point and any point
* on the 'centre' line, i.e. the finite line from the middle point of the
* lower end of the node to the middle point of the upper end of the node
* which belongs to this node.
* \param xyz The point for which the distance to the line is computed.
*/
float DriveNode3D::getDistance2FromPoint(const Vec3 &xyz) const
{
core::vector3df closest = m_line.getClosestPoint(xyz.toIrrVector());
return (closest-xyz.toIrrVector()).getLengthSQ();
} // getDistance2FromPoint

View File

@@ -0,0 +1,56 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2016 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.
#ifndef HEADER_DRIVE_NODE_3D_HPP
#define HEADER_DRIVE_NODE_3D_HPP
#include "tracks/bounding_box_3d.hpp"
#include "tracks/drive_node.hpp"
#include "utils/cpp2011.hpp"
/**
* \ingroup tracks
*/
class DriveNode3D : public DriveNode,
public BoundingBox3D
{
private:
/** Line between lower and upper center, saves computation in
* getDistance() later.
*/
core::line3df m_line;
public:
DriveNode3D(const Vec3 &p0, const Vec3 &p1, const Vec3 &p2, const Vec3 &p3,
const Vec3 &normal, unsigned int node_index, bool invisible,
bool ai_ignore);
// ------------------------------------------------------------------------
virtual bool pointInside(const Vec3& p,
bool ignore_vertical = false) const OVERRIDE
{
return BoundingBox3D::pointInside(p);
}
// ------------------------------------------------------------------------
virtual void getDistances(const Vec3 &xyz, Vec3 *result) const OVERRIDE;
// ------------------------------------------------------------------------
virtual float getDistance2FromPoint(const Vec3 &xyz) const OVERRIDE;
// ------------------------------------------------------------------------
virtual bool is3DQuad() const OVERRIDE { return true; }
};
#endif

620
src/tracks/graph.cpp Normal file
View File

@@ -0,0 +1,620 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2016 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.
#include "tracks/graph.hpp"
#include <ICameraSceneNode.h>
#include <IMesh.h>
#include <IMeshSceneNode.h>
#include <ISceneManager.h>
#include "config/user_config.hpp"
#include "graphics/irr_driver.hpp"
#include "graphics/glwrap.hpp"
#include "graphics/shaders.hpp"
#include "graphics/rtts.hpp"
#include "modes/profile_world.hpp"
#include "tracks/arena_node_3d.hpp"
#include "tracks/drive_node_2d.hpp"
#include "tracks/drive_node_3d.hpp"
#include "utils/log.hpp"
const int Graph::UNKNOWN_SECTOR = -1;
Graph *Graph::m_graph = NULL;
// -----------------------------------------------------------------------------
Graph::Graph()
{
m_scaling = 0;
m_node = NULL;
m_mesh = NULL;
m_mesh_buffer = NULL;
m_new_rtt = NULL;
m_bb_min = Vec3( 99999, 99999, 99999);
m_bb_max = Vec3(-99999, -99999, -99999);
} // Graph
// -----------------------------------------------------------------------------
Graph::~Graph()
{
if (m_new_rtt != NULL)
{
delete m_new_rtt;
m_new_rtt = NULL;
}
if (UserConfigParams::m_track_debug)
cleanupDebugMesh();
for (unsigned int i = 0; i < m_all_nodes.size(); i++)
{
delete m_all_nodes[i];
}
m_all_nodes.clear();
} // ~Graph
// -----------------------------------------------------------------------------
/** Creates the debug mesh to display the graph on top of the track
* model. */
void Graph::createDebugMesh()
{
if (getNumNodes() <= 0) return; // no debug output if not graph
createMesh(/*show_invisible*/true,
/*enable_transparency*/true);
video::S3DVertex *v = (video::S3DVertex*)m_mesh_buffer->getVertices();
for (unsigned int i = 0; i < m_mesh_buffer->getVertexCount(); i++)
{
// Swap the alpha and back
v[i].Color.setAlpha((i%2) ? 64 : 255);
}
m_node = irr_driver->addMesh(m_mesh, "track-debug-mesh");
#ifdef DEBUG
m_node->setName("track-debug-mesh");
#endif
} // createDebugMesh
// -----------------------------------------------------------------------------
/** Cleans up the debug mesh */
void Graph::cleanupDebugMesh()
{
if (m_node != NULL)
irr_driver->removeNode(m_node);
m_node = NULL;
// No need to call irr_driber->removeMeshFromCache, since the mesh
// was manually made and so never added to the mesh cache.
m_mesh->drop();
m_mesh = NULL;
} // cleanupDebugMesh
// -----------------------------------------------------------------------------
/** Creates the actual mesh that is used by createDebugMesh() or makeMiniMap()
*/
void Graph::createMesh(bool show_invisible, bool enable_transparency,
const video::SColor *track_color)
{
// The debug track will not be lighted or culled.
video::SMaterial m;
m.BackfaceCulling = false;
m.Lighting = false;
if (enable_transparency)
m.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
m.setTexture(0, getUnicolorTexture(video::SColor(255, 255, 255, 255)));
m.setTexture(1, getUnicolorTexture(video::SColor(0, 0, 0, 0)));
m.setTexture(7, getUnicolorTexture(video::SColor(0, 0, 0, 0)));
m_mesh = irr_driver->createQuadMesh(&m);
m_mesh_buffer = m_mesh->getMeshBuffer(0);
assert(m_mesh_buffer->getVertexType()==video::EVT_STANDARD);
unsigned int n = 0;
const unsigned int total_nodes = getNumNodes();
// Count the number of quads to display (some quads might be invisible)
for (unsigned int i = 0; i < total_nodes; i++)
{
if (show_invisible || !m_all_nodes[i]->isInvisible())
n++;
}
// Four vertices for each of the n-1 remaining quads
video::S3DVertex *new_v = new video::S3DVertex[4*n];
// Each quad consists of 2 triangles with 3 elements, so
// we need 2*3 indices for each quad.
irr::u16 *ind = new irr::u16[6*n];
video::SColor c(255, 255, 0, 0);
if (track_color)
c = *track_color;
// Now add all quads
int i = 0;
for (unsigned int count = 0; count < total_nodes; count++)
{
// Ignore invisible quads
if (!show_invisible && m_all_nodes[count]->isInvisible())
continue;
// Swap the colours from red to blue and back
if (!track_color)
{
c.setRed ((i%2) ? 255 : 0);
c.setBlue((i%2) ? 0 : 255);
}
video::SColor this_color = c;
differentNodeColor(count, &this_color);
// Transfer the 4 points of the current quad to the list of vertices
m_all_nodes[count]->getVertices(new_v+4*i, this_color);
// Set up the indices for the triangles
// (note, afaik with opengl we could use quads directly, but the code
// would not be portable to directx anymore).
ind[6*i ] = 4*i+2; // First triangle: vertex 0, 1, 2
ind[6*i+1] = 4*i+1;
ind[6*i+2] = 4*i;
ind[6*i+3] = 4*i+3; // second triangle: vertex 0, 1, 3
ind[6*i+4] = 4*i+2;
ind[6*i+5] = 4*i;
i++;
}
m_mesh_buffer->append(new_v, n*4, ind, n*6);
if (hasLapLine())
{
video::S3DVertex lap_v[4];
irr::u16 lap_ind[6];
video::SColor lap_color(128, 255, 0, 0);
m_all_nodes[0]->getVertices(lap_v, lap_color);
// Now scale the length (distance between vertix 0 and 3
// and between 1 and 2) to be 'length':
// Length of the lap line about 3% of the 'height'
// of the track.
const float length = (m_bb_max.getZ()-m_bb_min.getZ())*0.03f;
core::vector3df dl = lap_v[3].Pos-lap_v[0].Pos;
float ll2 = dl.getLengthSQ();
if (ll2 < 0.001)
lap_v[3].Pos = lap_v[0].Pos+core::vector3df(0, 0, 1);
else
lap_v[3].Pos = lap_v[0].Pos+dl*length/sqrt(ll2);
core::vector3df dr = lap_v[2].Pos-lap_v[1].Pos;
float lr2 = dr.getLengthSQ();
if (lr2 < 0.001)
lap_v[2].Pos = lap_v[1].Pos+core::vector3df(0, 0, 1);
else
lap_v[2].Pos = lap_v[1].Pos+dr*length/sqrt(lr2);
lap_ind[0] = 2;
lap_ind[1] = 1;
lap_ind[2] = 0;
lap_ind[3] = 3;
lap_ind[4] = 2;
lap_ind[5] = 0;
// Set it a bit higher to avoid issued with z fighting,
// i.e. part of the lap line might not be visible.
for (unsigned int i = 0; i < 4; i++)
lap_v[i].Pos.Y += 0.1f;
#ifndef USE_TEXTURED_LINE
m_mesh_buffer->append(lap_v, 4, lap_ind, 6);
#else
lap_v[0].TCoords = core::vector2df(0,0);
lap_v[1].TCoords = core::vector2df(3,0);
lap_v[2].TCoords = core::vector2df(3,1);
lap_v[3].TCoords = core::vector2df(0,1);
m_mesh_buffer->append(lap_v, 4, lap_ind, 6);
video::SMaterial &m = m_mesh_buffer->getMaterial();
video::ITexture *t = irr_driver->getTexture("chess.png");
m.setTexture(0, t);
#endif
}
// Instead of setting the bounding boxes, we could just disable culling,
// since the debug track should always be drawn.
//m_node->setAutomaticCulling(scene::EAC_OFF);
m_mesh_buffer->recalculateBoundingBox();
m_mesh->setBoundingBox(m_mesh_buffer->getBoundingBox());
m_mesh_buffer->getMaterial().setTexture(0, irr_driver
->getTexture("unlit.png"));
delete[] ind;
delete[] new_v;
} // createMesh
// -----------------------------------------------------------------------------
/** Takes a snapshot of the graph so they can be used as minimap.
*/
void Graph::makeMiniMap(const core::dimension2du &dimension,
const std::string &name,
const video::SColor &fill_color,
video::ITexture** oldRttMinimap,
FrameBuffer** newRttMinimap)
{
// Skip minimap when profiling
if (ProfileWorld::isNoGraphics()) return;
const video::SColor oldClearColor = World::getWorld()->getClearColor();
World::getWorld()
->setClearbackBufferColor(video::SColor(0, 255, 255, 255));
World::getWorld()->forceFogDisabled(true);
*oldRttMinimap = NULL;
*newRttMinimap = NULL;
RTT* newRttProvider = NULL;
IrrDriver::RTTProvider* oldRttProvider = NULL;
if (CVS->isGLSL())
{
m_new_rtt = newRttProvider =
new RTT(dimension.Width, dimension.Height);
}
else
{
oldRttProvider = new IrrDriver::RTTProvider(dimension, name, true);
}
irr_driver->getSceneManager()
->setAmbientLight(video::SColor(255, 255, 255, 255));
createMesh(/*show_invisible part of the track*/ false,
/*enable_transparency*/ false,
/*track_color*/ &fill_color);
m_node = irr_driver->addMesh(m_mesh, "mini_map");
#ifdef DEBUG
m_node->setName("minimap-mesh");
#endif
m_node->setAutomaticCulling(0);
m_node->setMaterialFlag(video::EMF_LIGHTING, false);
// Add the camera:
// ---------------
scene::ICameraSceneNode *camera = irr_driver->addCameraSceneNode();
Vec3 center = (m_bb_max+m_bb_min)*0.5f;
float dx = m_bb_max.getX()-m_bb_min.getX();
float dz = m_bb_max.getZ()-m_bb_min.getZ();
// Set the scaling correctly. Also the center point (which is used
// as the camera position) needs to be adjusted: the track must
// be aligned to the left/top of the texture which is used (otherwise
// mapPoint2MiniMap doesn't work), so adjust the camera position
// that the track is properly aligned (view from the side):
// c camera
// / \ .
// / \ <--- camera angle
// / \ .
// { [-] } <--- track flat (viewed from the side)
// If [-] is the shorter side of the track, then the camera will
// actually render the area in { } - which is the length of the
// longer side of the track.
// To align the [-] side to the left, the camera must be moved
// the distance betwwen '{' and '[' to the right. This distance
// is exacly (longer_side - shorter_side) / 2.
// So, adjust the center point by this amount:
if (dz > dx)
{
center.setX(center.getX() + (dz-dx)*0.5f);
m_scaling = dimension.Width / dz;
}
else
{
center.setZ(center.getZ() + (dx-dz)*0.5f);
m_scaling = dimension.Width / dx;
}
float range = (dx>dz) ? dx : dz;
core::matrix4 projection;
projection.buildProjectionMatrixOrthoLH
(range /* width */, range /* height */, -1,
m_bb_max.getY()-m_bb_min.getY()+1);
camera->setProjectionMatrix(projection, true);
irr_driver->suppressSkyBox();
irr_driver->clearLights();
// Adjust Y position by +1 for max, -1 for min - this helps in case that
// the maximum Y coordinate is negative (otherwise the minimap is mirrored)
// and avoids problems for tracks which have a flat (max Y=min Y) minimap.
camera->setPosition(core::vector3df(center.getX(), m_bb_min.getY() + 1.0f,
center.getZ()));
//camera->setPosition(core::vector3df(center.getX() - 5.0f,
// m_bb_min.getY() - 1 - 5.0f, center.getZ() - 15.0f));
camera->setUpVector(core::vector3df(0, 0, 1));
camera->setTarget(core::vector3df(center.getX(), m_bb_min.getY() - 1,
center.getZ()));
//camera->setAspectRatio(1.0f);
camera->updateAbsolutePosition();
video::ITexture* texture = NULL;
FrameBuffer* frame_buffer = NULL;
if (CVS->isGLSL())
{
frame_buffer = newRttProvider->render(camera,
GUIEngine::getLatestDt());
}
else
{
texture = oldRttProvider->renderToTexture();
delete oldRttProvider;
}
cleanupDebugMesh();
irr_driver->removeCameraSceneNode(camera);
if (texture == NULL && frame_buffer == NULL)
{
Log::error("Graph", "[makeMiniMap] WARNING: RTT does not"
"appear to work, mini-map will not be available.");
}
*oldRttMinimap = texture;
*newRttMinimap = frame_buffer;
World::getWorld()->setClearbackBufferColor(oldClearColor);
World::getWorld()->forceFogDisabled(false);
irr_driver->getSceneManager()->clear();
VAOManager::kill();
irr_driver->clearGlowingNodes();
irr_driver->clearLights();
irr_driver->clearForcedBloom();
irr_driver->clearBackgroundNodes();
} // makeMiniMap
// -----------------------------------------------------------------------------
/** Returns the 2d coordinates of a point when drawn on the mini map
* texture.
* \param xyz Coordinates of the point to map.
* \param draw_at The coordinates in pixel on the mini map of the point,
* only the first two coordinates will be used.
*/
void Graph::mapPoint2MiniMap(const Vec3 &xyz,Vec3 *draw_at) const
{
draw_at->setX((xyz.getX()-m_bb_min.getX())*m_scaling);
draw_at->setY((xyz.getZ()-m_bb_min.getZ())*m_scaling);
} // mapPoint
// -----------------------------------------------------------------------------
void Graph::createQuad(const Vec3 &p0, const Vec3 &p1, const Vec3 &p2,
const Vec3 &p3, unsigned int node_index,
bool invisible, bool ai_ignore, bool is_arena)
{
// Find the normal of this quad by computing the normal of two triangles
// and taking their average.
core::triangle3df tri1(p0.toIrrVector(), p1.toIrrVector(),
p2.toIrrVector());
core::triangle3df tri2(p0.toIrrVector(), p2.toIrrVector(),
p3.toIrrVector());
Vec3 normal1 = tri1.getNormal();
Vec3 normal2 = tri2.getNormal();
Vec3 normal = -0.5f * (normal1 + normal2);
normal.normalize();
// Use the angle between the normal and an up vector to choose 3d/2d quad
const float angle = normal.angle(Vec3(0, 1, 0));
Quad* q = NULL;
if (angle > 0.5f)
{
Log::debug("Graph", "3d node created, normal: %f, %f, %f",
normal.x(), normal.y(), normal.z());
if (is_arena)
{
q = new ArenaNode3D(p0, p1, p2, p3, normal, node_index);
}
else
{
q = new DriveNode3D(p0, p1, p2, p3, normal, node_index, invisible,
ai_ignore);
}
}
else
{
Log::debug("Graph", "2d node created, normal: %f, %f, %f",
normal.x(), normal.y(), normal.z());
if (is_arena)
{
q = new ArenaNode(p0, p1, p2, p3, normal, node_index);
}
else
{
q = new DriveNode2D(p0, p1, p2, p3, normal, node_index, invisible,
ai_ignore);
}
}
m_all_nodes.push_back(q);
m_bb_max.max(p0); m_bb_max.max(p1); m_bb_max.max(p2); m_bb_max.max(p3);
m_bb_min.min(p0); m_bb_min.min(p1); m_bb_min.min(p2); m_bb_min.min(p3);
} // createQuad
//-----------------------------------------------------------------------------
/** findRoadSector returns in which sector on the road the position
* xyz is. If xyz is not on top of the road, it sets UNKNOWN_SECTOR as sector.
*
* \param xyz Position for which the segment should be determined.
* \param sector Contains the previous sector (as a shortcut, since usually
* the sector is the same as the last one), and on return the result
* \param all_sectors If this is not NULL, it is a list of all sectors to
* test. This is used by the AI to make sure that it ends up on the
* selected way in case of a branch, and also to make sure that it
* doesn't skip e.g. a loop (see explanation below for details).
*/
void Graph::findRoadSector(const Vec3& xyz, int *sector,
std::vector<int> *all_sectors,
bool ignore_vertical) const
{
// Most likely the kart will still be on the sector it was before,
// so this simple case is tested first.
if (*sector!=UNKNOWN_SECTOR &&
getQuad(*sector)->pointInside(xyz, ignore_vertical))
{
return;
} // if still on same quad
// Now we search through all quads, starting with
// the current one
int indx = *sector;
// If a current sector is given, and max_lookahead is specify, only test
// the next max_lookahead quads instead of testing the whole graph.
// This is necessary for the AI: if the track contains a loop, e.g.:
// -A--+---B---+----F--------
// E C
// +---D---+
// and the track is supposed to be driven: ABCDEBF, the AI might find
// the quad on F, and then keep on going straight ahead instead of
// using the loop at all.
unsigned int max_count = (*sector!=UNKNOWN_SECTOR && all_sectors!=NULL)
? (unsigned int)all_sectors->size()
: (unsigned int)m_all_nodes.size();
*sector = UNKNOWN_SECTOR;
for(unsigned int i=0; i<max_count; i++)
{
if(all_sectors)
indx = (*all_sectors)[i];
else
indx = indx<(int)m_all_nodes.size()-1 ? indx +1 : 0;
const Quad* q = getQuad(indx);
if(q->pointInside(xyz, ignore_vertical))
{
*sector = indx;
return;
}
} // for i<m_all_nodes.size()
return;
} // findRoadSector
//-----------------------------------------------------------------------------
/** findOutOfRoadSector finds the sector where XYZ is, but as it name
implies, it is more accurate for the outside of the track than the
inside, and for STK's needs the accuracy on top of the track is
unacceptable; but if this was a 2D function, the accuracy for out
of road sectors would be perfect.
To find the sector we look for the closest line segment from the
right and left drivelines, and the number of that segment will be
the sector.
The SIDE argument is used to speed up the function only; if we know
that XYZ is on the left or right side of the track, we know that
the closest driveline must be the one that matches that condition.
In reality, the side used in STK is the one from the previous frame,
but in order to move from one side to another a point would go
through the middle, that is handled by findRoadSector() which doesn't
has speed ups based on the side.
NOTE: This method of finding the sector outside of the road is *not*
perfect: if two line segments have a similar altitude (but enough to
let a kart get through) and they are very close on a 2D system,
if a kart is on the air it could be closer to the top line segment
even if it is supposed to be on the sector of the lower line segment.
Probably the best solution would be to construct a quad that reaches
until the next higher overlapping line segment, and find the closest
one to XYZ.
*/
int Graph::findOutOfRoadSector(const Vec3& xyz, const int curr_sector,
std::vector<int> *all_sectors,
bool ignore_vertical) const
{
int count = (all_sectors!=NULL) ? (int)all_sectors->size() : getNumNodes();
int current_sector = 0;
if(curr_sector != UNKNOWN_SECTOR && !all_sectors)
{
// We have to test all quads here: reason is that on track with
// shortcuts the n quads of the main drivelines is followed by
// the quads of the shortcuts. So after quad n-1 (the last one
// before the lap counting line) quad n will not be 0 (the first
// quad after the lap counting line), but one of the quads on a
// shortcut. If we only tested a limited number of quads to
// improve the performance the crossing of a lap might not be
// detected (because quad 0 is not tested, only quads on the
// shortcuts are tested). If this should become a performance
// bottleneck, we need to set up a graph of 'next' quads for each
// quad (similar to what the AI does), and only test the quads
// in this graph.
const int LIMIT = getNumNodes();
count = LIMIT;
// Start 10 quads before the current quad, so the quads closest
// to the current position are tested first.
current_sector = curr_sector -10;
if(current_sector<0) current_sector += getNumNodes();
}
int min_sector = UNKNOWN_SECTOR;
float min_dist_2 = 999999.0f*999999.0f;
// If a kart is falling and in between (or too far below)
// a driveline point it might not fulfill
// the height condition. So we run the test twice: first with height
// condition, then again without the height condition - just to make sure
// it always comes back with some kind of quad.
for(int phase=0; phase<2; phase++)
{
for(int j=0; j<count; j++)
{
int next_sector;
if(all_sectors)
next_sector = (*all_sectors)[j];
else
next_sector = current_sector+1 == (int)getNumNodes()
? 0
: current_sector+1;
// A first simple test uses the 2d distance to the center of the
// quad.
float dist_2 =
m_all_nodes[next_sector]->getDistance2FromPoint(xyz);
if(dist_2<min_dist_2)
{
const Quad* q = getQuad(next_sector);
float dist = xyz.getY() - q->getMinHeight();
// While negative distances are unlikely, we allow some small
// negative numbers in case that the kart is partly in the
// track. Only do the height test in phase==0, in phase==1
// accept any point, independent of height, or this node is 3d
// which already takes height into account
if(phase==1 || (dist < 5.0f && dist>-1.0f) ||
q->is3DQuad() || ignore_vertical)
{
min_dist_2 = dist_2;
min_sector = next_sector;
}
}
current_sector = next_sector;
} // for j
// Leave in phase 0 if any sector was found.
if(min_sector!=UNKNOWN_SECTOR)
return min_sector;
} // phase
if(min_sector==UNKNOWN_SECTOR)
{
Log::info("Graph", "unknown sector found.");
}
return min_sector;
} // findOutOfRoadSector

155
src/tracks/graph.hpp Normal file
View File

@@ -0,0 +1,155 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2016 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.
#ifndef HEADER_GRAPH_HPP
#define HEADER_GRAPH_HPP
#include "utils/no_copy.hpp"
#include "utils/vec3.hpp"
#include <dimension2d.h>
#include <string>
#include <vector>
namespace irr
{
namespace scene { class ISceneNode; class IMesh; class IMeshBuffer; }
namespace video { class ITexture; struct S3DVertex; class SColor; }
}
using namespace irr;
class FrameBuffer;
class Quad;
class RTT;
/**
* \brief This class stores a graph of quads. It uses a 'simplified singleton'
* design pattern: it has a static create function to create exactly instance,
* a destroy function, and a get function (that does not have the side effect
* of the 'normal singleton' design pattern to create an instance). Besides
* saving on the if statement in get(), this is necessary since certain race
* modes might not have a quad graph at all (e.g. arena without navmesh). So
* get() returns NULL in this case, and this is tested where necessary.
* \ingroup tracks
*/
class Graph : public NoCopy
{
protected:
static Graph* m_graph;
std::vector<Quad*> m_all_nodes;
// ------------------------------------------------------------------------
/** Factory method to dynamic create 2d / 3d quad for drive and arena
* graph. */
void createQuad(const Vec3 &p0, const Vec3 &p1, const Vec3 &p2,
const Vec3 &p3, unsigned int node_index,
bool invisible, bool ai_ignore, bool is_arena);
private:
/** The 2d bounding box, used for hashing. */
Vec3 m_bb_min;
Vec3 m_bb_max;
RTT* m_new_rtt;
/** The node of the graph mesh. */
scene::ISceneNode *m_node;
/** The mesh of the graph mesh. */
scene::IMesh *m_mesh;
/** The actual mesh buffer storing the graph. */
scene::IMeshBuffer *m_mesh_buffer;
/** Scaling for mini map. */
float m_scaling;
// ------------------------------------------------------------------------
void createMesh(bool show_invisible=true,
bool enable_transparency=false,
const video::SColor *track_color=NULL);
// ------------------------------------------------------------------------
void cleanupDebugMesh();
// ------------------------------------------------------------------------
virtual bool hasLapLine() const = 0;
// ------------------------------------------------------------------------
virtual void differentNodeColor(int n, video::SColor* c) const = 0;
public:
static const int UNKNOWN_SECTOR;
// ------------------------------------------------------------------------
/** Returns the one instance of this object. It is possible that there
* is no instance created (e.g. arena without navmesh) so we don't assert
* that an instance exist. */
static Graph* get() { return m_graph; }
// ------------------------------------------------------------------------
/** Set the graph (either drive or arena graph for now). */
static void setGraph(Graph* graph)
{
assert(m_graph == NULL);
m_graph = graph;
} // setGraph
// ------------------------------------------------------------------------
/** Cleans up the graph. It is possible that this function is called even
* if no instance exists (e.g. arena without navmesh). So it is not an
* error if there is no instance. */
static void destroy()
{
if (m_graph)
{
delete m_graph;
m_graph = NULL;
}
} // destroy
// ------------------------------------------------------------------------
Graph();
// ------------------------------------------------------------------------
virtual ~Graph();
// ------------------------------------------------------------------------
void createDebugMesh();
// ------------------------------------------------------------------------
void makeMiniMap(const core::dimension2du &where, const std::string &name,
const video::SColor &fill_color,
video::ITexture** oldRttMinimap,
FrameBuffer** newRttMinimap);
// ------------------------------------------------------------------------
void mapPoint2MiniMap(const Vec3 &xyz, Vec3 *out) const;
// ------------------------------------------------------------------------
Quad* getQuad(unsigned int i) const
{
assert(i < m_all_nodes.size());
return m_all_nodes[i];
}
// ------------------------------------------------------------------------
unsigned int getNumNodes() const { return m_all_nodes.size(); }
// ------------------------------------------------------------------------
void findRoadSector(const Vec3& XYZ, int *sector,
std::vector<int> *all_sectors = NULL,
bool ignore_vertical = false) const;
// ------------------------------------------------------------------------
int findOutOfRoadSector(const Vec3& xyz,
const int curr_sector = UNKNOWN_SECTOR,
std::vector<int> *all_sectors = NULL,
bool ignore_vertical = false) const;
}; // Graph
#endif

View File

@@ -1,217 +0,0 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2009-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, B
#include "tracks/quad_graph.hpp"
#include "io/file_manager.hpp"
#include "io/xml_node.hpp"
#include "tracks/quad_graph.hpp"
#include "tracks/quad_set.hpp"
#include "utils/log.hpp"
// ----------------------------------------------------------------------------
/** Constructor. Saves the quad index which belongs to this graph node.
* \param index Index of the quad to use for this node (in QuadSet).
*/
GraphNode::GraphNode(unsigned int quad_index, unsigned int node_index)
{
if (quad_index >= QuadSet::get()->getNumberOfQuads())
Log::fatal("GraphNode", "No driveline found, or empty driveline.");
m_quad_index = quad_index;
m_node_index = node_index;
m_distance_from_start = -1.0f;
const Quad &quad = QuadSet::get()->getQuad(m_quad_index);
// The following values should depend on the actual orientation
// of the quad. ATM we always assume that indices 0,1 are the lower end,
// and 2,3 are the upper end (or the reverse if reverse mode is selected).
// The width is the average width at the beginning and at the end.
m_right_unit_vector = ( quad[0]-quad[1]
+quad[3]-quad[2]) * 0.5f;
m_right_unit_vector.normalize();
m_width = ( (quad[1]-quad[0]).length()
+ (quad[3]-quad[2]).length() ) * 0.5f;
if(QuadGraph::get()->isReverse())
{
m_lower_center = (quad[2]+quad[3]) * 0.5f;
m_upper_center = (quad[0]+quad[1]) * 0.5f;
m_right_unit_vector *= -1.0f;
}
else
{
m_lower_center = (quad[0]+quad[1]) * 0.5f;
m_upper_center = (quad[2]+quad[3]) * 0.5f;
}
m_line = core::line2df(m_upper_center.getX(), m_upper_center.getZ(),
m_lower_center.getX(), m_lower_center.getZ() );
// Only this 2d point is needed later
m_lower_center_2d = core::vector2df(m_lower_center.getX(),
m_lower_center.getZ() );
} // GraphNode
// ----------------------------------------------------------------------------
/** Adds a successor to a node. This function will also pre-compute certain
* values (like distance from this node to the successor, angle (in world)
* between this node and the successor.
* \param to The index of the graph node of the successor.
*/
void GraphNode::addSuccessor(unsigned int to)
{
m_successor_nodes.push_back(to);
// m_quad_index is the quad index
const Quad &this_quad = QuadSet::get()->getQuad(m_quad_index);
// to is the graph node
GraphNode &gn = QuadGraph::get()->getNode(to);
const Quad &next_quad = QuadGraph::get()->getQuadOfNode(to);
// Note that the first predecessor is (because of the way the quad graph
// is exported) the most 'natural' one, i.e. the one on the main
// driveline.
gn.m_predecessor_nodes.push_back(m_node_index);
Vec3 d = m_lower_center - QuadGraph::get()->getNode(to).m_lower_center;
m_distance_to_next.push_back(d.length());
Vec3 diff = next_quad.getCenter() - this_quad.getCenter();
m_angle_to_next.push_back(atan2(diff.getX(), diff.getZ()));
} // addSuccessor
// ----------------------------------------------------------------------------
/** If this node has more than one successor, it will set up a vector that
* contains the direction to use when a certain graph node X should be
* reached.
*/
void GraphNode::setupPathsToNode()
{
if(m_successor_nodes.size()<2) return;
const unsigned int num_nodes = QuadGraph::get()->getNumNodes();
m_path_to_node.resize(num_nodes);
// Initialise each graph node with -1, indicating that
// it hasn't been reached yet.
for(unsigned int i=0; i<num_nodes; i++)
m_path_to_node[i] = -1;
// Indicate that this node can be reached from this node by following
// successor 0 - just a dummy value that might only be used during the
// recursion below.
m_path_to_node[m_node_index] = 0;
// A simple depth first search is used to determine which successor to
// use to reach a certain graph node. Using Dijkstra's algorithm would
// give the shortest way to reach a certain node, but the shortest way
// might involve some shortcuts which are hidden, and should therefore
// not be used.
for(unsigned int i=0; i<getNumberOfSuccessors(); i++)
{
GraphNode &gn = QuadGraph::get()->getNode(getSuccessor(i));
gn.markAllSuccessorsToUse(i, &m_path_to_node);
}
#ifdef DEBUG
for(unsigned int i = 0; i < m_path_to_node.size(); ++i)
{
if(m_path_to_node[i] == -1)
Log::warn("GraphNode", "No path to node %d found on graph node %d.",
i, m_node_index);
}
#endif
} // setupPathsToNode
// ----------------------------------------------------------------------------
/** This function marks that the successor n should be used to reach this
* node. It then recursively (depth first) does the same for all its
* successors. Depth-first
* \param n The successor which should be used in m_path_node to reach
* this node.
* \param path_to_node The path-to-node data structure of the node for
* which the paths are currently determined.
*/
void GraphNode::markAllSuccessorsToUse(unsigned int n,
PathToNodeVector *path_to_node)
{
// End recursion if the path to this node has already been found.
if( (*path_to_node)[m_node_index] >-1) return;
(*path_to_node)[m_node_index] = n;
for(unsigned int i=0; i<getNumberOfSuccessors(); i++)
{
GraphNode &gn = QuadGraph::get()->getNode(getSuccessor(i));
gn.markAllSuccessorsToUse(n, path_to_node);
}
} // markAllSuccesorsToUse
// ----------------------------------------------------------------------------
void GraphNode::setDirectionData(unsigned int successor, DirectionType dir,
unsigned int last_node_index)
{
if(m_direction.size()<successor+1)
{
m_direction.resize(successor+1);
m_last_index_same_direction.resize(successor+1);
}
m_direction[successor] = dir;
m_last_index_same_direction[successor] = last_node_index;
} // setDirectionData
// ----------------------------------------------------------------------------
/** Returns the distance a point has from this quad in forward and sidewards
* direction, i.e. how far forwards the point is from the beginning of the
* quad, and how far to the side from the line connecting the center points
* is it. All these computations are done in 2D only.
* \param xyz The coordinates of the point.
* \param result The X coordinate contains the sidewards distance, the
* Z coordinate the forward distance.
*/
void GraphNode::getDistances(const Vec3 &xyz, Vec3 *result)
{
core::vector2df xyz2d(xyz.getX(), xyz.getZ());
core::vector2df closest = m_line.getClosestPoint(xyz2d);
if(m_line.getPointOrientation(xyz2d)>0)
result->setX( (closest-xyz2d).getLength()); // to the right
else
result->setX(-(closest-xyz2d).getLength()); // to the left
result->setZ( m_distance_from_start +
(closest-m_lower_center_2d).getLength());
} // getDistances
// ----------------------------------------------------------------------------
/** Returns the square of the distance between the given point and any point
* on the 'centre' line, i.e. the finite line from the middle point of the
* lower end of the quad node to the middle point of the upper end of the
* quad which belongs to this graph node. The value is computed in 2d only!
* \param xyz The point for which the distance to the line is computed.
*/
float GraphNode::getDistance2FromPoint(const Vec3 &xyz)
{
core::vector2df xyz2d(xyz.getX(), xyz.getZ());
core::vector2df closest = m_line.getClosestPoint(xyz2d);
return (closest-xyz2d).getLengthSQ();
} // getDistance2FromPoint
// ----------------------------------------------------------------------------
void GraphNode::setChecklineRequirements(int latest_checkline)
{
m_checkline_requirements.push_back(latest_checkline);
}

View File

@@ -1,388 +0,0 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2015 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.
#include "tracks/graph_structure.hpp"
#include <ICameraSceneNode.h>
#include <IMesh.h>
#include <IMeshSceneNode.h>
#include <ISceneManager.h>
#include "graphics/irr_driver.hpp"
#include "graphics/glwrap.hpp"
#include "graphics/shaders.hpp"
#include "graphics/rtts.hpp"
#include "modes/world.hpp"
#include "modes/profile_world.hpp"
#include "utils/log.hpp"
// -----------------------------------------------------------------------------
GraphStructure::GraphStructure()
{
m_min_coord = 0;
m_scaling = 0;
m_node = NULL;
m_mesh = NULL;
m_mesh_buffer = NULL;
m_new_rtt = NULL;
} // GraphStructure
// -----------------------------------------------------------------------------
void GraphStructure::destroyRTT()
{
if (m_new_rtt != NULL)
{
delete m_new_rtt;
m_new_rtt = NULL;
}
} // destroyRTT
// -----------------------------------------------------------------------------
/** Cleans up the debug mesh */
void GraphStructure::cleanupDebugMesh()
{
if (m_node != NULL)
irr_driver->removeNode(m_node);
m_node = NULL;
// No need to call irr_driber->removeMeshFromCache, since the mesh
// was manually made and so never added to the mesh cache.
m_mesh->drop();
m_mesh = NULL;
}
// -----------------------------------------------------------------------------
/** Creates the debug mesh to display the graph on top of the track
* model. */
void GraphStructure::createDebugMesh()
{
if (getNumNodes() <= 0) return; // no debug output if not graph
createMesh(/*show_invisible*/true,
/*enable_transparency*/true);
// Now colour the quads red/blue/red ...
video::SColor c( 128, 255, 0, 0);
video::S3DVertex *v = (video::S3DVertex*)m_mesh_buffer->getVertices();
for (unsigned int i = 0; i < m_mesh_buffer->getVertexCount(); i++)
{
// Swap the colours from red to blue and back
c.setRed ((i%2) ? 255 : 0);
c.setBlue((i%2) ? 0 : 255);
v[i].Color = c;
}
m_node = irr_driver->addMesh(m_mesh, "track-debug-mesh");
#ifdef DEBUG
m_node->setName("track-debug-mesh");
#endif
} // createDebugMesh
// -----------------------------------------------------------------------------
/** Creates the actual mesh that is used by createDebugMesh() or makeMiniMap() */
void GraphStructure::createMesh(bool show_invisible,
bool enable_transparency,
const video::SColor *track_color)
{
// The debug track will not be lighted or culled.
video::SMaterial m;
m.BackfaceCulling = false;
m.Lighting = false;
if (enable_transparency)
m.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
m.setTexture(0, getUnicolorTexture(video::SColor(255, 255, 255, 255)));
m.setTexture(1, getUnicolorTexture(video::SColor(0, 0, 0, 0)));
m.setTexture(7, getUnicolorTexture(video::SColor(0, 0, 0, 0)));
m_mesh = irr_driver->createQuadMesh(&m);
m_mesh_buffer = m_mesh->getMeshBuffer(0);
assert(m_mesh_buffer->getVertexType()==video::EVT_STANDARD);
unsigned int n = 0;
const unsigned int total_nodes = getNumNodes();
// Count the number of quads to display (some quads might be invisible)
for (unsigned int i = 0; i < total_nodes; i++)
{
if (show_invisible || !isNodeInvisible(i))
n++;
}
// Four vertices for each of the n-1 remaining quads
video::S3DVertex *new_v = new video::S3DVertex[4*n];
// Each quad consists of 2 triangles with 3 elements, so
// we need 2*3 indices for each quad.
irr::u16 *ind = new irr::u16[6*n];
video::SColor c(255, 255, 0, 0);
if (track_color)
c = *track_color;
// Now add all quads
int i = 0;
for (unsigned int count = 0; count < total_nodes; count++)
{
// Ignore invisible quads
if (!show_invisible && isNodeInvisible(count))
continue;
// Swap the colours from red to blue and back
if (!track_color)
{
c.setRed ((i%2) ? 255 : 0);
c.setBlue((i%2) ? 0 : 255);
}
NodeColor nc = COLOR_RED;
const bool different_color = differentNodeColor(count, &nc);
// Transfer the 4 points of the current quad to the list of vertices
set3DVerticesOfGraph(count, new_v+4*i, (different_color ?
(nc == COLOR_RED ? video::SColor(255, 255, 0, 0) :
video::SColor(255, 0, 0, 255)) : c));
// Set up the indices for the triangles
// (note, afaik with opengl we could use quads directly, but the code
// would not be portable to directx anymore).
ind[6*i ] = 4*i+2; // First triangle: vertex 0, 1, 2
ind[6*i+1] = 4*i+1;
ind[6*i+2] = 4*i;
ind[6*i+3] = 4*i+3; // second triangle: vertex 0, 1, 3
ind[6*i+4] = 4*i+2;
ind[6*i+5] = 4*i;
i++;
}
m_mesh_buffer->append(new_v, n*4, ind, n*6);
if (hasLapLine())
{
video::S3DVertex lap_v[4];
irr::u16 lap_ind[6];
video::SColor lap_color(128, 255, 0, 0);
set3DVerticesOfGraph(0, lap_v, lap_color);
// Now scale the length (distance between vertix 0 and 3
// and between 1 and 2) to be 'length':
Vec3 bb_min, bb_max;
getGraphBoundingBox(&bb_min, &bb_max);
// Length of the lap line about 3% of the 'height'
// of the track.
const float length = (bb_max.getZ()-bb_min.getZ())*0.03f;
core::vector3df dl = lap_v[3].Pos-lap_v[0].Pos;
float ll2 = dl.getLengthSQ();
if (ll2 < 0.001)
lap_v[3].Pos = lap_v[0].Pos+core::vector3df(0, 0, 1);
else
lap_v[3].Pos = lap_v[0].Pos+dl*length/sqrt(ll2);
core::vector3df dr = lap_v[2].Pos-lap_v[1].Pos;
float lr2 = dr.getLengthSQ();
if (lr2 < 0.001)
lap_v[2].Pos = lap_v[1].Pos+core::vector3df(0, 0, 1);
else
lap_v[2].Pos = lap_v[1].Pos+dr*length/sqrt(lr2);
lap_ind[0] = 2;
lap_ind[1] = 1;
lap_ind[2] = 0;
lap_ind[3] = 3;
lap_ind[4] = 2;
lap_ind[5] = 0;
// Set it a bit higher to avoid issued with z fighting,
// i.e. part of the lap line might not be visible.
for (unsigned int i = 0; i < 4; i++)
lap_v[i].Pos.Y += 0.1f;
#ifndef USE_TEXTURED_LINE
m_mesh_buffer->append(lap_v, 4, lap_ind, 6);
#else
lap_v[0].TCoords = core::vector2df(0,0);
lap_v[1].TCoords = core::vector2df(3,0);
lap_v[2].TCoords = core::vector2df(3,1);
lap_v[3].TCoords = core::vector2df(0,1);
m_mesh_buffer->append(lap_v, 4, lap_ind, 6);
video::SMaterial &m = m_mesh_buffer->getMaterial();
video::ITexture *t = irr_driver->getTexture("chess.png");
m.setTexture(0, t);
#endif
}
// Instead of setting the bounding boxes, we could just disable culling,
// since the debug track should always be drawn.
//m_node->setAutomaticCulling(scene::EAC_OFF);
m_mesh_buffer->recalculateBoundingBox();
m_mesh->setBoundingBox(m_mesh_buffer->getBoundingBox());
m_mesh_buffer->getMaterial().setTexture(0, irr_driver->getTexture("unlit.png"));
delete[] ind;
delete[] new_v;
} // createMesh
// -----------------------------------------------------------------------------
/** Takes a snapshot of the graph so they can be used as minimap.
*/
void GraphStructure::makeMiniMap(const core::dimension2du &dimension,
const std::string &name,
const video::SColor &fill_color,
video::ITexture** oldRttMinimap,
FrameBuffer** newRttMinimap)
{
// Skip minimap when profiling
if (ProfileWorld::isNoGraphics()) return;
const video::SColor oldClearColor = World::getWorld()->getClearColor();
World::getWorld()->setClearbackBufferColor(video::SColor(0, 255, 255, 255));
World::getWorld()->forceFogDisabled(true);
*oldRttMinimap = NULL;
*newRttMinimap = NULL;
RTT* newRttProvider = NULL;
IrrDriver::RTTProvider* oldRttProvider = NULL;
if (CVS->isGLSL())
{
m_new_rtt = newRttProvider = new RTT(dimension.Width, dimension.Height);
}
else
{
oldRttProvider = new IrrDriver::RTTProvider(dimension, name, true);
}
irr_driver->getSceneManager()->setAmbientLight(video::SColor(255, 255, 255, 255));
createMesh(/*show_invisible part of the track*/ false,
/*enable_transparency*/ false,
/*track_color*/ &fill_color);
m_node = irr_driver->addMesh(m_mesh, "mini_map");
#ifdef DEBUG
m_node->setName("minimap-mesh");
#endif
m_node->setAutomaticCulling(0);
m_node->setMaterialFlag(video::EMF_LIGHTING, false);
// Add the camera:
// ---------------
scene::ICameraSceneNode *camera = irr_driver->addCameraSceneNode();
Vec3 bb_min, bb_max;
getGraphBoundingBox(&bb_min, &bb_max);
Vec3 center = (bb_max+bb_min)*0.5f;
float dx = bb_max.getX()-bb_min.getX();
float dz = bb_max.getZ()-bb_min.getZ();
// Set the scaling correctly. Also the center point (which is used
// as the camera position) needs to be adjusted: the track must
// be aligned to the left/top of the texture which is used (otherwise
// mapPoint2MiniMap doesn't work), so adjust the camera position
// that the track is properly aligned (view from the side):
// c camera
// / \ .
// / \ <--- camera angle
// / \ .
// { [-] } <--- track flat (viewed from the side)
// If [-] is the shorter side of the track, then the camera will
// actually render the area in { } - which is the length of the
// longer side of the track.
// To align the [-] side to the left, the camera must be moved
// the distance betwwen '{' and '[' to the right. This distance
// is exacly (longer_side - shorter_side) / 2.
// So, adjust the center point by this amount:
if (dz > dx)
{
center.setX(center.getX() + (dz-dx)*0.5f);
m_scaling = dimension.Width / dz;
}
else
{
center.setZ(center.getZ() + (dx-dz)*0.5f);
m_scaling = dimension.Width / dx;
}
float range = (dx>dz) ? dx : dz;
core::matrix4 projection;
projection.buildProjectionMatrixOrthoLH(range /* width */,
range /* height */,
-1, bb_max.getY()-bb_min.getY()+1);
camera->setProjectionMatrix(projection, true);
irr_driver->suppressSkyBox();
irr_driver->clearLights();
// Adjust Y position by +1 for max, -1 for min - this helps in case that
// the maximum Y coordinate is negative (otherwise the minimap is mirrored)
// and avoids problems for tracks which have a flat (max Y = min Y) minimap.
camera->setPosition(core::vector3df(center.getX(), bb_min.getY() + 1.0f, center.getZ()));
//camera->setPosition(core::vector3df(center.getX() - 5.0f, bb_min.getY() - 1 - 5.0f, center.getZ() - 15.0f));
camera->setUpVector(core::vector3df(0, 0, 1));
camera->setTarget(core::vector3df(center.getX(),bb_min.getY()-1,center.getZ()));
//camera->setAspectRatio(1.0f);
camera->updateAbsolutePosition();
video::ITexture* texture = NULL;
FrameBuffer* frame_buffer = NULL;
if (CVS->isGLSL())
{
frame_buffer = newRttProvider->render(camera, GUIEngine::getLatestDt());
}
else
{
texture = oldRttProvider->renderToTexture();
delete oldRttProvider;
}
cleanupDebugMesh();
irr_driver->removeCameraSceneNode(camera);
m_min_coord = bb_min;
if (texture == NULL && frame_buffer == NULL)
{
Log::error("Graph Structure", "[makeMiniMap] WARNING: RTT does not"
"appear to work, mini-map will not be available.");
}
*oldRttMinimap = texture;
*newRttMinimap = frame_buffer;
World::getWorld()->setClearbackBufferColor(oldClearColor);
World::getWorld()->forceFogDisabled(false);
irr_driver->getSceneManager()->clear();
VAOManager::kill();
irr_driver->clearGlowingNodes();
irr_driver->clearLights();
irr_driver->clearForcedBloom();
irr_driver->clearBackgroundNodes();
} // makeMiniMap
// -----------------------------------------------------------------------------
/** Returns the 2d coordinates of a point when drawn on the mini map
* texture.
* \param xyz Coordinates of the point to map.
* \param draw_at The coordinates in pixel on the mini map of the point,
* only the first two coordinates will be used.
*/
void GraphStructure::mapPoint2MiniMap(const Vec3 &xyz,Vec3 *draw_at) const
{
draw_at->setX((xyz.getX()-m_min_coord.getX())*m_scaling);
draw_at->setY((xyz.getZ()-m_min_coord.getZ())*m_scaling);
} // mapPoint

View File

@@ -1,101 +0,0 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2015 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.
#ifndef HEADER_GRAPH_STRUCTURE_HPP
#define HEADER_GRAPH_STRUCTURE_HPP
#include <string>
#include <dimension2d.h>
#include <SColor.h>
#include "utils/vec3.hpp"
#include "utils/no_copy.hpp"
namespace irr
{
namespace scene { class ISceneNode; class IMesh; class IMeshBuffer; }
namespace video { class ITexture; struct S3DVertex; }
}
using namespace irr;
class FrameBuffer;
class RTT;
/**
* \brief Virtual base class for a graph structure.
* This is mainly used for drawing minimap in game.
*
* \ingroup tracks
*/
class GraphStructure : public NoCopy
{
protected:
/** Used by soccer field with navmesh to draw goal line. */
enum NodeColor
{
COLOR_BLUE,
COLOR_RED
};
void cleanupDebugMesh();
void destroyRTT();
private:
RTT* m_new_rtt;
/** The node of the graph mesh. */
scene::ISceneNode *m_node;
/** The mesh of the graph mesh. */
scene::IMesh *m_mesh;
/** The actual mesh buffer storing the graph. */
scene::IMeshBuffer *m_mesh_buffer;
/** The minimum coordinates of the graph. */
Vec3 m_min_coord;
/** Scaling for mini map. */
float m_scaling;
void createMesh(bool show_invisible=true,
bool enable_transparency=false,
const video::SColor *track_color=NULL);
virtual void set3DVerticesOfGraph(int i, video::S3DVertex *v,
const video::SColor &color) const = 0;
virtual void getGraphBoundingBox(Vec3 *min, Vec3 *max) const = 0;
virtual const bool isNodeInvisible(int n) const = 0;
virtual const bool hasLapLine() const = 0;
virtual const bool differentNodeColor(int n, NodeColor* c) const = 0;
public:
GraphStructure();
virtual ~GraphStructure() {};
void createDebugMesh();
void makeMiniMap(const core::dimension2du &where,
const std::string &name,
const video::SColor &fill_color,
video::ITexture** oldRttMinimap,
FrameBuffer** newRttMinimap);
void mapPoint2MiniMap(const Vec3 &xyz, Vec3 *out) const;
virtual const unsigned int getNumNodes() const = 0;
}; // GraphStructure
#endif

View File

@@ -1,133 +0,0 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2009-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 "tracks/navmesh.hpp"
#include "io/file_manager.hpp"
#include "io/xml_node.hpp"
#include "tracks/quad.hpp"
#include "utils/log.hpp"
#include <algorithm>
NavMesh *NavMesh::m_nav_mesh = NULL;
/** Constructor, loads the mesh information from a given set of polygons
* from a navmesh.xml file.
* \param filename Name of the file containing all polygons
*/
NavMesh::NavMesh(const std::string &filename)
{
m_min = Vec3( 99999, 99999, 99999);
m_max = Vec3(-99999, -99999, -99999);
XMLNode *xml = file_manager->createXMLTree(filename);
if (xml->getName() != "navmesh")
{
Log::error("NavMesh", "NavMesh is invalid.");
delete xml;
return;
}
std::vector<Vec3> all_vertices;
for (unsigned int i = 0; i < xml->getNumNodes(); i++)
{
const XMLNode *xml_node = xml->getNode(i);
if (xml_node->getName() == "vertices")
{
for (unsigned int i = 0; i < xml_node->getNumNodes(); i++)
{
const XMLNode *xml_node_node = xml_node->getNode(i);
if (!(xml_node_node->getName() == "vertex"))
{
Log::error("NavMesh", "Unsupported type '%s' found"
"in '%s' - ignored.",
xml_node_node->getName().c_str(), filename.c_str());
continue;
}
// Reading vertices
Vec3 p;
readVertex(xml_node_node, &p);
m_max.max(p);
m_min.min(p);
all_vertices.push_back(p);
}
}
if (xml_node->getName() == "faces")
{
for(unsigned int i = 0; i < xml_node->getNumNodes(); i++)
{
const XMLNode *xml_node_node = xml_node->getNode(i);
if (xml_node_node->getName() != "face")
{
Log::error("NavMesh", "Unsupported type '%s' found in '%s'"
" - ignored.",
xml_node_node->getName().c_str(), filename.c_str());
continue;
}
// Reading quads
std::vector<int> quad_index;
std::vector<int> adjacent_quad_index;
xml_node_node->get("indices", &quad_index);
xml_node_node->get("adjacents", &adjacent_quad_index);
assert(quad_index.size() == 4);
m_adjacent_quads.push_back(adjacent_quad_index);
m_quads.push_back(new Quad(
all_vertices[quad_index[0]], all_vertices[quad_index[1]],
all_vertices[quad_index[2]], all_vertices[quad_index[3]]));
}
}
}
delete xml;
} // NavMesh
// ----------------------------------------------------------------------------
NavMesh::~NavMesh()
{
for (unsigned int i = 0; i < m_quads.size(); i++)
{
delete m_quads[i];
}
m_quads.clear();
} // ~NavMesh
// ----------------------------------------------------------------------------
/** Reads the vertex information from an XMLNode */
void NavMesh::readVertex(const XMLNode *xml, Vec3* result) const
{
float x, y, z;
xml->get("x", &x);
xml->get("y", &y);
xml->get("z", &z);
Vec3 temp(x, y, z);
*result = temp;
} // readVertex
// ----------------------------------------------------------------------------
const Vec3& NavMesh::getCenterOfQuad(unsigned int n) const
{
assert(m_quads.size() > 0 && n < m_quads.size());
return m_quads[n]->getCenter();
} // getCenterOfQuad

View File

@@ -1,121 +0,0 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2009-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, B
#ifndef HEADER_NAVMESH_HPP
#define HEADER_NAVMESH_HPP
#include <vector>
#include <string>
#include "utils/vec3.hpp"
class Quad;
class XMLNode;
/**
* \brief This class stores a set of navigation quads. It uses a
* 'simplified singleton' design pattern: it has a static create function
* to create exactly one instance, a destroy function, and a get function
* (that does not have the side effect of the 'normal singleton' design
* pattern to create an instance). Besides saving on the if statement in
* get(), this is necessary since certain race modes might not have a
* navigation mesh at all (e.g. race mode). So get() returns NULL in this
* case, and this is tested where necessary.
* \ingroup tracks
*/
class NavMesh
{
private:
static NavMesh *m_nav_mesh;
/** The 2d bounding box, used for hashing. */
Vec3 m_min;
Vec3 m_max;
/** The actual set of quads that constitute the nav mesh */
std::vector<Quad*> m_quads;
std::vector<std::vector<int>> m_adjacent_quads;
void readVertex(const XMLNode *xml, Vec3* result) const;
// ------------------------------------------------------------------------
NavMesh(const std::string &filename);
// ------------------------------------------------------------------------
~NavMesh();
public:
/** Creates a NavMesh instance. */
static void create(const std::string &filename)
{
assert(m_nav_mesh == NULL);
m_nav_mesh = new NavMesh(filename);
}
// ------------------------------------------------------------------------
/** Cleans up the nav mesh. It is possible that this function is called
* even if no instance exists (e.g. in race). So it is not an
* error if there is no instance.
*/
static void destroy()
{
if (m_nav_mesh)
{
delete m_nav_mesh;
m_nav_mesh = NULL;
}
}
// ------------------------------------------------------------------------
/** Returns the one instance of this object. It is possible that there
* is no instance created (e.g. in normal race, since it doesn't have
* a nav mesh), so we don't assert that an instance exist, and we
* also don't create one if it doesn't exists.
*/
static NavMesh *get() { return m_nav_mesh; }
// ------------------------------------------------------------------------
/** Return the minimum and maximum coordinates of this navmesh. */
void getBoundingBox(Vec3 *min, Vec3 *max) { *min=m_min; *max=m_max; }
// ------------------------------------------------------------------------
/** Returns a const reference to a quad */
const Quad& getQuad(unsigned int n) const
{
assert(m_quads.size() > 0 && n < m_quads.size());
return *(m_quads[n]);
}
// ------------------------------------------------------------------------
/** Returns a const referece to a vector containing the indices
* of quads adjacent to a given quad
*/
const std::vector<int>& getAdjacentQuads(unsigned int n) const
{
assert(m_adjacent_quads.size() > 0 && n < m_adjacent_quads.size() &&
m_quads.size() == m_adjacent_quads.size());
return m_adjacent_quads[n];
}
// ------------------------------------------------------------------------
/** Returns the total number of quads */
unsigned int getNumberOfQuads() const
{
assert(m_quads.size() > 0);
return m_quads.size();
}
// ------------------------------------------------------------------------
/** Returns the center of a quad */
const Vec3& getCenterOfQuad(unsigned int n) const;
};
#endif

View File

@@ -23,33 +23,18 @@
#include <S3DVertex.h>
#include <triangle3d.h>
#include "LinearMath/btTransform.h"
/** Constructor, takes 4 points. */
Quad::Quad(const Vec3 &p0, const Vec3 &p1, const Vec3 &p2, const Vec3 &p3,
bool invisible, bool ai_ignore)
{
if(p1.sideOfLine2D(p0, p2)>0 ||
p3.sideOfLine2D(p0, p2)<0)
{
Log::warn("Quad", "Quad has wrong orientation: p0=%f %f %f p1=%f %f %f",
p0.getX(), p0.getY(), p0.getZ(),p1.getX(), p1.getY(), p1.getZ());
Log::warn("Quad", "The quad will be swapped, nevertheless test for correctness -");
Log::warn("Quad", "quads must be counter-clockwise oriented.");
m_p[0]=p1; m_p[1]=p0; m_p[2]=p3; m_p[3]=p2;
}
else
{
m_p[0]=p0; m_p[1]=p1; m_p[2]=p2; m_p[3]=p3;
}
const Vec3 &normal, int index, bool invisible)
: m_index(index), m_normal(normal), m_invisible(invisible)
{
m_p[0]=p0; m_p[1]=p1; m_p[2]=p2; m_p[3]=p3;
m_center = 0.25f*(p0+p1+p2+p3);
m_min_height = std::min ( std::min(p0.getY(), p1.getY()),
std::min(p2.getY(), p3.getY()) );
m_max_height = std::max ( std::max(p0.getY(), p1.getY()),
std::max(p2.getY(), p3.getY()) );
m_invisible = invisible;
m_ai_ignore = ai_ignore;
} // Quad
// ----------------------------------------------------------------------------
@@ -61,25 +46,17 @@ void Quad::getVertices(video::S3DVertex *v, const video::SColor &color) const
{
// Eps is used to raise the track debug quads a little bit higher than
// the ground, so that they are actually visible.
core::vector3df eps(0, 0.1f, 0);
core::vector3df normal = getNormal().toIrrVector();
core::vector3df eps = normal * 0.1f;
v[0].Pos = m_p[0].toIrrVector()+eps;
v[1].Pos = m_p[1].toIrrVector()+eps;
v[2].Pos = m_p[2].toIrrVector()+eps;
v[3].Pos = m_p[3].toIrrVector()+eps;
core::triangle3df tri(m_p[0].toIrrVector(), m_p[1].toIrrVector(),
m_p[2].toIrrVector());
core::vector3df normal = tri.getNormal();
normal.normalize();
v[0].Normal = normal;
v[1].Normal = normal;
v[2].Normal = normal;
core::triangle3df tri1(m_p[0].toIrrVector(), m_p[2].toIrrVector(),
m_p[3].toIrrVector());
core::vector3df normal1 = tri1.getNormal();
normal1.normalize();
v[3].Normal = normal1;
v[3].Normal = normal;
v[0].Color = color;
v[1].Color = color;
@@ -88,7 +65,7 @@ void Quad::getVertices(video::S3DVertex *v, const video::SColor &color) const
} // setVertices
// ----------------------------------------------------------------------------
bool Quad::pointInQuad(const Vec3& p, bool ignore_vertical) const
bool Quad::pointInside(const Vec3& p, bool ignore_vertical) const
{
// In case that a kart can validly run too high over one driveline
// and it should not be considered to be on that driveline. Example:
@@ -116,26 +93,4 @@ bool Quad::pointInQuad(const Vec3& p, bool ignore_vertical) const
return p.sideOfLine2D(m_p[2], m_p[3]) > 0.0 &&
p.sideOfLine2D(m_p[3], m_p[0]) >= 0.0;
}
} // pointInQuad
// ----------------------------------------------------------------------------
/** Transforms a quad by a given transform (i.e. translation+rotation). This
* function does not modify this quad, the results are stored in the quad
* specified as parameter. These functions are used for slipstreaming to
* determine the slipstream area from the original value (kart at 0,0,0 and
* no rotation) to the current value.
* \param t The transform to apply.
* \param result The quad which stores the result.
*/
void Quad::transform(const btTransform &t, Quad *result) const
{
result->m_p[0] = t(m_p[0]);
result->m_p[1] = t(m_p[1]);
result->m_p[2] = t(m_p[2]);
result->m_p[3] = t(m_p[3]);
result->m_min_height = std::min ( std::min(result->m_p[0].getY(),
result->m_p[1].getY()),
std::min(result->m_p[2].getY(),
result->m_p[3].getY()) );
} // transform
} // pointInside

View File

@@ -20,6 +20,9 @@
#define HEADER_QUAD_HPP
#include <SColor.h>
#include "utils/leak_check.hpp"
#include "utils/no_copy.hpp"
#include "utils/vec3.hpp"
namespace irr
@@ -28,14 +31,12 @@ namespace irr
}
using namespace irr;
class btTransform;
/**
* \ingroup tracks
*/
class Quad
class Quad : public NoCopy
{
private:
protected:
/** The four points of a quad. */
Vec3 m_p[4];
@@ -43,6 +44,16 @@ private:
* This saves some computations at runtime. */
Vec3 m_center;
/** Index of this quad, used only with graph. */
int m_index;
/** Normal of the quad */
Vec3 m_normal;
private:
/** Set to true if this quad should not be shown in the minimap. */
bool m_invisible;
/** The minimum height of the quad, used in case that several quads
* are on top of each other when determining the sector a kart is on. */
float m_min_height;
@@ -51,34 +62,55 @@ private:
* to distinguish between quads which are on top of each other. */
float m_max_height;
/** Set to true if this quad should not be shown in the minimap. */
bool m_invisible;
/** Set if this quad should not be used by the AI. */
bool m_ai_ignore;
public:
Quad(const Vec3 &p0, const Vec3 &p1, const Vec3 &p2, const Vec3 &p3,
bool invis=false, bool ai_ignore=false);
LEAK_CHECK()
// ------------------------------------------------------------------------
Quad(const Vec3 &p0, const Vec3 &p1, const Vec3 &p2, const Vec3 &p3,
const Vec3 & normal = Vec3(0, 1, 0), int index = -1,
bool invisible = false);
// ------------------------------------------------------------------------
virtual ~Quad() {}
// ------------------------------------------------------------------------
void getVertices(video::S3DVertex *v, const video::SColor &color) const;
bool pointInQuad(const Vec3& p, bool ignore_vertical = false) const;
void transform(const btTransform &t, Quad *result) const;
// ------------------------------------------------------------------------
/** Returns the i-th. point of a quad. */
const Vec3& operator[](int i) const {return m_p[i]; }
const Vec3& operator[](int i) const { return m_p[i]; }
// ------------------------------------------------------------------------
/** Returns the center of a quad. */
const Vec3& getCenter () const {return m_center; }
const Vec3& getCenter () const { return m_center; }
// ------------------------------------------------------------------------
/** Returns the minimum height of a quad. */
float getMinHeight() const { return m_min_height; }
float getMinHeight() const { return m_min_height; }
// ------------------------------------------------------------------------
/** Returns the index of this quad. */
int getIndex() const
{
// You should not call this if it has default value (like slipstream)
assert(m_index != -1);
return m_index;
}
// ------------------------------------------------------------------------
/** Returns true of this quad is invisible, i.e. not to be shown in
* the minimap. */
bool isInvisible() const { return m_invisible; }
bool isInvisible() const { return m_invisible; }
// ------------------------------------------------------------------------
/** True if this quad should be ignored by the AI. */
bool letAIIgnore() const { return m_ai_ignore; }
/** Returns the normal of this quad. */
const Vec3& getNormal() const { return m_normal; }
// ------------------------------------------------------------------------
/** Returns true if a point is inside this quad. */
virtual bool pointInside(const Vec3& p,
bool ignore_vertical = false) const;
// ------------------------------------------------------------------------
/** Returns true if this quad is 3D, which additional 3D testing is used in
* pointInside. */
virtual bool is3DQuad() const { return false; }
// ------------------------------------------------------------------------
virtual float getDistance2FromPoint(const Vec3 &xyz) const
{
// You should not call this in a bare quad
assert(false);
return 0.0f;
}
}; // class Quad
#endif

View File

@@ -1,182 +0,0 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2009-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, B
#ifndef HEADER_QUAD_GRAPH_HPP
#define HEADER_QUAD_GRAPH_HPP
#include <vector>
#include <string>
#include <set>
#include "tracks/graph_node.hpp"
#include "tracks/graph_structure.hpp"
#include "tracks/quad_set.hpp"
#include "utils/aligned_array.hpp"
class CheckLine;
/**
* \brief This class stores a graph of quads. It uses a 'simplified singleton'
* design pattern: it has a static create function to create exactly instance,
* a destroy function, and a get function (that does not have the side effect
* of the 'normal singleton' design pattern to create an instance). Besides
* saving on the if statement in get(), this is necessary since certain race
* modes might not have a quad graph at all (e.g. battle mode). So get()
* returns NULL in this case, and this is tested where necessary.
* \ingroup tracks
*/
class QuadGraph : public GraphStructure
{
private:
static QuadGraph *m_quad_graph;
/** The actual graph data structure. */
std::vector<GraphNode*> m_all_nodes;
/** The length of the first loop. */
float m_lap_length;
/** Stores the filename - just used for error messages. */
std::string m_quad_filename;
/** Wether the graph should be reverted or not */
bool m_reverse;
void setDefaultSuccessors();
void computeChecklineRequirements(GraphNode* node, int latest_checkline);
void computeDirectionData();
void determineDirection(unsigned int current, unsigned int succ_index);
float normalizeAngle(float f);
void addSuccessor(unsigned int from, unsigned int to);
void load (const std::string &filename);
void computeDistanceFromStart(unsigned int start_node, float distance);
unsigned int getStartNode() const;
QuadGraph (const std::string &quad_file_name,
const std::string &graph_file_name,
const bool reverse);
~QuadGraph ();
// ------------------------------------------------------------------------
virtual void set3DVerticesOfGraph(int i, video::S3DVertex *v,
const video::SColor &color) const
{ m_all_nodes[i]->getQuad().getVertices(v, color); }
// ------------------------------------------------------------------------
virtual void getGraphBoundingBox(Vec3 *min, Vec3 *max) const
{ QuadSet::get()->getBoundingBox(min, max); }
// ------------------------------------------------------------------------
virtual const bool isNodeInvisible(int n) const
{ return m_all_nodes[n]->getQuad().isInvisible(); }
// ------------------------------------------------------------------------
virtual const bool hasLapLine() const
{ return true; }
// ------------------------------------------------------------------------
virtual const bool differentNodeColor(int n, NodeColor* c) const
{ return false; }
public:
static const int UNKNOWN_SECTOR;
void getSuccessors(int node_number,
std::vector<unsigned int>& succ,
bool for_ai=false) const;
void spatialToTrack(Vec3 *dst, const Vec3& xyz,
const int sector) const;
void findRoadSector(const Vec3& XYZ, int *sector,
std::vector<int> *all_sectors=NULL) const;
int findOutOfRoadSector(const Vec3& xyz,
const int curr_sector=UNKNOWN_SECTOR,
std::vector<int> *all_sectors=NULL
) const;
void setDefaultStartPositions(AlignedArray<btTransform>
*start_transforms,
unsigned int karts_per_row,
float forwards_distance=1.5f,
float sidewards_distance=1.5f,
float upwards_distance=0.0f) const;
void updateDistancesForAllSuccessors(unsigned int indx,
float delta,
unsigned int count);
void setupPaths();
void computeChecklineRequirements();
// ----------------------------------------------------------------------======
/** Returns the one instance of this object. It is possible that there
* is no instance created (e.g. in battle mode, since it doesn't have
* a quad graph), so we don't assert that an instance exist, and we
* also don't create one if it doesn't exists. */
static QuadGraph *get() { return m_quad_graph; }
// ----------------------------------------------------------------------==
/** Creates a QuadGraph instance. */
static void create(const std::string &quad_file_name,
const std::string &graph_file_name,
const bool reverse)
{
assert(m_quad_graph==NULL);
// assignment to m_quad_graph is done in the constructor, since
// functions called from the constructor need it to be defined.
new QuadGraph(quad_file_name, graph_file_name, reverse);
} // create
// ------------------------------------------------------------------------
/** Cleans up the quad graph. It is possible that this function is called
* even if no instance exists (e.g. in battle mode). So it is not an
* error if there is no instance. */
static void destroy()
{
if(m_quad_graph)
{
delete m_quad_graph;
m_quad_graph = NULL;
}
} // destroy
// ------------------------------------------------------------------------
/** Returns the number of nodes in the graph. */
virtual const unsigned int getNumNodes() const
{ return (unsigned int)m_all_nodes.size();}
// ------------------------------------------------------------------------
/** Return the distance to the j-th successor of node n. */
float getDistanceToNext(int n, int j) const
{ return m_all_nodes[n]->getDistanceToSuccessor(j);}
// ------------------------------------------------------------------------
/** Returns the angle of the line between node n and its j-th.
* successor. */
float getAngleToNext(int n, int j) const
{ return m_all_nodes[n]->getAngleToSuccessor(j); }
// ------------------------------------------------------------------------
/** Returns the number of successors of a node n. */
int getNumberOfSuccessors(int n) const
{ return m_all_nodes[n]->getNumberOfSuccessors(); }
// ------------------------------------------------------------------------
/** Returns the quad that belongs to a graph node. */
const Quad& getQuadOfNode(unsigned int j) const
{ return QuadSet::get()->getQuad(m_all_nodes[j]->getQuadIndex()); }
// ------------------------------------------------------------------------
/** Returns the quad that belongs to a graph node. */
GraphNode& getNode(unsigned int j) const{ return *m_all_nodes[j]; }
// ------------------------------------------------------------------------
/** Returns the distance from the start to the beginning of a quad. */
float getDistanceFromStart(int j) const
{ return m_all_nodes[j]->getDistanceFromStart(); }
// ------------------------------------------------------------------------
/** Returns the length of the main driveline. */
float getLapLength() const {return m_lap_length; }
// ------------------------------------------------------------------------
bool isReverse() const {return m_reverse; }
}; // QuadGraph
#endif

View File

@@ -1,124 +0,0 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2009-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 "tracks/quad_set.hpp"
#include <stdlib.h>
#include <algorithm>
#include "io/file_manager.hpp"
#include "io/xml_node.hpp"
#include "utils/string_utils.hpp"
QuadSet *QuadSet::m_quad_set = NULL;
/** Constructor, loads the quad set from a file. Assigns a pointer
* to this instance to m_quad_set, so that it can be accessed using get().
*/
QuadSet::QuadSet()
{
m_quad_set = this;
} // QuadSet
// -----------------------------------------------------------------------------
/** Destructor, frees all memory.
*/
QuadSet::~QuadSet()
{
for(unsigned int i=0; i<m_all_quads.size(); i++)
{
delete m_all_quads[i];
}
m_all_quads.clear();
m_quad_set = NULL;
} // ~QuadSet
// -----------------------------------------------------------------------------}
/** This function interprets a point specification as an attribute in the
xml quadset file. It understands two different specifications:
p1="n:p" : get point p from square n (n, p integers)
p1="p1,p2,p3" : make a 3d point out of these 3 floating point values
*/
void QuadSet::getPoint(const XMLNode *xml, const std::string &attribute_name,
Vec3* result) const
{
std::string s;
xml->get(attribute_name, &s);
int pos=(int)s.find_first_of(":");
if(pos>0) // n:p specification
{
std::vector<std::string> l = StringUtils::split(s, ':');
int n=atoi(l[0].c_str());
int p=atoi(l[1].c_str());
*result=(*m_all_quads[n])[p];
}
else
{
xml->get(attribute_name, result);
}
} // getPoint
// -----------------------------------------------------------------------------
/** Loads the set of all quads from the specified filename.
* \param filename The absolute filename to load the quad file from.
*/
void QuadSet::init(const std::string &filename)
{
m_min = Vec3( 99999, 99999, 99999);
m_max = Vec3(-99999, -99999, -99999);
XMLNode *xml = file_manager->createXMLTree(filename);
if(!xml || xml->getName()!="quads")
{
Log::error("[QuadSet::load] ERROR : QuadSet '%s' not found.", filename.c_str());
delete xml;
return;
}
for(unsigned int i=0; i<xml->getNumNodes(); i++)
{
const XMLNode *xml_node = xml->getNode(i);
if(xml_node->getName()!="quad")
{
Log::warn("[QuadSet::load] WARNING: Unsupported node type '%s' found in '%s' - ignored.",
xml_node->getName().c_str(), filename.c_str());
continue;
}
// Note that it's not easy to do the reading of the parameters here
// in quad, since the specification in the xml can contain references
// to previous points. E.g.:
// <quad p0="40:3" p1="40:2" p2="25.396030 0.770338 64.796539" ...
Vec3 p0, p1, p2, p3;
getPoint(xml_node, "p0", &p0);
getPoint(xml_node, "p1", &p1);
getPoint(xml_node, "p2", &p2);
getPoint(xml_node, "p3", &p3);
bool invisible=false;
xml_node->get("invisible", &invisible);
bool ai_ignore=false;
xml_node->get("ai-ignore", &ai_ignore);
Quad* q=new Quad(p0,p1,p2,p3, invisible, ai_ignore);
m_all_quads.push_back(q);
m_max.max(p0);m_max.max(p1);m_max.max(p2);m_max.max(p3);
m_min.min(p0);m_min.min(p1);m_min.min(p2);m_min.min(p3);
}
delete xml;
} // load
// -----------------------------------------------------------------------------

View File

@@ -1,90 +0,0 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2009-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.
#ifndef HEADER_QUAD_SET_HPP
#define HEADER_QUAD_SET_HPP
#include <vector>
#include <string>
#include "tracks/quad.hpp"
#include "utils/vec3.hpp"
class XMLNode;
/**
* \ingroup tracks
*/
class QuadSet
{
private:
/** The 2d bounding box, used for hashing. */
Vec3 m_min;
Vec3 m_max;
/** The list of all quads. */
std::vector<Quad*> m_all_quads;
/** Pointer to the one instance of a quad set. */
static QuadSet *m_quad_set;
void getPoint(const XMLNode *xml, const std::string &attribute_name,
Vec3 *result) const;
QuadSet();
~QuadSet();
public:
static const int QUAD_NONE=-1;
void init (const std::string &filename);
// ------------------------------------------------------------------------
/** Creates one instance of the quad set. */
static void create()
{
assert(m_quad_set==NULL);
m_quad_set = new QuadSet();
}
// ------------------------------------------------------------------------
/** Destroys the one instance of a quad set. */
static void destroy()
{
delete m_quad_set; m_quad_set = NULL;
}
// ------------------------------------------------------------------------
/** Static member function to access the QuadSet instance. */
static QuadSet *get() { return m_quad_set; }
// ------------------------------------------------------------------------
/** Returns the quad with a given index number. */
const Quad& getQuad(int n) const {return *(m_all_quads[n]); }
// ------------------------------------------------------------------------
/** Return the minimum and maximum coordinates of this quad set. */
void getBoundingBox(Vec3 *min, Vec3 *max)
{ *min=m_min; *max=m_max; }
// ------------------------------------------------------------------------
/** Returns the number of quads. */
unsigned int getNumberOfQuads() const
{return (unsigned int)m_all_quads.size(); }
// ------------------------------------------------------------------------
/** Returns the center of quad n. */
const Vec3& getCenterOfQuad(int n) const
{return m_all_quads[n]->getCenter(); }
// ------------------------------------------------------------------------
/** Returns the n-th. quad. */
const Quad& getQuad(int n) {return *(m_all_quads[n]); }
}; // QuadSet
#endif

View File

@@ -93,6 +93,19 @@ void TerrainInfo::update(const btMatrix3x3 &rotation, const Vec3 &from)
->castRay(from, to, &m_hit_point, &m_material,
&m_normal, /*interpolate*/true);
} // update
//-----------------------------------------------------------------------------
/** Update the terrain information based on the latest position.
* \param Position from which to start the rayast from.
*/
void TerrainInfo::update(const Vec3 &from, const Vec3 &towards)
{
m_last_material = m_material;
Vec3 direction = towards.normalized();
btVector3 to = from + 10000.0f*direction;
const TriangleMesh &tm = World::getWorld()->getTrack()->getTriangleMesh();
tm.castRay(from, to, &m_hit_point, &m_material, &m_normal);
} // update
// -----------------------------------------------------------------------------
/** Does a raycast upwards from the given position

View File

@@ -52,6 +52,7 @@ public:
const Material **m);
virtual void update(const btMatrix3x3 &rotation, const Vec3 &from);
virtual void update(const Vec3 &from);
virtual void update(const Vec3 &from, const Vec3 &towards);
// ------------------------------------------------------------------------
/** Simple wrapper with no offset. */

View File

@@ -32,6 +32,7 @@
#include "graphics/glwrap.hpp"
#include "graphics/irr_driver.hpp"
#include "graphics/lod_node.hpp"
#include "graphics/material.hpp"
#include "graphics/material_manager.hpp"
#include "graphics/mesh_tools.hpp"
#include "graphics/moving_texture.hpp"
@@ -51,13 +52,14 @@
#include "physics/physics.hpp"
#include "physics/triangle_mesh.hpp"
#include "race/race_manager.hpp"
#include "scriptengine/script_engine.hpp"
#include "tracks/arena_graph.hpp"
#include "tracks/bezier_curve.hpp"
#include "tracks/battle_graph.hpp"
#include "tracks/check_manager.hpp"
#include "tracks/drive_graph.hpp"
#include "tracks/drive_node.hpp"
#include "tracks/model_definition_loader.hpp"
#include "tracks/track_manager.hpp"
#include "tracks/quad_graph.hpp"
#include "tracks/quad_set.hpp"
#include "tracks/track_object_manager.hpp"
#include "utils/constants.hpp"
#include "utils/log.hpp"
@@ -275,8 +277,7 @@ void Track::reset()
*/
void Track::cleanup()
{
QuadGraph::destroy();
BattleGraph::destroy();
Graph::destroy();
ItemManager::destroy();
VAOManager::kill();
@@ -372,7 +373,7 @@ void Track::cleanup()
}
if (m_new_rtt_mini_map)
{
m_new_rtt_mini_map = NULL; // already deleted by QuadGraph::~QuadGraph
m_new_rtt_mini_map = NULL; // already deleted by Graph::~Graph
}
for(unsigned int i=0; i<m_sky_textures.size(); i++)
@@ -665,14 +666,15 @@ void Track::startMusic() const
} // startMusic
//-----------------------------------------------------------------------------
/** Loads the polygon graph for battle, i.e. the definition of all polys, and the way
* they are connected to each other. Input file name is hardcoded for now
/** Loads the quad graph for arena, i.e. the definition of all quads, and the
* way they are connected to each other. Input file name is hardcoded for now
*/
void Track::loadBattleGraph(const XMLNode &node)
void Track::loadArenaGraph(const XMLNode &node)
{
BattleGraph::create(m_root+"navmesh.xml", &node);
ArenaGraph* graph = new ArenaGraph(m_root+"navmesh.xml", &node);
Graph::setGraph(graph);
if(BattleGraph::get()->getNumNodes()==0)
if(Graph::get()->getNumNodes()==0)
{
Log::warn("track", "No graph nodes defined for track '%s'\n",
m_filename.c_str());
@@ -681,27 +683,56 @@ void Track::loadBattleGraph(const XMLNode &node)
{
loadMinimap();
}
} // loadBattleGraph
} // loadArenaGraph
//-----------------------------------------------------------------------------
/** Loads the quad graph, i.e. the definition of all quads, and the way
btQuaternion Track::getArenaStartRotation(const Vec3& xyz, float heading)
{
btQuaternion def_pos(Vec3(0, 1, 0), heading * DEGREE_TO_RAD);
if (!ArenaGraph::get())
return def_pos;
// Set the correct axis based on normal of the starting position
int node = Graph::UNKNOWN_SECTOR;
Graph::get()->findRoadSector(xyz, &node);
if (node == Graph::UNKNOWN_SECTOR)
{
Log::warn("track", "Starting position is not on ArenaGraph");
return def_pos;
}
const Vec3& normal = Graph::get()->getQuad(node)->getNormal();
Vec3 axis = -normal.cross(Vec3(0, 1, 0));
if (axis.length() == 0)
axis = Vec3(0, 0, 1);
btQuaternion q(axis, normal.angle(Vec3(0, 1, 0)));
btMatrix3x3 m;
m.setRotation(q);
return btQuaternion(m.getColumn(1), heading * DEGREE_TO_RAD) * q;
} // getArenaStartRotation
//-----------------------------------------------------------------------------
/** Loads the drive graph, i.e. the definition of all quads, and the way
* they are connected to each other.
*/
void Track::loadQuadGraph(unsigned int mode_id, const bool reverse)
void Track::loadDriveGraph(unsigned int mode_id, const bool reverse)
{
QuadGraph::create(m_root+m_all_modes[mode_id].m_quad_name,
m_root+m_all_modes[mode_id].m_graph_name,
reverse);
new DriveGraph(m_root+m_all_modes[mode_id].m_quad_name,
m_root+m_all_modes[mode_id].m_graph_name, reverse);
QuadGraph::get()->setupPaths();
// setGraph is done in DriveGraph constructor
assert(DriveGraph::get());
DriveGraph::get()->setupPaths();
#ifdef DEBUG
for(unsigned int i=0; i<QuadGraph::get()->getNumNodes(); i++)
for(unsigned int i=0; i<DriveGraph::get()->getNumNodes(); i++)
{
assert(QuadGraph::get()->getNode(i).getPredecessor(0)!=-1);
assert(DriveGraph::get()->getNode(i)->getPredecessor(0)!=-1);
}
#endif
if(QuadGraph::get()->getNumNodes()==0)
if(DriveGraph::get()->getNumNodes()==0)
{
Log::warn("track", "No graph nodes defined for track '%s'\n",
m_filename.c_str());
@@ -715,16 +746,13 @@ void Track::loadQuadGraph(unsigned int mode_id, const bool reverse)
{
loadMinimap();
}
} // loadQuadGraph
} // loadDriveGraph
// -----------------------------------------------------------------------------
void Track::mapPoint2MiniMap(const Vec3 &xyz, Vec3 *draw_at) const
{
if ((m_is_arena || m_is_soccer) && m_has_navmesh)
BattleGraph::get()->mapPoint2MiniMap(xyz, draw_at);
else
QuadGraph::get()->mapPoint2MiniMap(xyz, draw_at);
Graph::get()->mapPoint2MiniMap(xyz, draw_at);
draw_at->setX(draw_at->getX() * m_minimap_x_scale);
draw_at->setY(draw_at->getY() * m_minimap_y_scale);
}
@@ -1027,16 +1055,8 @@ void Track::loadMinimap()
core::dimension2du size = m_mini_map_size
.getOptimalSize(!nonpower,!nonsquare);
if ((m_is_arena || m_is_soccer) && m_has_navmesh)
{
BattleGraph::get()->makeMiniMap(size, "minimap::" + m_ident, video::SColor(127, 255, 255, 255),
&m_old_rtt_mini_map, &m_new_rtt_mini_map);
}
else
{
QuadGraph::get()->makeMiniMap(size, "minimap::" + m_ident, video::SColor(127, 255, 255, 255),
&m_old_rtt_mini_map, &m_new_rtt_mini_map);
}
Graph::get()->makeMiniMap(size, "minimap::" + m_ident, video::SColor(127, 255, 255, 255),
&m_old_rtt_mini_map, &m_new_rtt_mini_map);
if (m_old_rtt_mini_map)
{
@@ -1655,9 +1675,9 @@ void Track::loadTrackModel(bool reverse_track, unsigned int mode_id)
// the race gui was created. The race gui is needed since it stores
// the information about the size of the texture to render the mini
// map to.
if (!m_is_arena && !m_is_soccer && !m_is_cutscene) loadQuadGraph(mode_id, reverse_track);
if (!m_is_arena && !m_is_soccer && !m_is_cutscene) loadDriveGraph(mode_id, reverse_track);
else if ((m_is_arena || m_is_soccer) && !m_is_cutscene && m_has_navmesh)
loadBattleGraph(*root);
loadArenaGraph(*root);
ItemManager::create();
@@ -1687,7 +1707,7 @@ void Track::loadTrackModel(bool reverse_track, unsigned int mode_id)
}
else
m_start_transforms.resize(race_manager->getNumberOfKarts());
QuadGraph::get()->setDefaultStartPositions(&m_start_transforms,
DriveGraph::get()->setDefaultStartPositions(&m_start_transforms,
karts_per_row,
forwards_distance,
sidewards_distance,
@@ -1873,20 +1893,8 @@ void Track::loadTrackModel(bool reverse_track, unsigned int mode_id)
delete root;
if ((m_is_arena || m_is_soccer) && !m_is_cutscene && m_has_navmesh && !arena_random_item_created)
BattleGraph::get()->findItemsOnGraphNodes();
if (UserConfigParams::m_track_debug &&
race_manager->getMinorMode()!=RaceManager::MINOR_MODE_3_STRIKES &&
!m_is_cutscene)
{
QuadGraph::get()->createDebugMesh();
}
if (UserConfigParams::m_track_debug && m_has_navmesh &&
race_manager->getMinorMode()==RaceManager::MINOR_MODE_3_STRIKES &&
!m_is_cutscene)
BattleGraph::get()->createDebugMesh();
if (UserConfigParams::m_track_debug && Graph::get() && !m_is_cutscene)
Graph::get()->createDebugMesh();
// Only print warning if not in battle mode, since battle tracks don't have
// any quads or check lines.
@@ -1910,7 +1918,7 @@ void Track::loadTrackModel(bool reverse_track, unsigned int mode_id)
World *world = World::getWorld();
if (world->useChecklineRequirements())
{
QuadGraph::get()->computeChecklineRequirements();
DriveGraph::get()->computeChecklineRequirements();
}
EasterEggHunt *easter_world = dynamic_cast<EasterEggHunt*>(world);
@@ -1969,9 +1977,8 @@ void Track::loadObjects(const XMLNode* root, const std::string& path, ModelDefin
}
m_start_transforms[position].setOrigin(xyz);
m_start_transforms[position].setRotation(
btQuaternion(btVector3(0,1,0),
h*DEGREE_TO_RAD ) );
m_start_transforms[position]
.setRotation(getArenaStartRotation(xyz, h));
}
else if (name == "camera")
{
@@ -2260,19 +2267,38 @@ void Track::itemCommand(const XMLNode *node)
}
Vec3 loc(xyz);
// if only 2d coordinates are given, let the item fall from very high
if(drop)
// Test if the item lies on a 3d node, if so adjust the normal
// Also do a raycast if drop item is given
Vec3 normal(0, 1, 0);
Vec3 quad_normal = normal;
Vec3 hit_point = loc;
if (Graph::get())
{
int road_sector = Graph::UNKNOWN_SECTOR;
Graph::get()->findRoadSector(xyz, &road_sector);
// Only do custom direction of raycast if item is on quad graph
if (road_sector != Graph::UNKNOWN_SECTOR)
{
quad_normal = Graph::get()->getQuad(road_sector)->getNormal();
}
}
if (drop)
{
const Material *m;
// If raycast is used, increase the start position slightly
// in case that the point is too close to the actual surface
// (e.g. floating point errors can cause a problem here).
loc += Vec3(0,0.1f,0);
loc += quad_normal * 0.1f;
#ifndef DEBUG
// Avoid unused variable warning in case of non-debug compilation.
setTerrainHeight(&loc);
m_track_mesh->castRay(loc, loc + (-10000 * quad_normal), &hit_point,
&m, &normal);
#else
bool drop_success = setTerrainHeight(&loc);
if(!drop_success)
bool drop_success = m_track_mesh->castRay(loc, loc +
(-10000 * quad_normal), &hit_point, &m, &normal);
if (!drop_success)
{
Log::warn("track",
"Item at position (%f,%f,%f) can not be dropped",
@@ -2282,38 +2308,9 @@ void Track::itemCommand(const XMLNode *node)
#endif
}
// Don't tilt the items, since otherwise the rotation will look odd,
// i.e. the items will not rotate around the normal, but 'wobble'
// around.
//Vec3 normal(0.7071f, 0, 0.7071f);
Vec3 normal(0, 1, 0);
ItemManager::get()->newItem(type, loc, normal);
ItemManager::get()->newItem(type, drop ? hit_point : loc, normal);
} // itemCommand
// ----------------------------------------------------------------------------
/** Does a raycast from the given position, and if terrain was found
* adjust the Y position of the given vector to the actual terrain
* height. If no terrain is found, false is returned and the
* y position is not modified.
* \param pos Pointer to the position at which to determine the
* height. If terrain is found, its Y position will be
* set to the actual height.
* \return True if terrain was found and the height was adjusted.
*/
bool Track::setTerrainHeight(Vec3 *pos) const
{
Vec3 hit_point;
Vec3 normal;
const Material *m;
Vec3 to=*pos+Vec3(0,-10000,0);
if(m_track_mesh->castRay(*pos, to, &hit_point, &m, &normal))
{
pos->setY(hit_point.getY());
return true;
}
return false;
} // setTerrainHeight
// ----------------------------------------------------------------------------
std::vector< std::vector<float> > Track::buildHeightMap()
@@ -2368,15 +2365,14 @@ const core::vector3df& Track::getSunRotation()
bool Track::findGround(AbstractKart *kart)
{
btVector3 to(kart->getXYZ());
to.setY(-100000.f);
const Vec3 &xyz = kart->getXYZ();
Vec3 down = kart->getTrans().getBasis() * Vec3(0, -10000.0f, 0);
// Material and hit point are not needed;
const Material *m;
Vec3 hit_point, normal;
bool over_ground = m_track_mesh->castRay(kart->getXYZ(), to, &hit_point,
bool over_ground = m_track_mesh->castRay(xyz, down, &hit_point,
&m, &normal);
const Vec3 &xyz = kart->getXYZ();
if(!over_ground)
{
Log::warn("physics", "Kart at (%f %f %f) can not be dropped.",
@@ -2405,7 +2401,6 @@ bool Track::findGround(AbstractKart *kart)
return false;
}
btTransform t = kart->getBody()->getCenterOfMassTransform();
// The computer offset is slightly too large, it should take
// the default suspension rest instead of suspension rest (i.e. the
@@ -2413,9 +2408,21 @@ bool Track::findGround(AbstractKart *kart)
// it). On the other hand this initial bouncing looks nice imho
// - so I'll leave it in for now.
float offset = kart->getKartProperties()->getSuspensionRest();
t.setOrigin(hit_point+Vec3(0, offset, 0) );
t.setOrigin(hit_point + normal * offset);
kart->getBody()->setCenterOfMassTransform(t);
kart->setTrans(t);
return true;
} // findGround
//-----------------------------------------------------------------------------
float Track::getTrackLength() const
{
return DriveGraph::get()->getLapLength();
} // getTrackLength
//-----------------------------------------------------------------------------
float Track::getAngle(int n) const
{
return DriveGraph::get()->getAngleToNext(n, 0);
} // getAngle

Some files were not shown because too many files have changed in this diff Show More