Utiliser un système automatique de rez qui s'occupe de tout (ou presque) ? [[Mise à jour 19/12/09]]
Ce mini tuto s'adresse à ceux qui souhaitent avoir un système rez extrêmement simplifié pour ne pas avoir à créer 10 fois la même chose dans des projets de script.
Qu'est-ce qui est simplifié ? (explications juste après)
- [1] La synchronisation de l'envoi de données entre le rezzer et l'objet rezzé ;
- [2] Le rez de plusieurs objets à la suite ainsi que la multi-synchronisation de données ( [1] ) ;
- [3] La vérification si le rez est possible à cette position ;
- [4] L'utilisation d'un proxy.
- [5] La distance de rez de plus de 10 mètres est gérée automatiquement
[1] - La synchronisation des données
Cela concerne la nécessité de rez un objet par script ainsi que lui transmettre des données par le biais d'une des fonctions Say (llWhisper, llSay, llShout, llRegionSay) de façon automatique. Comment savoir quand l'objet est rez et opérationnel (même dans des sims ultra laggy) pour ne pas lui transmettre les données trop tôt ? Comment faire fonctionner cette transmission sur n'importe quel canal de façon à être plus "sécurisée" ? On verra par la suite que ces tâches deviennent très simples.
[2] - Le rez de plusieurs objets à la suite + synchronisation de données
Même chose que le [1] mais cette fois avec un rez de plusieurs objets à la suite tout en restant avec une synchronisation parfaite.
[3] - Vérifier si le rez est possible
Rez un objet et lui envoyer des données de façon synchronisée peut déjà s'avérer assez lourd et répétitif pour un débutant, mais pour vérifier ensuite si un objet peut être rez à la position (parcel no build, offworld, au dessus de 4096 mètres) c'est encore pire. Le système va également s'occuper de ça et dans certain cas utiliser un rez à distance ( voir le [4] ).
[4] - Le proxy (ou rez à distance)
Il serait dommage de ne pas y ajouter une fonction utile comme celle du rez à distance ( ou proxy). Si vous êtes au dessus de 4096 mètres ou dans une parcel qui n'autorise pas le build, comment faire pour rez un objet par script ? Nous verrons par la suite que c'est tout à fait possible.
[5] - La distance de rez
La fonction llRezObject possède une limite bien contraignante, on ne peut pas rez plus loin que
10 mètres. Si une position qui est trop loin est donnée en paramètre à la fonction, le rez ne fonctionnera pas. Le système va s'occuper de régler ce problème automatiquement en réduisant la distance si elle dépasse cette limite pour éviter une erreur inutile.
Première étape : Le script principal (Rezzer) :
// !! NOTE !! Le script a été mis à jour le 19/12/09 et fonctionne un peu différemment de l'ancienne version.
//----------------------------------------------------------------------------------
// LIBRARY FUNCTIONS
//----------------------------------------------------------------------------------
// -- Fonction de "correction" de vecteurs de position pour ne pas aller offworld ou au dessus de 4096m --
// @ param [vector] position à corriger
// @ return [vector] retourne le vecteur corrigé
vector CorrectVectorPos( vector p ) {
if( p.x > 256.0 ) p.x = 256.0;
if( p.x < 0.0 ) p.x = 0.0;
if( p.y > 256.0 ) p.y = 256.0;
if( p.y < 0.0 ) p.y = 0.0;
if( p.z > 4096.0 ) p.z = 4096.0;
return p;
}
// -- Fonction permettant de vérifier si l'objet est autorisé à rez à cette position --
// @ param [vector] position à vérifier
// @ return [bool] retourne true si il est autorisé, false dans le cas contraire
integer ParcelCheck( vector p ) {
integer flags = llGetParcelFlags( p );
if( flags & PARCEL_FLAG_ALLOW_CREATE_OBJECTS ||
(flags & PARCEL_FLAG_ALLOW_CREATE_GROUP_OBJECTS &&
llList2Key(llGetObjectDetails(llGetKey(), [OBJECT_GROUP]), 0) == llList2Key(llGetParcelDetails(p, [PARCEL_DETAILS_GROUP]), 0)) )
return TRUE;
return FALSE;
}
// -- Fonction qui génere un canal au hasard (négatif) --
// @ return [integer] retourne le canal
integer GetRandomChannel() {
return ~(integer)llFrand( (float)DEBUG_CHANNEL );
}
//----------------------------------------------------------------------------------
// SCRIPT FUNCTIONS
//----------------------------------------------------------------------------------
// -- Fonction llRezObject protégée qui vérifie si le rez est possible et envoie des données à l'objet
// de façon automatique --
// @ param [string] nom de l'objet dans l'inventaire
// @ param [vector] position du rez
// @ param [vector] vélocité de l'objet rezzé
// @ param [rotation] rotation de l'objet
// @ param [integer] canal de l'objet pour transmettre les données
// @ param [integer] type de rez, position locale/rotation locale ou non
// @ param [string] les données à envoyer
SafeRezObject( string name, vector pos, vector vel, rotation rot, integer chan, integer rezType, string data ) {
// Est-ce que l'objet est bien un objet et présent dans l'inventaire ? si non, on envoie une erreur au owner.
if( llGetInventoryType(name) != INVENTORY_OBJECT ) {
llOwnerSay( "Erreur :: " + name + " n'est pas un objet ou n'est pas présent dans l'inventaire." );
return;
}
// on stock temporairement la pos/rot de l'objet/avatar
vector myPos = llGetPos();
rotation myRot = llGetRot();
vector rezPos = myPos + pos;
// on calcule le type de rez demandé
if( rezType & POS_TYPE_REGION )
rezPos = pos;
else if( rezType & POS_TYPE_LOCAL_ROTATED )
rezPos = myPos + pos * myRot;
if( rezType & ROT_TYPE_LOCAL )
rot *= myRot;
// si la distance de rez est plus de 10 mètres, on la réduit au max possible
// pour éviter une erreur de rez silencieuse
if( llVecDist(myPos, rezPos) > 10.0 )
rezPos = myPos + llVecNorm(rezPos-myPos) * 10.0;
// Si il y a possibilité de rez à la position
if( CorrectVectorPos(rezPos) == rezPos && ParcelCheck(rezPos) ) {
// Si le canal n'est pas 0 et que les données à envoyer ne sont pas vides, on crée une connexion
if( chan != 0 && data != "" ) {
if( !CreateConnection(chan, data) )
return;
}
llRezObject( name, rezPos, vel, rot, chan );
// Sinon on vérifie la présence d'un proxy dans la région
} else if( IsProxyAvailable() ) {
// Si le canal n'est pas 0 et que les données à envoyer ne sont pas vides, on crée une connexion
if( chan != 0 && data != "" ) {
if( !CreateConnection(chan, data) )
return;
}
llGiveInventory( gProxyKey, name );
llSleep( 0.2 );
llRegionSay( gProxyChan, name + PRIM_SEP + (string)vel + PRIM_SEP + (string)rot + PRIM_SEP + (string)chan );
}
}
// -- Vérifie si un proxy est disponible --
// @ return [bool] true : disponible, false : non disponible
integer IsProxyAvailable() {
return ( PROXY_ACTIVE && llKey2Name(gProxyKey) != "" );
}
// -- Crée une connexion et stock les données dans une mémoire tampon en attendant le rez --
// @ param [integer] canal du transfert
// @ param [string] les données à envoyer
// @ return [bool] true : la connexion est établie, false : echec de la création d'une connexion
integer CreateConnection( integer chan, string data ) {
if( (gListenChan != []) > 64 )
return FALSE;
gListenData += data;
gListenChan += chan;
gListenID += llListen( chan, "", NULL_KEY, "get" );
return TRUE;
}
// -- Envoie les données mises en mémoire sur le canal concerné --
// @ param [integer] le canal du transfert
FlushData( integer chan ) {
integer fd = llListFindList( gListenChan, [chan] );
// si le canal possède bien des données à envoyer
if( ~fd ) {
llListenRemove( llList2Integer(gListenID, fd) );
llRegionSay( chan, llList2String(gListenData, fd) );
gListenID = llDeleteSubList( gListenID, fd, fd );
gListenChan = llDeleteSubList( gListenChan, fd, fd );
gListenData = llDeleteSubList( gListenData, fd, fd );
}
}
//------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------
// CONSTANTS
//--------------------------------------------------------------------------------------
//----------------------------------------------
// INTERNAL COMM
//----------------------------------------------
//----------------
// ME
//----------------
integer IC_REZ_REQUEST = -1621184001;
integer IC_REZ_CONNECTION = -250865153;
integer IC_REZ_FLUSH = -605179777;
integer IC_REZ_CLEAR = -2105587073;
//----------------
//SCRIPT ADDRESSES
//----------------
//----------------
// GLOBAL
//----------------
integer IC_GLOBAL_REZ_RETCONNECTION = -1204281601;
//----------------------------------------------
// PARAMETERS
//----------------------------------------------
// activer ou désactiver la fonction du proxy.
integer PROXY_ACTIVE = FALSE;
// le délais de rafraichissement de timer de vérification
float PROXY_REFRESH = 5.0;
// le nom du proxy dans l'inventaire
string PROXY_NAME = "MyProxy";
// le temps de vie du proxy
float PROXY_LIFE = 600.0; // 10 minutes
// rez d'un autre proxy X secondes avant que l'ancien se supprime
float PROXY_TIME_REZ = 20.0;
// offset de rez du proxy
vector PROXY_REZ_OFFSET = <0.0, 0.0, 2.0>;
//----------------------------------------------
// REZ TYPE
//----------------------------------------------
// rez par position relative à la région
integer POS_TYPE_REGION = 0x01;
// rez par position locale selon la rotation de l'objet/l'avatar
integer POS_TYPE_LOCAL_ROTATED = 0x02;
// rez par rotation locale
integer ROT_TYPE_LOCAL = 0x04;
//----------------------------------------------
// UTIL
//----------------------------------------------
string PRIM_SEP = "|";
string SUB_SEP = ":";
//--------------------------------------------------------------------------------------
// GLOBAL VARIABLES | State : Default
//--------------------------------------------------------------------------------------
//----------------------------------------------
// MEMORY
//----------------------------------------------
list gListenData;
list gListenChan;
list gListenID;
//----------------------------------------------
// PROXY
//----------------------------------------------
key gProxyKey;
integer gProxyChan;
//----------------------------------------------
// OTHERS
//----------------------------------------------
key gOwnerKey;
default
{
on_rez( integer r ) {
state clearconnections;
}
state_entry() {
gOwnerKey = llGetOwner();
if( PROXY_ACTIVE )
llSetTimerEvent( 0.1 );
}
changed( integer c ) {
if( c & CHANGED_REGION )
state clearconnections;
}
link_message( integer link, integer address, string data, key data2 ) {
// Si l'adresse correspond à une requête de rez, on reforme le packet pour l'envoyer à la fonction
if( address == IC_REZ_REQUEST ) {
list packet = llParseString2List( data, [PRIM_SEP], [] );
SafeRezObject(
llList2String(packet, 0), // name
(vector)llList2String(packet, 1), // pos
(vector)llList2String(packet, 2), // vel
(rotation)llList2String(packet, 3), // rot
(integer)llList2String(packet, 4), // chan
(integer)llList2String(packet, 5), // rezType
(string)data2 // data
);
}
// Si l'adresse est une demande de création de connexion
else if( address == IC_REZ_CONNECTION )
llMessageLinked( LINK_SET, IC_GLOBAL_REZ_RETCONNECTION, (string)data2, (string)CreateConnection((integer)data, (string)data2) );
// Si l'adresse correspond à une requête d'envoi de données
else if( address == IC_REZ_FLUSH )
FlushData( (integer)data );
// Demande de clearconnections
else if( address == IC_REZ_CLEAR )
state clearconnections;
}
listen( integer chan, string name, key id, string msg ) {
if( llGetOwnerKey(id) == gOwnerKey ) {
FlushData( chan );
if( name == PROXY_NAME )
gProxyKey = id;
}
}
timer() {
// si aucun proxy détecté ou en fin de vie, on en rez un autre
if( llKey2Name(gProxyKey) == "" || (llGetTime() + PROXY_TIME_REZ) >= PROXY_LIFE ) {
gProxyChan = GetRandomChannel();
SafeRezObject( PROXY_NAME, PROXY_REZ_OFFSET, ZERO_VECTOR, ZERO_ROTATION, gProxyChan, 0, (string)PROXY_LIFE );
llResetTime();
}
llSetTimerEvent( PROXY_REFRESH );
}
}
// State permettant de désactiver tous les listens du script rapidement
// ainsi qu'éffacer la mémoire.
state clearconnections
{
state_entry() {
gListenData = [];
gListenChan = [];
gListenID = [];
state default;
}
}
J'ai pas mal commenté les fonctions afin de comprendre le fonctionnement au minimum. Pour commencer, créez un objet puis un script à l'intérieur que vous appellerez par exemple "Rezzer", copiez/collez le code ci-dessus dans votre script et enregistrez-le.
Deuxième étape : Comprendre avec un exemple simple
On va commencer avec un exemple simple, celui de rez un objet sans envoyer de données en utilisant notre système. Pour ce faire , il y a 2 solutions que le script propose :
- Utiliser sa fonction SafeRezObject en modifiant le script
- Créer un second script et utiliser la fonction SafeRezObject "à distance"
Je choisis la seconde solution pour ce tuto qui me parait être la plus simple à comprendre. On va donc créer un second script vide dans notre objet que vous appellerez "Testeur" et utiliser la fonction SafeRezObject à distance. Pour ce faire, copiez/collez ce code dans votre second script :
SafeRezObject( string name, vector pos, vector vel, rotation rot, integer chan, integer rezType, string data ) {
llMessageLinked( LINK_SET, -1621184001,
name + "|" +
(string)pos + "|" +
(string)vel + "|" +
(string)rot + "|" +
(string)chan + "|" +
(string)rezType, data
);
}
default
{
state_entry()
{
}
touch_start(integer total_number)
{
llSay( 0, "Touched !" );
}
}
Et enregistrez-le. Maintenant placez un objet dans l'inventaire avec les 2 scripts. Vous pouvez y mettre ce que vous voulez. Pour ma part j'y ai placé un objet nommé "Cube", qui est, comme son nom l'indique, un cube simple et sans script.
On va maintenant passer à l'édition du second script (Testeur) en commençant par utiliser la fonction llRezObject habituelle puis ensuite SafeRezObject pour voir les différences.
Petit rappel :
La fonction llRezObject ( détails
ici ) se compose de 5 paramètres :
llRezObject( nom, position, vélocité, rotation, nombre );
- nom : Le nom de l'objet à rez ( prévisible )
- position : La position du rez ( où l'objet doit se trouver quand il sera rezzé )
- vélocité : La vitesse de départ de l'objet ( dans le cas d'un objet physique )
- rotation : Sa rotation de départ
- nombre : Un nombre envoyé au script dans l'objet rezzé
Note : La position ne doit pas être à plus de 10 mètres de l'objet qui rez sinon ça ne fonctionnera pas (valable seulement si vous utilisez la fonction officielle).
Nous allons déclencher le rez au touché, c'est à dire dans l'event touch_start. Le nom de l'objet est "Cube", on va le rez à 2 mètres au dessus du cube rezzer ( axe Z ), avec une rotation nulle. Voilà le résultat :
SafeRezObject( string name, vector pos, vector vel, rotation rot, integer chan, integer rezType, string data ) {
llMessageLinked( LINK_SET, -1621184001,
name + "|" +
(string)pos + "|" +
(string)vel + "|" +
(string)rot + "|" +
(string)chan + "|" +
(string)rezType, data
);
}
default
{
state_entry()
{
llSay( 0, "Hello, Avatar !" );
}
touch_start(integer total_number)
{
llRezObject( "Cube", llGetPos() + <0.0, 0.0, 2.0>, ZERO_VECTOR, ZERO_ROTATION, 0 );
}
}
Quand on touche note objet, ça se rez bien 2 mètres au dessus. Je vais pas aller plus loin dans les explications de la fonction llRezObject qui devrait être comprise pour suivre le reste du tuto.
On va pratiquer le même test mais cette fois-ci avec la fonction SafeRezObject du système. Cette fonction se compose non pas de 5 mais
7 paramètres, les 5 premiers étant exactement les même que llRezObject(); :
SafeRezObject( nom, position, vélocité, rotation, nombre, rezType, données );
- nom : Le nom de l'objet à rez ( prévisible )
- position : La position du rez ( où l'objet doit se trouver quand il sera rezzé )
- vélocité : La vitesse de départ de l'objet ( dans le cas d'un objet physique )
- rotation : Sa rotation de départ
- nombre : Un nombre envoyé au script dans l'objet rezzé
- rezType : le 6ème paramètre de la fonction du système sert à indiquer quel type de rez on veut utiliser (position relative à la region, locale, rotation locale etc) ne vous en préoccupez pas on verra ça plutard, notez juste qu'on met 0 quand on ne l'utilise pas.
- données : le 7ème paramètre de cette fonction est justement celui qui servira à envoyer des données à notre objet de façon synchronisée.
Vu que notre Cube n'a pas besoin de données, le paramètre
données restera vide pour ce premier exemple. Concernant la position de rez, notez qu'il n'y a pas besoin de s'embêter à utiliser llGetPos() le système le fait pour vous, ce paramètre est donc utilisé de façon
locale .
Les différents types de positions (locales et non locales) sont expliqués plus bas dans le tuto mais je montre tout de même une différence :
// une position non locale se fait par rapport à la région
vector positionPasLocale = llGetPos() + <0.0, 0.0, 2.0>;
// une position locale se fait par rapport à la position de l'objet ou de l'avatar ( ici 2 mètres au dessus de notre objet/avatar)
vector positionLOCALE = <0.0, 0.0, 2.0>;
SafeRezObject( string name, vector pos, vector vel, rotation rot, integer chan, integer rezType, string data ) {
llMessageLinked( LINK_SET, -1621184001,
name + "|" +
(string)pos + "|" +
(string)vel + "|" +
(string)rot + "|" +
(string)chan + "|" +
(string)rezType, data
);
}
default
{
state_entry()
{
llSay( 0, "Hello, Avatar !" );
}
touch_start(integer total_number)
{
SafeRezObject( "Cube", <0.0, 0.0, 2.0>, ZERO_VECTOR, ZERO_ROTATION, 0, 0, "" );
}
}
Le changement est très déroutant, n'est-ce pas ?
. Vous remarquerez que le rez demande un peu plus de temps qu'avec la fonction llRezObject, c'est tout à fait normal cela est dû au script Rezzer qui va utiliser plusieurs processus avant de rez votre objet.
Troisième étape : Envoyer des données à l'objet
Le premier exemple n'était qu'une introduction. Nous entrons dans le vif du sujet maintenant
. Le fait d'avoir testé la fonction SafeRezObject pour rez un simple cube n'a vraiment que trop peu d'intérêts et utiliserait des ressources inutilement alors que llRezObject est amplement suffisant pour ça.
Là où ça devient intéressant, c'est de faire des tests sur sa principale utilisation :
Le transfert de données.
Nous allons garder notre objet tel qu'il était au premier exemple :
Je vais commencer par expliquer le déroulement du transfert entre le rezzer et l'objet rezzé. Nous allons utiliser
AUCUN canal fixe pour transmettre nos données, la raison est que je trouve ça totalement pas adapté et beaucoup moins sécurisé. Un canal fixe devrait être seulement utilisé pour le transfert
Avatar -> Script. Il y a 3 types de système de canaux sur SL :
- [1] Les canaux fixes ( par exemple mes 2 objets s'échangent des données sur le canal 100 )
- [2] Les canaux dynamiques ( qui sont uniques et ne changent que selon des critères. Exemple : un canal par rapport à une key )
- [3] Les canaux non fixes ( qui sont simplement des canaux au hasard )
Nous allons prendre le 3 ème cas, soit des canaux au hasard qui changeront à chaque rez. Voici le déroulement :
- Le script Testeur génère un canal au hasard
- Le script Testeur utilise la fonction SafeRezObject avec la donnée "Hello"
- Le script Rezzer rez le Cube
- Le Cube demande les données sur le canal généré
- Le Rezzer envoie la donnée "Hello" au Cube
Comment générer un canal au hasard ?
La fonction llFrand permet de générer un nombre décimal aléatoire c'est d'ailleurs ce que j'utilise. Je propose donc ma fonction qui renvoie un canal au hasard négatif :
integer GetRandomChannel() {
return ~(integer)llFrand( (float)DEBUG_CHANNEL );
}
Elle s'utilise tout bêtement de cette façon :
integer monCanalAuHasard = GetRandomChannel();
llOwnerSay( "Mon canal au hasard : " + (string)monCanalAuHasard );
Comment le Cube pourra connaitre ce canal au hasard ?
Comme vu plus haut, la fonction llRezObject contient 5 paramètres :
llRezObject( nom, position, vélocité, rotation, nombre );
Le paramètre nombre permet d'envoyer un nombre au script de l'objet rezzé dans l'event on_rez. C'est avec cette technique que nous transmettrons le canal au Cube.
Note : C'est le même paramètre pour la fonction SafeRezObject. Vous pouvez voir plus haut qu'elle dispose également d'un paramètre nombre.
On va donc commencer par rezzer nous-même l'objet ( nommé Cube dans mon exemple ) pour y ajouter un script. Créez un script vide et nommez-le par exemple
test cube puis copiez-collez ce code :
integer gCanal;
integer gListenID;
default
{
// Se déclenche quand l'objet est rezzé
on_rez( integer nombre ) {
// on reçoit le paramètre nombre
if( nombre != 0 ) // on vérifie si le canal n'est pas 0
{
gCanal = nombre; // on stock le numéro du canal généré
gListenID = llListen( gCanal, "", NULL_KEY, "" ); // on écoute le canal
llRegionSay( gCanal, "get" ); // on demande les données au Rezzer
}
}
// Se déclenche quand un message est reçu sur le canal écouté
listen( integer canal, string nom, key id, string msg ) {
if( llGetOwnerKey(id) == llGetOwner() ) { // si le owner du message = mon owner
// les données sont stockées dans msg
llOwnerSay( "J'ai reçu : " + msg + " sur le canal : " + (string)canal );
// on désactive le listen une fois qu'on a reçu les données
llListenRemove( gListenID );
}
}
}
Enregistrez-le, prenez une copie et remplacez par l'ancien dans l'inventaire de l'objet.
Notre objet est désormais fin prêt pour recevoir les données que nous lui aurons envoyé. Il nous reste plus qu'à changer le script Testeur. Notre script est :
SafeRezObject( string name, vector pos, vector vel, rotation rot, integer chan, integer rezType, string data ) {
llMessageLinked( LINK_SET, -1621184001,
name + "|" +
(string)pos + "|" +
(string)vel + "|" +
(string)rot + "|" +
(string)chan + "|" +
(string)rezType, data
);
}
default
{
state_entry()
{
llSay( 0, "Hello, Avatar !" );
}
touch_start(integer total_number)
{
SafeRezObject( "Cube", <0.0, 0.0, 2.0>, ZERO_VECTOR, ZERO_ROTATION, 0, 0, "" );
}
}
Ce qu'il faut ajouter :
- Un canal généré au hasard
- Mettre la donnée "Hello" à envoyer
- Mettre le canal généré dans la fonction SafeRezObject
Résultat :
SafeRezObject( string name, vector pos, vector vel, rotation rot, integer chan, integer rezType, string data ) {
llMessageLinked( LINK_SET, -1621184001,
name + "|" +
(string)pos + "|" +
(string)vel + "|" +
(string)rot + "|" +
(string)chan + "|" +
(string)rezType, data
);
}
integer GetRandomChannel() {
return ~(integer)llFrand( (float)DEBUG_CHANNEL );
}
default
{
state_entry()
{
llSay( 0, "Hello, Avatar !" );
}
touch_start(integer total_number)
{
// on prend un canal au hasard
integer monCanalAuHasard = GetRandomChannel();
// la donnée à envoyer
string data = "Hello";
SafeRezObject( "Cube", <0.0, 0.0, 2.0>, ZERO_VECTOR, ZERO_ROTATION, monCanalAuHasard, 0, data );
}
}
Maintenant cliquez sur votre objet et le résultat devrait être un message comme :
[11:47] Cube: J'ai reçu : Hello sur le canal : -1649279489
Essayez de cliquer 3 fois de suite, vous verrez qu'il y a bien une synchronisation de tous les objets :
[11:48] Cube: J'ai reçu : Hello sur le canal : -1444719745
[11:48] Cube: J'ai reçu : Hello sur le canal : -1925779201
[11:48] Cube: J'ai reçu : Hello sur le canal : -662922049
Vous remarquerez également que le canal change pour chaque objet
.
Les positions Locales / Non locales
Introduction
Une région ou sim est un espace en 3 dimensions et donc 3 axes que l'on note
X,
Y et
Z . Pour mettre nos objets dans une région nous avons besoin de connaitre exactement où les placer. Pour ce faire, nous utilisons 3 axes qui représentent un point dans l'espace de cette région.
X et
Y sont tous les deux des axes correspondants à la
surface de la région. Voici une sim vu en hauteur avec les 2 axes X et Y :
Le 3 ème,
Z est la
hauteur dans une région.
Sur SecondLife, les 3 axes sont en
mètres et une région fait 256 mètres en longueur comme en largeur. Ce qui fait qu'une sim en surface va de :
X :
0,
Y :
0
à
X :
256,
Y :
256
Comme vous avez déjà pu le voir en LSL on affiche les 3 axes dans un
vecteur qui se présente sous cette forme :
<
X, Y, Z>
quelques exemples :
<
125, 147, 236>
<
12, 0, 147>
<
256, 256, 23>
pour être plus précis on peut également mettre des nombres décimaux mais attention :
Un nombre décimal ne s'écrit pas avec une virgule mais un point en LSL
Exemple d'un nombre décimal :
12
.7 ( qui est 12,7 )
Exemple d'un vecteur avec nombres décimaux :
<
12.3, 147.85, 212.1>
<
X, Y, Z>
Les positions Non locales :
Les positions non locales correspondent à un point dans la région, comme je l'ai expliqué dans l'introduction. Par exemple je veux envoyer mon objet au milieux de la sim, on a vu qu'une sim faisait 256 mètres en longueur/largeur il faudra simplement utiliser le vecteur :
<
128, 128, Z>
Note : Je n'ai pas précisé le Z tout simplement car ça n'a pas d'importance puisqu'il s'agit de la hauteur, toute valeur différente de Z ne changera rien, l'objet sera toujours au milieux de la sim.
Les positions locales :
Après les positions relatives à la région (non locales), nous voilà rendu aux locales. C'est tout aussi simple à comprendre, je vais essayer d'expliquer par plusieurs exemples simples
.
Imaginons que nous avons notre objet Rezzer à cette position :
<
112, 105, 28>
Et que nous voulons rez notre objet
Cube à
2 mètres de distance (à côté de l'objet Rezzer) sur l'axe
X .
Le vecteur du
Cube sera soit :
<
114, 105, 28> car
112 +
2 =
114
Ou :
<
110, 105, 28> car
112 -
2 =
110
Il faut donc utiliser l'addition ou la soustraction (cela dépendra le sens de l'axe
X que vous utiliserez ). Nous avons donc 2 vecteurs à additionner ou soustraire qui sont :
La position de notre objet Rezzer :
<
112, 105, 28>
Le vecteur avec les 2 mètres de distance sur l'axe X :
<
2, 0, 0>
ce qui donne en position
NON locale :
<
112, 105, 28>
+ <
2, 0, 0>
= <
114, 105, 28>
ou :
<
112, 105, 28>
- <
2, 0, 0>
= <
110, 105, 28>
J'ai bien précisé NON-Locale car on
se réfère à un point par rapport aux axes de la région .
Maintenant, imaginons que notre position doit se référer aux axes de notre objet
Rezzer. Son centre sera donc le vecteur :
<
0, 0, 0>
Pour indiquer que nous voulons que notre
Cube se rez à 2 mètres de distance de notre Rezzer, il faut additioner ou soustraire le centre de notre objet Rezzer comme ceci :
<
0, 0, 0>
+ <
2, 0, 0>
= <
2, 0, 0>
ou :
<
0, 0, 0>
- <
2, 0, 0>
= <
-2, 0, 0>
Ce qui donne <2,0,0> ou <-2,0,0> qui sont des positions locales par rapport au Rezzer.