La 3D dans Director. Les sources d'informations sont rares
et d'autant plus précieuses en ce domaine. Montrons
notre reconnaissance et disons tout de suite que, largement
inspiré du code de l'excellent Dave Cole, l'exercice
mis en uvre dans ces pages na pas d'autre prétention
que d'en être une simplification. Les super matheux
se reporteront au modèle plutôt qu'à
la copie. On trouvera en fin de cet article
une liste de lien web sur le sujet.
Le principe
Faire tourner un objet dans l'espace c'est faire tourner
ensemble, de façon cohérente tous les points
de l'objet. Afin de faire tourner chaque point autour du
même axe il faut connaître la distance de ce
point au centre de rotation, calculer sa nouvelle position,
recommencer avec l'autre point... quasi impossible ! on
choisit une autre voie... il s'agit de faire subir toutes
les transformations voulues au système local de l'objet
c'est à dire au système de repères
où tous ses points sont mesurés puis de recalculer
la position absolue de chaque point. On va donc utiliser
un système de repères double
On se dote d'un repère orthonormé x,y, z
ET AUSSI d'un système de repères a,b, c d'abord
confondu avec le premier et qui sera le système local
de l'objet. Ce système noté Oméga est
défini par les trois points de coordonnées
a b c. C'est dans ce système que les points quelconques
seront d'abord placés et définis.
Pour faire tourner un objet, nous feront tourner son système
local tout entier. Et ainsi, un point quelconque de coordonnées
3,3,3 dans le système local (abc) ou système
de coordonnées de l'objet, aura les même coordonnées
dans SON système (abc) après que celui ci
aura été modifié MAIS pas les même
coordonnées dans le monde absolu XYZ
Exemple après une rotation du monde de l'objet (OMÉGA)
selon l'axe X. Dans leur système de référence
les points de l'objet n'ont pas bougé... mais POUR
NOUS dans le système absolu (x,y,z), si:
Fonctions essentielles
la fonction suivante permet de faire tourner selon l'axe
X, le système Oméga de coordonnées
a b c (le rayon est 1000). Après une rotation à
45 ° par exemple (PI/4 en degrés radians), le
système est renversé et l'on obtient :
Bien sûr une fois le système (abc) modifié,
il nous faut obtenir les coordonnées absolues (xA,
yA, zA) d'un point donné dans ce système.
Les coordonnées (aR, bR, cR) de ce point sont relatives
à SON système de repères. Nous voulons
connaître leur équivalence absolue. Nous obtenons
la conversion par un calcul matriciel.
(aR, bR, cR) * [ #a : [x, y, z], #b : [x, y, z], #c : [x,
y, z] ]
c'est à dire que l'on obtient les coordonnées
absolues du point en multipliant ses coordonnées
locales par les coordonnées absolue du système
où elle sont mesurées.
En Lingo :
on equivalenceAbsolu aR , bR , cR, LeSystemeModifié
-- convertit un point dans son espace de coordonnées
en coordonnées absolues
yA = AR * LeSystemeModifié[#a][2] + bR * LeSystemeModifié[#b][2]
+ cR * LeSystemeModifié[#c][2]
zA = AR * LeSystemeModifié[#a][3] + bR * LeSystemeModifié[#b][3]
+ cR * LeSystemeModifié[#c][3]
return [xA/1000.0, yA/1000.0, zA/1000.0]
end
C'est ainsi que l'on a pu trouver les équivalences
notées sur l'image ci-après :
Nous voilà déjà en mesure d'effectuer
de nombreux calculs. Le principe d'une animation sera simple
dès lors :
pivoter ou transformer diversement le système
local (a,b,c) de l'objet
calculer les nouvelles coordonnées absolues (xA,
yA, zA) de tout point noté (AR, bR, cR) dans son
systËme local
projeter ces coordonnées absolues sur la scène
de Director (et pour ce faire ne retenir que x et y)
Créer une première animation
Pour créer notre animation nous devons animer les
facettes d'un cube. Nous nous proposons d'utiliser la propriété
Quad of sprite et d'étirer ainsi les quatre coins
d'un bitmap. Nul besoin dès lors de créer
de nombreux acteurs. 1 seul acteur 1 bit de quelques pixels
fera l'affaire. Nous disposons six fois cet acteur sur la
scène (piste 1 à 6 du scénario) et
nous sommes prêt.
On ouvre une fenêtre de script d'animation et l'on
rédige nos indispensable fonctions de conversions.
Elles nous seront bientôt très utiles.
on miseAlEchelleDe systemeLocal, echelle
return [ #a:[1000.0*echelle,0,0], #b:[0,1000.0*echelle,0],
#c:[0,0,1000.0*echelle] ]
end
on equivalenceAbsolu AR , bR , cR, nouveauSyteme
-- convertit un point depuis SON
système de coordonnées en coordonnées
absolues sur l'axe X,Y,Z
xA = AR * nouveauSyteme[#a][1] + bR
* nouveauSyteme[#b][1] + cR * nouveauSyteme[#c][1]
yA = AR * nouveauSyteme[#a][2] + bR
* nouveauSyteme[#b][2] + cR * nouveauSyteme[#c][2]
zA = AR * nouveauSyteme[#a][3] + bR
* nouveauSyteme[#b][3] + cR * nouveauSyteme[#c][3]
lePoint = [xA/1000.0, yA/1000.0, zA/1000.0]
-- division par mille
return lePoint
end
On y ajoute une fonction retournant les coordonnées
x, y, z d'un cube. On se reportera à la deuxième
partie de cet article pour générer des formes
plus élaborées ou à tout le moins pour
les gÈnÈrer de façon plus élégante.
Ici on définit chaque coin autour du centre. Ce centre
peut être positionné diversement dans l'espace
en modifiant le paramètre position [x,y,z].
on definitUnCube cote, position
origH = position[1]
origV = position[2]
posZ = position[3]
-- on crée
les cotés un par un et on les ajoute à la
liste des points
unCote = [[-cote/2 +origH, -cote/2 +
origV, -cote/2+ posZ], [cote/2 +origH, -cote/2 + origV,
-cote/2+ posZ], [cote/2 +origH, cote/2 + origV, -cote/2+
posZ], [-cote/2+origH, cote/2 + origV, -cote/2+ posZ]
]
repeat with n in unCote
add lesPoints , n
end repeat
unCote = [[-cote/2
+ origH, -cote/2 + origV, -cote/2 + posZ], [cote/2 + origH,
-cote/2 + origV, -cote/2+ posZ], [cote/2 +origH, -cote/2
+ origV, cote/2+ posZ],[-cote/2 +origH, -cote/2
+ origV, cote/2+ posZ]]
repeat with n in unCote
add lesPoints , n
end repeat
unCote = [[cote/2 +origH, -cote/2 +
origV, cote/2+ posZ],[cote/2 +origH, cote/2 + origV, cote/2+
posZ],[cote/2 +origH, cote/2 + origV, -cote/2+ posZ],
[cote/2 +origH, -cote/2 + origV, -cote/2+ posZ]]
repeat with n in unCote
add lesPoints , n
end repeat
unCote = [[cote/2 +origH, cote/2 + origV,
-cote/2+ posZ], [-cote/2 +origH, cote/2 + origV, -cote/2+
posZ],[-cote/2 +origH, cote/2 + origV, cote/2+ posZ],
[cote/2 +origH, cote/2 + origV, cote/2+ posZ]]
repeat with n in unCote
add lesPoints , n
end repeat
unCote = [[-cote/2 +origH, cote/2 +
origV, -cote/2+ posZ], [-cote/2 +origH, -cote/2 + origV,
-cote/2+ posZ],[-cote/2 +origH, -cote/2 + origV, cote/2+
posZ],[-cote/2 +origH, cote/2 + origV, cote/2+ posZ]]
repeat with n in unCote
add lesPoints , n
end repeat
unCote = [[-cote/2 +origH, -cote/2 +
origV, cote/2+ posZ], [cote/2 +origH, -cote/2 + origV,
cote/2+ posZ], [cote/2 +origH, cote/2 + origV, cote/2+
posZ], [-cote/2 +origH, cote/2 + origV, cote/2+ posZ]
]
repeat with n in unCote
add lesPoints , n
end repeat
return lesPoints
end
Dès lors on peut en appelant la fonction "definitUnCube"
se faire renvoyer une liste de coordonnées x,y,z
correspondant aux 4 coins des 6 facettes du cube. Dans le
même script d'animation on rédige la fonction
vasY qui mobilisera le nombre requis de sprites en leur
passant à chaque fois 4 jeux (propriétés
pC1, pC2, pC3, pC4) de coordonnées. Bien sûr
ce script n'aura de sens que lorsqu'on aura associé
aux sprite le script "facette" (voir plus bas).
on vasY
tout = definitUnCube (100, [0,0,0])
UnSprite = 1 -- on commence avec
le sprite 1
repeat with n = 1 to count(tout)
-- on informe ce sprite des coordonnées
de chacun de
-- ses coins (propriétés pC1,
pC2, pC3, pC4)
the pc1 of sprite UnSprite = [tout[n][1],
tout[n][2], tout[n][3]]
the pc2 of sprite UnSprite = [tout[n+1][1],
tout[n+1][2], tout[n+1][3]]
the pc3 of sprite UnSprite = [tout[n+2][1],
tout[n+2][2], tout[n+2][3]]
the pc4 of sprite UnSprite = [tout[n+3][1],
tout[n+3][2], tout[n+3][3]]
sendSprite(UnSprite, #Initialisation) --
on envoie au sprite une instruction d'initialisation
UnSprite = UnSprite + 1
-- on passe au sprite suivant
n = n + 3 -- on passe
au quatrième jeu de coordonnées
end repeat
nouvellesCoordonnéesDuSystemeLocal
= rotationSelonAxeYDe ( pi/3)
-- on fait tourner
le monde selon l'axe y afin que les objets
-- se présentent avec une
légère inclinaison (angle pi/3 en degrés
radians)
sendAllSprites(#transformation, nouvellesCoordonnéesDuSystemeLocal)
-- on informe les sprites
de ce changement (instruction "transformation")
end
Notre fonction ayant besoin d'être appelée
dès le début de l'animation, on ajoute :
on startmovie
vasy
end
Il nous faut maintenant rédiger le behavior "facette"
que nous associons à tous les sprites. Ce behavior
doit permettre à chaque sprite représentant
une facette, de stocker les coordonnées de ses 4
coins.
C'est un nouveau script de type "comportement" que nous
ouvrons maintenant.
on beginSprite me
the loc of sprite pNum = point(-1000,-1000)
pPosition = point(160,120)
-- pPosition c'est la
position initiale de l'objet sur la scène lors
de son apparition
-- les coordonnées des facettes en
effets sont pour plus de commodité définies
depuis l'origine
-- absolue (0,0,0) mais nous voulons que le
monde se déploie au centre de la scène
end
On ajoute la fonction d'initialisation. La propriété
pOk permettra de distinguer les sprites mobilisés
des autres et ainsi de passer une instruction à tous
les sprites sans discernement... ceux-ci ne l'intercepteront
que si pOk est positionné à TRUE. On profite
de l'initialisation pour affecter une couleur aléatoire
à nos sprites. La réduction d'opacité
enfin est nécessaire dès lors que nous ne
gérons pas encore les faces cachées.
On Initialisation me
pOk = true
the blend of sprite spriteNum = 40
the foreColor of sprite spriteNum =
random(255)-25
end
La méthode "transformation"est appelée après
une transformation du système local. Elle va (si
pOk est à TRUE) reprendre chaque coin (pC1,PC2,PC3,C4)
du sprite et calculer sa nouvelle position.
On transformation me, nouvellesCoordonnéesDuSystemeLocal
LesQuad = []
if not pOk then exit -- on quitte
ici si l'initialisation n'a pas eu lieu
origineH = pPosition[1]
origineV = pPosition[2]
unCoin = equivalenceAbsolu (pc1[1] , pc1[2] , pc1[3],
nouvellesCoordonnéesDuSystemeLocal)
pc1 = unCoin
-- les nouvelles coordonnées de ses coins doivent
être mémorisées par le sprite, pC1
est donc mis à jour
add LesQuad, point(unCoin[1] + origineH ,unCoin[2]+ origineV
)
on ajoute dans une liste "LesQuad"
les deux premières valeurs de pC1
the quad of sprite spriteNum = LesQuad
-- la liste LesQuad contient donc
les coordonnées absolues des quads du sprite
end
D'ores et déjà nous pouvons lancer l'animation.
Nous obtiendrons un beau cube... parfaitement statique !
Nous créons immédiatement trois boutons d'animation
auxquels nous associons chaque fois un des behaviors suivants :
on mouseDown
angleX = 0.01
repeat while the mousedown
-- on fait
tourner un peu le monde à chaque passage dans la
boucle
-- l'incrément
est faible mais le resultat plutôt rapide sur un
G3 !
nouveauSysteme = rotationSelonAxeXDe
(angleX)
sendAllSprites(#transformation,
nouveauSysteme)
-- l'instruction
est donnée à tous les sprites mais seuls
ceux
-- dont le flag pOk est à
TRUE en feront quelquechose
go the frame
end repeat
end
nouveauSysteme = rotationSelonAxeZDe
( angleZ)
sendAllSprites(#transformation,
nouveauSysteme)
go the frame
end repeat
end
Notre animation est finie. On peut bien sûr exploiter
les mêmes routines pour créer et animer d'autres
objets. Sur une machine véloce, l'animation est plutôt
fluide. Dès lors ne peut-on envisager de créer
des objets plus complexes ? A
suivre...
Rappel sur les règles du calcul
matriciel
Une matrice de dimension R x C (rangées x colonnes)
ne peut être multipliée que par une matrice
de dimension C x n. Le résultat est une matrice de
dimension R x n. Pour trouver le nouvel élément
[a,b] dans la nouvelle matrice, on multiplie chaque élément
de la rangée a de la première matrice par
l'élément correpsondant de la colonne b de
la deuxième matrice.