Écriture d'une application avec gestion de la physique
Ref:
RaydiumTutoriels
Mise en place:
Notion de moteur physique:
Lors du développement d'un jeu vidéo, il arrive rapidement un moment où la facon de déplacer les différents objets dans la scène se pose. Pour reprendre l'exemple du tutoriel précédent (
TutorielDeplacerDesObjets), nous avons été capables de déplacer la voiture dans la scène, mais sans grand réalisme: accélérations instannées, possibilités de tourner dans toute situation, pas de collisions possibles (que ce soit avec un mur ou un autre objet ou joueur), ...
Bien sur, il serait possible de programmer ces comportements, mais c'est une tâche terriblement ardue, et il est très dur d'obtenir un résultat réaliste.
Grace à une intégration d'
ODE,
Raydium offre la possibilité au programmeur de l'application d'obtenir des applications "physiquement réalistes": Il suffit de déclarer quelques propriétés pour les différents objets de la scène (masse / poids, taille, ...), pour que leurs déplacements soient automatiquements calculés.
Vocabulaire:
Raydium utilise la notion d' élément. Les différents éléments sont rassemblés au sein d'entités plus larges que sont les objets. Il est possible de relier les différents éléments entre eux grâce à des joints.
Objets:
Les objets sont des containeurs pour les éléments. "voiture" est par exemple un objet composé de différents éléments tels que les roues, le chassis, ...
Créer un objet est une opération très simple:
int raydium_ode_object_create(char *name);
Cette fonction créé un objet (et stocke son nom), et retourne un identifiant vers cet objet, qui n'est qu'optionnel. Il sera ensuite possible de rajouter des éléments à cet objet, grace à son nom ou son identifiant. Détruire un objet détruit aussi tout les éléments qui le compose.
Eléments:
Les éléments sont la base de la représentation visuelle de la physique: ce sont eux qui sont déplacés et affichés. En conséquence, la création d'un élément demande plus d'informations que celle d'un objet:
int raydium_ode_object_sphere_add(char *name, int group, dReal mass,
dReal radius, char type, int tag, char *mesh);
int raydium_ode_object_box_add(char *name, int group, dReal mass,
dReal tx, dReal ty, dReal tz, char type, int tag, char *mesh);
- name: nom de l'élément
- group: identifiant de l'objet père de cet élément
- mass: poids de l'élément
- radius, tx, ty, tz: taille de l'objet (il est possible d'utiliser RAYDIUM_ODE_AUTODETECT pour que Raydium détermine seul cette information).
- type: RAYDIUM_ODE_STANDARD (il existe deux autres type d'éléments, non traités ici).
- tag: à utiliser comme vous le souhaitez.
- mesh: nom du fichier 3D qui représente l'objet (fichier .tri).
Il est bien sur possible d'effacer un élément à n'importe quel moment, ainsi que de le déplacer, de le pousser, et de régler beaucoup de paramètres plus ou moins avancés: friction, "type de matériau", viscosité, ...
Joints:
Les joints permettre de créer différents type de liaisons entre les éléments de la scène: attacher un bras à un torse, une roue au chassis, etc...
Raydium supporte différentes liaisons: rotule, charnière, suspension+arbre, et la construction de ces liens est spécifique à leur type: on ne construit pas un ensemble suspension + arbre de rotation de roue de la même manière qu'une charnière. Voici un exemple de fonction de construction de joint (type charnière ici):
int raydium_ode_joint_attach_hinge_name(char *name, char *elem1, char *elem2,
dReal posx, dReal posy, dReal posz, dReal axe1x, dReal axe1y, dReal axe1z);
Cette version utilise les noms des éléments à attacher (elem1 et elem2), et non leurs identifiants. Les arguments suivants définissent le point (x,y,z) d'attache, et l'axe (x,y,z) de la charnière.
De manière générale, il est possible de préciser une force maximum au delà de laquelle le joint cède.
Agir sur la scène:
Moteurs:
Raydium permet la création de moteurs (au sens large) pour agir sur la scène
- Moteur "à force": Ce type de moteur peut être utilisé pour motoriser une voiture. Il n'agit pas sur un élément, mais sur un joint. Pour reprendre l'exemple de la voiture, il est probable qu'un joint réunisse la roue et le chassis, et c'est ce joint qui sera motorisé. Les moteurs "à force" possèdent quelques paramètres (en particulier une force maximum), et supportent la notion de "boite de vitesse".
- Moteur angulaire: Ce type de moteur est très proche du précédent (attaché à un joint, gestion d'une force maximum, ...) mais recoit un ordre directionnel, et non une commande de force. Un exemple d'utilisation est le volant de notre voiture d'exemple: l'utilisateur (de la voiture) indique une rotation souhaitée, et les roues s'efforcent de suivre le mouvement.
- Réacteurs: Contrairement aux moteurs précédents, les réacteurs s'attachent à des éléments, et non à des joints. Un réacteur est placé à un endroit précis d'un élément, et est orienté. Un réacteur peut être utilisé comme tel (motorisation d'une fusée, par exemple) ou pour simuler l'action d'un rotor d'hélicoptère, ou tout autre mode de propulsion.
L'exemple de programme présent plus bas montre l'utilisation de plusieurs types de moteurs.
Forces:
Raydium dispose d'un certain nombre de fonction pour simuler différentes forces (pression de l'eau, vent, gravité déformée, ...). L'utilisation de ces fonction permet de répondre à des besoins avancés, qui ne sont solubles avec les autres outils cités plus haut.
Explosions:
Il existe deux types d'explosions, qui répondent à des besoins différents:
- Explosions avec souffle: Ce type d'explosion génère un souffle sphérique, dont la taille évolue avec le temps jusqu'a atteindre une taille critique. N'importe quel élément situé sur la course de ce souffle est expulsé. Ce type de souffle est généralement utilisé pour simuler des explosions de taille très conséquentes.
- Explosions instantanées (souffle dégressif): Ce type d'explosions applique une force (aux éléments alentours) calculée pour chaque élément en fonction de la distance qui sépare l'élément et le centre de l'explosion. Cette variante est intéressante pour des explosions d'ampleur plus faible, offrant une précision plus importante que les explosions avec souffle, et permettant plus de ... discernement :)
Mise en oeuvre: construction d'une voiture:
create_car(), avec commentaires :
Pour commencer, déclarons une variable entière, qui contiendra l'identifiant de l'objet "voiture" que nous allons créer.
Ces quelques constantes déterminent respectivement la force maximum au delà de laquelle les essieux céderont, la "friction à l'air" des roues, et les paramètres des suspensions.
#define BREAK_FORCE 130
#define ROTFRICTION 0.0005
#define ERP_CFM 0.3,0.8
Ensuite, on efface un éventuel objet qui porte déjà le nom "WATURE" (cette fonction va en général générer une erreur non fatale). Il ne reste plus ensuite qu'a créer notre objet "WATURE", et a affecter le retour de la fonction à la variable "a".
raydium_ode_object_delete_name("WATURE");
a=raydium_ode_object_create("WATURE");
L'objet est créé, il est maintenant possible d'y ajouter les différents éléments qui vont le composer: une boite (corps de la voiture) et 4 sphères (roues). Chacune des roues se verra attachée au corps par un "hinge2". Le hinge2 est une liaison "suspension" qui offre 2 axes de rotation : l'un pour tourner la roue à droite ou à gauche (axe 0), et l'autre pour permettre à la roue de tourner autour de l'essieu (axe 1), ajouté à un degré de liberté en translation le long du premier axe, qui va jouer le rôle de suspension. Même si l'expliquation peut sembler complexe, c'est une opération simple, encapsulée dans un seul appel de fonction.
Dans un premier temps, il faut donc créer la boite "corps" : (ne tenez pas compte de l'instruction suivante)
raydium_ode_object_box_add("corps",a,1,1.2,0.6,0.4,RAYDIUM_ODE_STANDARD,0,"clio.tri");
raydium_ode_element_slip_name("corps",RAYDIUM_ODE_SLIP_ICE);
Ensuite, il est possible de créer une première roue (avant gauche) :
raydium_ode_object_sphere_add("pneu_ag",a,0.5,RAYDIUM_ODE_AUTODETECT,RAYDIUM_ODE_STANDARD,0,"roue5.tri");
raydium_ode_element_rotfriction_name("pneu_ag",ROTFRICTION);
... et de la placer correctement par rapport à la caisse, qui est centrée en 0,0,0 :
raydium_ode_element_move_name_3f("pneu_ag",0.42,0.253,-0.180);
Il ne reste plus qu'a construire et pramètrer la suspension "hinge2" :
raydium_ode_joint_attach_hinge2_name("suspet_ag","corps","pneu_ag",RAYDIUM_ODE_JOINT_SUSP_DEFAULT_AXES);
raydium_ode_joint_break_force_name("suspet_ag",BREAK_FORCE);
raydium_ode_joint_suspension_name("suspet_ag",ERP_CFM);
Il reste à répéter l'opération pour les 3 autres roues :
raydium_ode_object_sphere_add("pneu_ad",a,0.5,RAYDIUM_ODE_AUTODETECT,RAYDIUM_ODE_STANDARD,0,"roue5.tri");
raydium_ode_element_rotfriction_name("pneu_ad",ROTFRICTION);
raydium_ode_element_move_name_3f("pneu_ad",0.42,-0.253,-0.180);
raydium_ode_joint_attach_hinge2_name("suspet_ad","corps","pneu_ad",RAYDIUM_ODE_JOINT_SUSP_DEFAULT_AXES);
raydium_ode_joint_break_force_name("suspet_ad",BREAK_FORCE);
raydium_ode_joint_suspension_name("suspet_ad",ERP_CFM);
raydium_ode_object_sphere_add("pneu_rg",a,0.5,RAYDIUM_ODE_AUTODETECT,RAYDIUM_ODE_STANDARD,0,"roue5.tri");
raydium_ode_element_rotfriction_name("pneu_rg",ROTFRICTION);
raydium_ode_element_move_name_3f("pneu_rg",-0.444,0.253,-0.180);
raydium_ode_joint_attach_hinge2_name("suspet_rg","corps","pneu_rg",RAYDIUM_ODE_JOINT_SUSP_DEFAULT_AXES);
raydium_ode_joint_hinge2_block_name("suspet_rg",1);
raydium_ode_joint_break_force_name("suspet_rg",BREAK_FORCE);
raydium_ode_joint_suspension_name("suspet_rg",ERP_CFM);
raydium_ode_object_sphere_add("pneu_rd",a,0.5,RAYDIUM_ODE_AUTODETECT,RAYDIUM_ODE_STANDARD,0,"roue5.tri");
raydium_ode_element_rotfriction_name("pneu_rd",ROTFRICTION);
raydium_ode_element_move_name_3f("pneu_rd",-0.444,-0.253,-0.180);
raydium_ode_joint_attach_hinge2_name("suspet_rd","corps","pneu_rd",RAYDIUM_ODE_JOINT_SUSP_DEFAULT_AXES);
raydium_ode_joint_hinge2_block_name("suspet_rd",1);
raydium_ode_joint_break_force_name("suspet_rd",BREAK_FORCE);
raydium_ode_joint_suspension_name("suspet_rd",ERP_CFM);
... et la voiture est construite ! Pour pouvoir conduire cette voiture, nous allons mettre en place 2 entités de contrôle: l'une pour permettre à la voiture d'avancer, et la seconde pour la diriger :
Moteur de la voiture (relié à l'axe 1 des deux liaisons avant) :
raydium_ode_motor_create("moteur",a,RAYDIUM_ODE_MOTOR_ENGINE);
raydium_ode_motor_attach_name("moteur","suspet_ag",1);
raydium_ode_motor_attach_name("moteur","suspet_ad",1);
raydium_ode_motor_power_max_name("moteur",0.2);
"Volant", reliè à l'axe 0 de ces même liaisons :
raydium_ode_motor_create("direction",a,RAYDIUM_ODE_MOTOR_ANGULAR);
raydium_ode_motor_attach_name("direction","suspet_ag",0);
raydium_ode_motor_attach_name("direction","suspet_ad",0);
raydium_ode_motor_power_max_name("direction",0.2);
La force maximum de ces moteurs est fixée à 0.2 unités, valeur déterminée après différents tests de conduite. La voiture est maintenant prête à rouler, nous avons juste à donner des ordres aux 2 moteurs et à placer la caméra au bon endroit à l'intérieur pour vivre de grands frissons ;)
code source complet : (les parties importantes sont données en gras)
#include "raydium/index.c"
GLfloat sun[]={1.0,0.9,0.5,1.0};
void create_car(void)
{
int a;
#define BREAK_FORCE 130
#define ROTFRICTION 0.0005
#define ERP_CFM 0.3,0.8
raydium_ode_object_delete_name("WATURE");
a=raydium_ode_object_create("WATURE");
raydium_ode_object_box_add("corps",a,1,1.2,0.6,0.4,RAYDIUM_ODE_STANDARD,0,"clio.tri");
raydium_ode_element_slip_name("corps",RAYDIUM_ODE_SLIP_ICE);
raydium_ode_object_sphere_add("pneu_ag",a,0.5,RAYDIUM_ODE_AUTODETECT,RAYDIUM_ODE_STANDARD,0,"roue5.tri");
raydium_ode_element_rotfriction_name("pneu_ag",ROTFRICTION);
raydium_ode_element_move_name_3f("pneu_ag",0.42,0.253,-0.180);
raydium_ode_joint_attach_hinge2_name("suspet_ag","corps","pneu_ag",RAYDIUM_ODE_JOINT_SUSP_DEFAULT_AXES);
raydium_ode_joint_break_force_name("suspet_ag",BREAK_FORCE);
raydium_ode_joint_suspension_name("suspet_ag",ERP_CFM);
raydium_ode_object_sphere_add("pneu_ad",a,0.5,RAYDIUM_ODE_AUTODETECT,RAYDIUM_ODE_STANDARD,0,"roue5.tri");
raydium_ode_element_rotfriction_name("pneu_ad",ROTFRICTION);
raydium_ode_element_move_name_3f("pneu_ad",0.42,-0.253,-0.180);
raydium_ode_joint_attach_hinge2_name("suspet_ad","corps","pneu_ad",RAYDIUM_ODE_JOINT_SUSP_DEFAULT_AXES);
raydium_ode_joint_break_force_name("suspet_ad",BREAK_FORCE);
raydium_ode_joint_suspension_name("suspet_ad",ERP_CFM);
raydium_ode_object_sphere_add("pneu_rg",a,0.5,RAYDIUM_ODE_AUTODETECT,RAYDIUM_ODE_STANDARD,0,"roue5.tri");
raydium_ode_element_rotfriction_name("pneu_rg",ROTFRICTION);
raydium_ode_element_move_name_3f("pneu_rg",-0.444,0.253,-0.180);
raydium_ode_joint_attach_hinge2_name("suspet_rg","corps","pneu_rg",RAYDIUM_ODE_JOINT_SUSP_DEFAULT_AXES);
raydium_ode_joint_hinge2_block_name("suspet_rg",1);
raydium_ode_joint_break_force_name("suspet_rg",BREAK_FORCE);
raydium_ode_joint_suspension_name("suspet_rg",ERP_CFM);
raydium_ode_object_sphere_add("pneu_rd",a,0.5,RAYDIUM_ODE_AUTODETECT,RAYDIUM_ODE_STANDARD,0,"roue5.tri");
raydium_ode_element_rotfriction_name("pneu_rd",ROTFRICTION);
raydium_ode_element_move_name_3f("pneu_rd",-0.444,-0.253,-0.180);
raydium_ode_joint_attach_hinge2_name("suspet_rd","corps","pneu_rd",RAYDIUM_ODE_JOINT_SUSP_DEFAULT_AXES);
raydium_ode_joint_hinge2_block_name("suspet_rd",1);
raydium_ode_joint_break_force_name("suspet_rd",BREAK_FORCE);
raydium_ode_joint_suspension_name("suspet_rd",ERP_CFM);
raydium_ode_motor_create("moteur",a,RAYDIUM_ODE_MOTOR_ENGINE);
raydium_ode_motor_attach_name("moteur","suspet_ag",1);
raydium_ode_motor_attach_name("moteur","suspet_ad",1);
raydium_ode_motor_power_max_name("moteur",0.2);
raydium_ode_motor_create("direction",a,RAYDIUM_ODE_MOTOR_ANGULAR);
raydium_ode_motor_attach_name("direction","suspet_ag",0);
raydium_ode_motor_attach_name("direction","suspet_ad",0);
raydium_ode_motor_power_max_name("direction",0.2);
}
void display(void)
{
float speed,direct;
raydium_joy_key_emul();
direct=raydium_joy_x*0.3;
speed=raydium_joy_y*55;
raydium_ode_motor_speed_name("moteur",-speed);
raydium_ode_motor_angle_name("direction",direct);
if(raydium_key_last==1027) exit(0);
raydium_clear_frame();
raydium_ode_element_camera_inboard_name("corps",0.2,0,0.1,2,0,0);
raydium_ode_draw_all(0);
if(raydium_key[GLUT_KEY_F1]) raydium_ode_draw_all(1);
raydium_rendering_finish();
}
int main(int argc, char **argv)
{
raydium_init_args(argc,argv);
raydium_window_create(640,480,RAYDIUM_RENDERING_WINDOW,"Physics test");
raydium_texture_filter_change(RAYDIUM_TEXTURE_FILTER_TRILINEAR);
raydium_projection_near=0.01;
raydium_projection_far=1000;
raydium_projection_fov=60;
raydium_window_view_update();
raydium_light_on(0);
memcpy(raydium_light_color[0],sun,raydium_internal_size_vector_float_4);
raydium_light_intensity[0]=1000000;
raydium_light_position[0][0]=50;
raydium_light_position[0][1]=150;
raydium_light_position[0][2]=200;
raydium_light_update_all(0);
raydium_background_color_change(sun[0],sun[1],sun[2],sun[3]);
raydium_fog_disable();
raydium_ode_ground_set_name("cocorobix.tri");
create_car();
raydium_callback(&display);
return 0;
}
Ce tutoriel ne montre qu'une toute petite partie de ce qu'il est possible de faire avec
RayODE. Vous pouvez tenter de rajouter une boite de vitesses, des explosions, d'autres vues, un joueur extérieur à la voiture, etc. N'hésitez pas à consulter la documentation, et surtout les applications Raydium telles que "test6.c". Un autre tutoriel dédié à l'aspect réseau est disponible, et un autre s'intéresse a la gestion du son (avec
RayODE en particulier).
Tutoriel 4:
TutorielUtiliserPhp