La déformation d'un bitmap via ses quads reste dans Director
7 le seul moyen de simuler un monde 3D. 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 (abc) 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 local de l'objet (OMÉGA
: abc) 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
lesPoints =[]
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.