3D Lingo

 

 I - Perdu dans l'espace ? 

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 :

LeNouveauSysteme = [ #a:[1000.0, 0, 0], #b:[0, 0, -1000.0], #c:[0, 1000.0, 0] ]

 

on rotationSelonAxeXDe angle

systemeInitial = [ #a:[1000.0,0,0], #b:[0,1000.0,0], #c:[0,0,1000.0] ]

LeNouveauSysteme= [ #a : [systemeInitial[#a][1], systemeInitial[#b][2] * 0, systemeInitial[#c][3] * 0] , ¬

#b : [systemeInitial[#a][1] * 0, systemeInitial[#b][2] * COS(angle), systemeInitial[#c][3] * -SIN(angle)],¬

#c : [systemeInitial[#a][1] * 0, systemeInitial[#b][2] * SIN(angle), systemeInitial[#c][3] * COS(angle) ] ]

return LeNouveauSysteme

end

 

De même pour une rotation selon l'axe Z (rayon 1000) :

on rotationSelonAxeZDe angle

systemeInitial = [ #a:[1000.0,0,0], #b:[0,1000.0,0], #c:[0,0,1000.0] ]

LeNouveauSysteme = [ #a : [systemeInitial[#a][1] * COS(angle), systemeInitial[#b][2] * SIN(angle), systemeInitial[#c][3] * 0] , ¬

#b : [systemeInitial[#a][1] * -SIN(angle), systemeInitial[#b][2] * COS(angle), systemeInitial[#c][3] * 0],¬

#c : [systemeInitial[#a][1] * 0, systemeInitial[#b][2] * 0, systemeInitial[#c][3] ] ]

return LeNouveauSysteme

end

 

 

 

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

xA = aR* LeSystemeModifié[#a][1] + bR * LeSystemeModifié[#b][1] + cR * LeSystemeModifié[#c][1]

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 :

  1. pivoter ou transformer diversement le système local (a,b,c) de l'objet
  2. calculer les nouvelles coordonnées absolues (xA, yA, zA) de tout point noté (AR, bR, cR) dans son systËme local
  3. 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 rotationSelonAxeZDe angle
   systemeLocal = [ #a:[1000.0,0,0], #b:[0,1000.0,0], #c:[0,0,1000.0] ]
   nouveauSyteme = [¬
   #a : [systemeLocal[#a][1] * cos(angle), systemeLocal[#b][2] * sin(angle), systemeLocal[#c][3] * 0] , ¬
   #b : [systemeLocal[#a][1] * -sin(angle), systemeLocal[#b][2] * cos(angle), systemeLocal[#c][3] * 0],¬
   #c : [systemeLocal[#a][1] * 0, systemeLocal[#b][2] * 0, systemeLocal[#c][3] ] ]
   return nouveauSyteme
-- "nouveauSyteme" c'est, dans l'espace absolu (x,y,z)
-- les nouvelles coordonnées du repère local (a,b,c)

end

on rotationSelonAxeXDe angle
   systemeLocal = [ #a:[1000.0,0,0], #b:[0,1000.0,0], #c:[0,0,1000.0] ]
   nouveauSyteme = [¬
   #a : [systemeLocal[#a][1], systemeLocal[#b][2] * 0, systemeLocal[#c][3] * 0] , ¬
   #b : [systemeLocal[#a][1] * 0, systemeLocal[#b][2] * cos(angle), systemeLocal[#c][3] * -sin(angle)],¬
   #c : [systemeLocal[#a][1] * 0, systemeLocal[#b][2] * sin(angle), systemeLocal[#c][3] * cos(angle) ] ]
   return nouveauSyteme
end

on rotationSelonAxeYDe angle
   systemeLocal = [ #a:[1000.0,0,0], #b:[0,1000.0,0], #c:[0,0,1000.0] ]
   nouveauSyteme = [
   #a : [systemeLocal[#a][1] *cos(angle) , systemeLocal[#b][2] *0, systemeLocal[#c][3] * -sin(angle)] , ¬
   #b : [systemeLocal[#a][1] *0, systemeLocal[#b][2], systemeLocal[#c][3] *0 ],¬
   #c :[systemeLocal[#a][1] * sin(angle), systemeLocal[#b][2] * 0, systemeLocal[#c][3] * cos(angle) ] ]¬
   return nouveauSyteme
end

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.

property spriteNum
property pC1, pC2, pC3, pC4
property pOk


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

unCoin = equivalenceAbsolu (pc2[1] , pc2[2] , pc2[3], nouvellesCoordonnéesDuSystemeLocal)
pc2 = unCoin


add LesQuad, point(unCoin[1] + origineH ,unCoin[2] + origineV)

unCoin = equivalenceAbsolu (pc3[1] , pc3[2] , pc3[3], nouvellesCoordonnéesDuSystemeLocal)
pc3 = unCoin

add LesQuad, point(unCoin[1] + origineH ,unCoin[2]+ origineV )

unCoin = equivalenceAbsolu (pc4[1] , pc4[2] , pc4[3], nouvellesCoordonnéesDuSystemeLocal)
pc4 = unCoin

add LesQuad, point(unCoin[1] + origineH ,unCoin[2] + origineV)

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

---------------------------------------------------------------------


on mouseDown

angleY = 0.01
  repeat while the mousedown
    
    nouveauSysteme = rotationSelonAxeYDe (angleY)
    sendAllSprites(#transformation, nouveauSysteme)
    go the frame
  end repeat
end

---------------------------------------------------------------------


on mouseDown

 angleZ = 0.01
  repeat while the mousedown
   
    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.