Home Mapping d'animation Flash interactive sur un objet 3D Alexandre Labedade - 2004
Dans ce tutorial, vous allez voir comment utiliser une animation Flash en tant que texture animée sur un objet 3D, tout en conservant les interactions avec la souris. Cliquez ici pour voir les news |
|||||||||
Utiliser une animation Flash en tant que texture
animée est relativement simple : un acteur Flash possède une
propriété posterFrame qui permet de récupérer une
copie de son image... Si votre animation est un simple 'dessin animé', cela fonctionnera sans problème, vu que rien n'est créé dynamiquement. Le seul problème avec cette propriété, c'est qu'elle ne supporte pas les évènements action scripts et tout ce qui est création dynamique de movieclips... c'est un peu comme si elle capturait l'image de la scène de l'anim flash lorsqu'on navigue dedans depuis l'interface de Flash : simplement les éléments créés 'en dur' sont visibles. Un exemple avec posterFrame : Ce script est un comportement à assigner sur un sprite Flash pour faire changer la propriété poster frame de son acteur, et pour redéfinir en même temps la texture d'un acteur 3D : property spritenum L'autre façon de créer une "texture Flash" sur un objet 3D est un peu plus complexe : L'image actuelle du sprite Flash va etre capturée en temps réel directement depuis la scène, pour ensuite etre utilisée en tant que texture. En procédant ainsi, on conserve le résultat visuel de l'execution de tous les scripts contenus dans l'animation Flash, ce qui n'est pas le cas avec posterFrame. Le premier problème que l'on va donc rencontrer, c'est de capturer le sprite Flash sur la scene, sans que celui-ci ne soit visible... et ce problème va en engendrer un autre : il ne sera pas possible d'utiliser cette méthode en mode projecteur fenetré (à moins de trouver une ruse que je ne connais pas encore), mais il sera possible, en bidouillant un peu, de la faire fonctionner en plein ecran. Pour ce qui est du web, ça dépendra du navigateur utilisé pour visionner l'animation shockwave, avec Internet Explorer, il n'y a pas de soucis... avec les autres c'est une autre histoire. "Mais quel est ce problème ?" me direz-vous... Et bien le problème est que director ne peut pas capturer l'image de la scene en dehors de sa taille déterminée au moment de la compilation. Or, pour une raison qui sera expliquée plus bas, nous allons avoir besoin de "marges" tout autour du sprites 3D. Par exemple, vous avez une scène qui a les dimensions 640x480, dans ce cas, si vous capturez une zone se trouvant au delà de 640 horizontalement ou 480 verticalement vous obtiendrez uniquement du blanc sur cette partie. La ruse consiste donc à créer une animation plus grande, mais de n'en afficher qu'une partie (celle qui nous interesse) à l'execution finale. Donc pour une execution depuis un navigateur internet il faudra utiliser du DHTML pour effectuer un recadrage sur l'animation Shockwave. 1) Capturer l'image du sprite Flash sur la scène sans que celui-ci ne soit visible : Ceci est relativement simple : il suffit de placer le sprite flash sur la scène en désactivant sa propriété Direct to Stage et de placer par dessus le sprite 3D en activant la propriété Direct to Stage de l'acteur correspondant. Cette propriété permet d'inclure ou non le sprite dans le moteur de rendu graphique de Director, si DTS (= Direct to Stage) est inactif, le sprite sera considéré comme n'importe quel autre sprite (ce qui peut provoquer des ralentissements dans son animation), sinon il sera affiché en dernier, par dessus tous les autres sprites et n'apparaitra pas dans la propriété image de la scene. De plus, le fait d'activer la propriété DTS permet d'obtenir un affichage plus fluide sur l'acteur concerné, dans notre cas, l'acteur 3D. Donc dans ce cas, on pourrait placer le sprite Flash sur la piste numéro 2 et le sprite 3D au dessus, sur la piste numéro 3, celui-ci étant en DTS, il apparaitra de toute façon au dessus. (je n'utilise pas le sprite 1 par habitude... au cas où j'aurais besoin d'insérer un fond par la suite...) Le problème dans ce cas, c'est que l'interaction entre la souris et le sprite Flash sera perdu car "physiquement" le sprite 3D est au dessus dans la liste des sprites. Il faut donc placer le sprite Flash par dessus le sprite 3D dans le scenario, afin de conserver les interactions souris sur celui-ci, et comme le sprite 3D est en DTS, il masquera quand même le sprite Flash, donc on est content. Voici un script d'animation à ajouter pour effectuer un premier test : Il faut bien sur avoir un objet 3d qui utilise la texture n°1, et qui dispose de coordonnées de projection UV Par defaut les primitives (sphère, cube...) ont ces coordonnées générées, donc pas d'ennui à ce niveau, par contre, si vous importez un objet 3D depuis une application externe, il faudra penser à générer ces coordonnées correctement. on EnterFrame -- sprite 2 : 3D sprite3D = 2 -- sprite 3 : Flash spriteFlash = 3 TextureResolution = 512 -- Redimensionnement du sprite flash en fonction de la resolution sprite(spriteFlash).width=TextureResolution sprite(spriteFlash).height=TextureResolution sprite(spriteFlash).member.defaultrect=rect(0,0,TextureResolution,TextureResolution) -- creation de l'image Img=image(TextureResolution,TextureResolution,24) -- capture de l'image actuelle du sprite flash Img.copypixels((the stage).image,Img.rect,sprite(spriteFlash).rect) -- remplacement de la texture par la nouvelle image capturée sprite(sprite3D).member.texture[1].image=Img end on exitframe go to the frame end 2) Les évènements souris : Maintenant que l'on dispose d'une animation Flash qui est correctement affichée sur notre objet 3D, il faut pouvoir interagir directement avec en promenant le pointeur de la souris directement sur l'objet 3D quelle que soit sa forme / position / orientation / taille, et non sur le le sprite flash qui lui ne sera de toute façon pas visible au final. Comme l'objet 3D peut avoir n'importe quelle forme ou orientation, on ne peut pas utiliser le repère orthonormé de la scène pour récupérer les coordonnées de la souris Il va donc falloir utiliser les coordonnées UV du pointeur sur l'objet qu'il est en train de survoler, pour obtenir la position correspondante en 2D sur la surface de l'objet, et ainsi replacer le sprite flash juste en dessous afin que la zone survolée corresponde. En plus clair : lorsque le pointeur se balade sur la surface de l'objet 3D, le sprite flash est déplacé de façon "inverse" afin que la zone survolée sur l'objet 3D coïncide avec celle du sprite Flash Voici le script de frame précédent avec cette modification : J'utilise la fonction ModelsUnderLoc pour détecter l'objet se trouvant sous le pointeur, ceci va donner une liste d'info sur le point d'impact trouvé ou non, dont l'identifiant de la Face percutée ainsi que les coordonnées UV sur cette face. Le problème ici, c'est que les coordonnées UV retournées ne peuvent pas etre utilisées telles quelles et doivent être converties afin d'être utilisables correctement en 2D. Pour cela, j'utilise la fonction mMapPointToTexture ecrite par James Newton qui permet de corriger ces coordonnées, cette fonction est à placer dans un script d'animation afin de la rendre accessible de n'importe quel autre script. on EnterFrame -- sprite 2 : 3D sprite3D = 2 -- sprite 3 : Flash spriteFlash = 3 TextureResolution = 512 -- Redimensionnement du sprite flash en fonction de la resolution sprite(spriteFlash).width=TextureResolution sprite(spriteFlash).height=TextureResolution sprite(spriteFlash).member.defaultrect=rect(0,0,TextureResolution,TextureResolution) -- creation de l'image Img=image(TextureResolution,TextureResolution,24) -- capture de l'image actuelle du sprite flash Img.copypixels((the stage).image,Img.rect,sprite(spriteFlash).rect) -- remplacement de la texture par la nouvelle image capturée sprite(sprite3D).member.texture[1].image=Img -- Récupération du point d'impact du curseur sur l'objet 3D pt = the mouseLoc - point(sprite(sprite3D).left, sprite(sprite3D).top) m = sprite(sprite3D).camera.modelsUnderLoc(pt, 1, #detailed) -- Si un point d'impact est trouvé : if m<>[] then -- récupération des coordonnées d'uv mapping au point d'impact uv=mMapPointToTexture(m[1],point(sprite(spriteFlash).width,sprite(spriteFlash).height)) -- si les coordonnées sont bonnes, on replace l'acteur flash -- pour que la zone survolée sur l'objet 3D coincide avec celle -- du sprite, on peut ainsi cliquer sur les boutons if not voidp(uv) then -- repositionnement du sprite Flash sous la souris sprite(spriteFlash).loc = (the mouseloc - uv) + sprite(spriteFlash).member.regpoint end if else -- Sinon, le sprite flash est placé en haut à gauche, hors de portée du pointeur sprite(spriteFlash).loc = sprite(spriteFlash).member.regpoint end if end on exitframe go to the frame end -- MAP POINT TO TEXTURE -- -- -- © October 2001, James Newton <newton@planetb.fr> on mMapPointToTexture(iSectList, anImage) ---------------------------- -- RETURNS the point in the Bitmap member used by the texture -- defined implicitly in <iSectList> -- -- INPUT: -- <iSectList> should be a property list of the format: -- [#model: model("model name"), -- #distance: <float>, -- #isectPosition: <vector>, -- #isectNormal: <vector>, -- #meshID: <integer>, -- #faceID: <integer>, -- #vertices: [<vector>, <vector>, <vector>], -- #uvCoord: [#u: <float>, #v: <float>]] -- -- <anImage> can be VOID, an image object, or any type of object -- with width and height properties (eg: propList) -- -- When you use aCamera.modelsUnderLoc(aLoc, #detailed) or -- aMember.modelsUnderRay(aPoint, aVector, #detailed), the result is -- a linear list of lists with this format. --------------------------------------------------------------------- tModel = iSectList.model tResource = tModel.resource isMesh = (tResource.type = #mesh) if not isMesh then -- We need to use the mesh deform modifier to get texture data if not((tModel.modifier).getPos(#meshdeform))then -- Add mesh deform modifier... but remember to remove it later tModel.addModifier(#meshdeform) isModified = TRUE end if end if tFace = iSectList.faceID tUVCoord = iSectList.uvCoord -- Determine how the iSect data maps to this particular face if isMesh then tCoordList = tResource.textureCoordinateList tFaceList = tResource.face[tFace].textureCoordinates else tCoordList = tModel.meshdeform.mesh[iSectList.meshID].textureCoordinateList tFaceList = tModel.meshdeform.mesh[iSectList.meshID].face[tFace] if isModified then -- Remove the modifier now that it has done its job tModel.removeModifier(#meshdeform) end if end if case anImage.ilk of #void: tWidth = 1 tHeight = 1 #point,#list: tWidth = anImage[1] tHeight = anImage[2] #image,#rect,#propList: tWidth = anImage.width tHeight = anImage.height end case -- tCoordList will be a list of lists, each containing two floating- -- point numbers between 0.0 and 1.0. The first number defines the -- relative horizontal position of a point in the texture, the -- second number defines the relative vertical point. The origin -- point [0.0, 0.0] is in the bottom left hand corner. -- -- tFaceList will be a list with three integer values [a, b, c] -- These values determine which entry in tCoordList is used by the -- chosen face. The first entry <a> defines the origin. The u -- value increases from <a> to <b>. Any point on the line between -- <a> and <b> will have a v value of zero. The v value increases -- from <a> to <c>. Any point on the line between <a> and <b> will -- have a v value of zero. -- Calculate the position of the points <a>, <b> and <c> within the -- Bitmap member. tLocA = tCoordList[tFaceList[1]] -- [<float>, <float>] tLocA = point(tLocA[1] * tWidth, (1 - tLocA[2]) * tHeight) tLocB = tCoordList[tFaceList[2]] -- [<float>, <float>] tLocB = point(tLocB[1] * tWidth, (1 - tLocB[2]) * tHeight) tLocC = tCoordList[tFaceList[3]] -- [<float>, <float>] tLocC = point(tLocC[1] * tWidth, (1 - tLocC[2]) * tHeight) tUVector = (tLocB - tLocA) * tUVCoord.u -- actually a 2D point... tVVector = (tLocC - tLocA) * tUVCoord.v -- ... rather than a vector -- Start from the origin <a>, move first in the u direction then in -- the v direction to end up at the point in the texture return tLocA + tUVector + tVVector end mMapPointToTexture Et voilà... vous avez maintenant une animation Flash mappée sur un objet 3D, et les interactions avec l'animation Flash son toujours actives. Comme le sprite Flash bouge sans arret en fonction de la position de la souris, il arrive (souvent) que celui-ci passe en dehors de la zone visible de la scene, et donc il possède parfois des zones blanches... ce qui est plutot dérangeant. Pour corriger ce problème, voici comment procéder : a) Déterminez quelle va etre la taille maximale de la texture que vous allez utiliser. 512 et déjà une bonne taille, au dessus, ça commence à vraiment ramer b) Multipliez cette taille par 3 et utilisez cette valeur comme dimensions pour votre scene : 512 * 3 = 1536 c) Placez le sprite 3D exactement au centre de la scene et donnez lui les dimension 512x512, vous aurez ainsi une marge de 512 pixels tout autour de votre sprite 3D Bien entendu, vous pouvez donner les dimensions que vous souhaitez à votre sprite 3D, dans ce cas, pour calculer les dimensions de la scene, il faudra faire le calcul suivant : Largeur de la scene = (Largeur maxi de la texture Flash * 2) + largeur du sprite 3D Hauteur de la scene = (Hauteur maxi de la texture Flash * 2) + hauteur du sprite 3D Mais continuons avec notre exemple en 512x512, pour faire plus simple. Publiez maintenant votre animation en Shockwave, et éditez la page html générée dans un editeur texte quelconque, et ajoutez cette ligne juste avant le début du tag <OBJECT> pour créer un nouveau calque DHTML : <div style="position:absolute; width:1536; height:1536; top:-512px; left:-512px; clip: rect(512px, 1024px, 1024px, 512px); "> et pensez bien à fermer ce calque juste apres la fin du tage <OBJECT> en tapant :</div> Vous devriez alors avoir quelque chose comme ça : <div id=disp style="position:absolute; width:1536; height:1536; top:-512px; left:-512px; clip: rect(512px, 1024px, 1024px, 512px); "> <OBJECT classid="clsid:166B1BCA-3F9C-11CF-8075-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/director/sw.cab#version=8,5,1,0" ID=tutorial WIDTH=1536 HEIGHT=1536> <param name=src value="tutorial.dcr"> <param name=swStretchStyle value=fill> <param name=swRemote value="swSaveEnabled='false' swVolume='false' swRestart='false' swPausePlay='false' swFastForward='false' swContextMenu='false' "> <PARAM NAME=bgColor VALUE=#696882> <EMBED SRC="tutorial.dcr" bgColor=#696882 WIDTH=1536 HEIGHT=1536 swRemote="swSaveEnabled='false' swVolume='false' swRestart='false' swPausePlay='false' swFastForward='false' swContextMenu='false' " swStretchStyle=fill TYPE="application/x-director" PLUGINSPAGE="http://www.macromedia.com/shockwave/download/"> </EMBED> </OBJECT> </div> Ce qui nous interesse ici, c'est le Style appliqué au calque : style="position:absolute; width:1536; height:1536; top:-512px; left:-512px; clip: rect(512px, 1024px, 1024px, 512px); " Explication de chaque attribut :
bien sur, il vous sera possible de placer ce calque ailleurs qu'au point 0,0 : il suffira de changer ses attributs top et left, en n'oubliant pas de soustraire à chaque fois la dimension des marges. Une autre méthode peut être utilisée pour recadrer l'animation shockwave : ouvrez la page dans une popup aux dimension 512x512 et spécifiez simplement les coordonnées top et left dans le style du calque. Cliquez ici pour voir un demonstration en ligne : Exemple (6ko) Le fichier source est disponible ici : Source (13ko) Ici, un autre exemple avec plus d'animations Flash : Gros Test (attention, c'est plus gros : 3Mo) NEWS : Ce qui reste à faire : - Trouver une solution pour ce problème de recadrage afin que çela puisse marcher en mode projecteur. Il existe bien un moyen, mais ceci necessite d'utiliser 3 fois la fonction updatestage à chaque frame, et donc la fluidité de l'animation est divisée par 3... donc c'est pas bon - Un petit truc que je n'arrive pas à rectifier : lorsque le cube se trouve bien en face de la camera, la texture pixelise, alors que si on l'incline légèrement, elle devient lissée grace au mip mapping... mais je ne comprend pas pourquoi ça pixelise, alors qu'elle devrait etre toujours lissée. (le 11/03/2004) Solution trouvée ! Pour ne plus avoir ce problème d'aliasing sur la texture, il faut utiliser la valeur #LowFiltered sur la qualité de la texture. Dans ce cas, il n'y aura plus de mipmapping, mais la texture sera bien propre lorsque les polygones seront face à la camera. J'ais mis le fichier source à jour avec cette nouvelle option. |