#include <irrlicht.h>
#include <cmath>
using namespace irr;
/**
* Stocke une liste de points et calcule un chemin fluide par interpolation de Hermite
*/
class Path3D
{
public:
/*
Hermite interpolation
Tension: 1 is high, 0 normal, -1 is low
Bias: 0 is even,
positive is towards first segment,
negative towards the other
Source:
http://local.wasp.uwa.edu.au/~pbourke/miscellaneous/interpolation/
*/
Path3D(double tension = 0, double bias = 0) :
m_tension(tension), m_bias(bias)
{
}
void push(const core::vector3df& v)
{
m_array.push_back(v);
}
int size() const
{
return m_array.size();
}
/**
* mu : Partie entière = n° intervalle, partie décimale = pourcentage parcouru dans l'intervalle
*/
core::vector3df getPoint(double mu) const
{
core::vector3df v1, v2, v3, v4;
int index;
double mu_interval, intpart;
mu_interval = modf(mu, &intpart);
index = (int)intpart;
return getPoint(index, mu_interval);
}
/**
* index : n° intervalle
* mu : pourcentage parcouru dans l'intervalle
*/
core::vector3df getPoint(int index, double mu) const
{
core::vector3df v1, v2, v3, v4;
v1 = getVectorById(index - 1);
v2 = getVectorById(index);
v3 = getVectorById(index + 1);
v4 = getVectorById(index + 2);
return HermiteInterpolateVector(v1, v2, v3, v4, mu);
}
private:
core::vector3df getVectorById(int id) const
{
if (m_array.empty())
return core::vector3df(0, 0, 0);
else
return m_array[id % m_array.size()];
}
core::vector3df HermiteInterpolateVector(
const core::vector3df& v1,
const core::vector3df& v2,
const core::vector3df& v3,
const core::vector3df& v4,
double mu) const
{
core::vector3df w;
w.X = HermiteInterpolate(v1.X, v2.X, v3.X, v4.X, mu);
w.Y = HermiteInterpolate(v1.Y, v2.Y, v3.Y, v4.Y, mu);
w.Z = HermiteInterpolate(v1.Z, v2.Z, v3.Z, v4.Z, mu);
return w;
}
double HermiteInterpolate(double y0, double y1, double y2, double y3, double mu) const
{
double m0, m1, mu2, mu3;
double a0, a1, a2, a3;
mu2 = mu * mu;
mu3 = mu2 * mu;
m0 = (y1 - y0)*(1 + m_bias)*(1 - m_tension)/2;
m0 += (y2 - y1)*(1 - m_bias)*(1 - m_tension)/2;
m1 = (y2 - y1)*(1 + m_bias)*(1 - m_tension)/2;
m1 += (y3 - y2)*(1 - m_bias)*(1 - m_tension)/2;
a0 = 2*mu3 - 3*mu2 + 1;
a1 = mu3 - 2*mu2 + mu;
a2 = mu3 - mu2;
a3 = -2*mu3 + 3*mu2;
return (a0*y1 + a1*m0 + a2*m1 + a3*y2);
}
private:
double m_tension;
double m_bias;
core::array<core::vector3df> m_array;
};
/**
* Fabrique un tube à partir d'une liste de points (Path3D) en les reliant avec des virages arrondis
* path : L'objet Path3D contenant la liste des points
* radius : Le rayon du tube
* pathDivide : Le nombre de sections entre 2 points (minimum: 1, un grand nombre augmente le détail)
* ringDivide : Le nombre de côtés d'une section du tube (minimum: 8, un grand nombre augmente le détail)
*/
class TubeSceneNode : public scene::ISceneNode
{
public:
TubeSceneNode(scene::ISceneNode *parent, scene::ISceneManager *mgr, s32 id, const Path3D& path, int radius, int pathDivide = 16, int ringDivide = 16)
: scene::ISceneNode(parent, mgr, id)
{
core::vector3df u, v, A, B, dir, pt;
m_material.Wireframe = false;
m_material.Lighting = false;
m_material.BackfaceCulling = false;
// Calcule le nombre de cercles composant le tube
m_ringDivide = ringDivide;
m_verticesCount = path.size() * pathDivide * m_ringDivide;
m_vertices = new video::S3DVertex[m_verticesCount];
for (int i = 0; i < path.size(); i++)
{
for (int j = 0; j < pathDivide; j++)
{
// Calcule le vecteur directeur entre 2 points distincts
A = path.getPoint(f64(i) + ( f64(j) / f64(pathDivide) ));
B = path.getPoint(f64(i) + ( f64(j + 1) / f64(pathDivide) ));
dir = (B - A).normalize();
// Construit une nouvelle base (dir, u, v)
u = (core::vector3df(dir.Y, -dir.X, 0)).normalize();
v = (dir.crossProduct(u)).normalize();
int indexRef = i * pathDivide * m_ringDivide + j * m_ringDivide;
for (int k = 0; k < m_ringDivide; k++)
{
// Définit les points d'un cercle entourant le vecteur directeur, qui fera partie du tube matériel
double teta = k * 2.0 * M_PI / m_ringDivide;
pt = A + ( radius * cos(teta) ) * u + ( radius * sin(teta) ) * v;
m_vertices[indexRef + k] = video::S3DVertex(pt, core::vector3df(0, 0, 0), video::SColor(128, 128, 255, 255), core::vector2d< f32 >(0, 0));
}
}
}
}
virtual void OnRegisterSceneNode()
{
if (IsVisible)
SceneManager->registerNodeForRendering(this);
ISceneNode::OnRegisterSceneNode();
}
virtual void render()
{
u16 indices[6*m_verticesCount];
video::IVideoDriver* driver;
// Relie la moitié des triangles
for (int i = 0; i < m_verticesCount; i++)
{
indices[3*i] = i % m_verticesCount;
indices[3*i + 1] = ( i + 1 ) % m_verticesCount;
indices[3*i + 2] = ( i + m_ringDivide ) % m_verticesCount;
}
// Relie l'autre moitié
int j = m_verticesCount * 3;
for (int i = 0; i < m_verticesCount; i++)
{
indices[j + 3*i] = ( i + 1 ) % m_verticesCount;
indices[j + 3*i + 1] = ( i + m_ringDivide ) % m_verticesCount;
indices[j + 3*i + 2] = ( i + m_ringDivide + 1 ) % m_verticesCount;
}
driver = SceneManager->getVideoDriver();
driver->setMaterial(m_material);
driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
driver->drawIndexedTriangleList(m_vertices, m_verticesCount, &indices[0], m_verticesCount*2);
}
virtual const core::aabbox3d<f32>& getBoundingBox() const
{
return m_box;
}
virtual u32 getMaterialCount() const
{
return 1;
}
virtual video::SMaterial& getMaterial(u32 i)
{
return m_material;
}
private:
core::aabbox3d<irr::f32> m_box;
video::SMaterial m_material;
video::S3DVertex *m_vertices;
int m_verticesCount;
int m_pathDivide;
int m_ringDivide;
};
/**
* Dessine seulement des traits des traits qui suivent le tunnel (pour débug)
*/
void drawTubeLines(video::IVideoDriver *driver, const Path3D &path, int precision)
{
core::vector3df w1, w2;
core::vector3df u, v, w;
for (int i = 0; i < path.size(); i++)
{
for (int j = 0; j <= precision - 1; j++)
{
w1 = path.getPoint(i, f64(j) / f64(precision));
w2 = path.getPoint(i, f64(j + 1) / f64(precision));
driver->draw3DLine(w1, w2, video::SColor(0, 255*( f64(j) / f64(precision) ), 255*( f64(j) / f64(precision) ), 0 ));
// Nouvelle base
u = (w2 - w1).normalize();
v = (core::vector3df(u.Y, -u.X, 0)).normalize();
w = (u.crossProduct(v)).normalize();
// Repère local (u, v, w)
driver->draw3DLine(w1, w1 + 40 * u, video::SColor(0, 128, 0, 0 ));
driver->draw3DLine(w1, w1 + 40 * v, video::SColor(0, 0, 128, 0 ));
driver->draw3DLine(w1, w1 + 40 * w, video::SColor(0, 0, 0, 128 ));
// Dessine un cercle
int radius = 80;
int circleDetail = 16;
for (int k = 0; k < circleDetail; k++)
{
double teta = k * 2 * M_PI / circleDetail;
double teta2 = (k + 1) * 2 * M_PI / circleDetail;
core::vector3df u1, u2;
u1 = w1 + radius * cos(teta) * v + radius * sin(teta) * w;
u2 = w1 + radius * cos(teta2) * v + radius * sin(teta2) * w;
driver->draw3DLine(u1, u2, video::SColor(0, 0, 128, 128 ));
}
}
}
}
int main(void)
{
IrrlichtDevice *device;
video::IVideoDriver *driver;
scene::ISceneManager *sceneManager;
gui::IGUIEnvironment *gui;
scene::ICameraSceneNode *camera;
gui::IGUIStaticText *coordText;
Path3D path;
device = createDevice(video::EDT_OPENGL, core::dimension2d<u32>(800, 600), 32, false, false, false, 0);
driver = device->getVideoDriver();
sceneManager = device->getSceneManager();
gui = device->getGUIEnvironment();
camera = sceneManager->addCameraSceneNodeFPS();
// Pour afficher les coordonnées
coordText = gui->addStaticText(L"Coordonnees", core::rect<s32>(10, 10, 100, 45), true, true, 0, -1, true);
// Le chemin suivi par le tube
path.push(core::vector3df(80, 80, 80));
path.push(core::vector3df(500, 200, 200));
path.push(core::vector3df(500, 1000, 0));
path.push(core::vector3df(200, 1000, 600));
// Création du tube
TubeSceneNode *tube = new TubeSceneNode(sceneManager->getRootSceneNode(), sceneManager, 666, path, 80);
tube->drop();
// Pour affichage des coordonnées
core::vector3df posCam;
wchar_t buffer[128];
// Pour le dessin des lignes servant pour le débug
video::SMaterial material;
material.Lighting = false;
while (device->run())
{
driver->beginScene(true, true, video::SColor(255, 255, 255, 255));
sceneManager->drawAll();
gui->drawAll();
driver->setMaterial(material);
// Dessin d'un repère orthonormé (x, y, z)
driver->draw3DLine(core::vector3df(0, 0, 0), core::vector3df(100, 0, 0), video::SColor(0, 255, 0, 0));
driver->draw3DLine(core::vector3df(0, 0, 0), core::vector3df(0, 100, 0), video::SColor(0, 0, 255, 0));
driver->draw3DLine(core::vector3df(0, 0, 0), core::vector3df(0, 0, 100), video::SColor(0, 0, 0, 255));
// Débug (commenter pour ne pas afficher les lignes de débug)
drawTubeLines(driver, path, 16);
driver->endScene();
// Actualise les coordonnées et les affiche
posCam = camera->getPosition();
swprintf(buffer, 128, L"X : %f\
Y : %f\
Z : %f", posCam.X, posCam.Y, posCam.Z);
coordText->setText(buffer);
}
device->drop();
return 0;
}