Qui veut des Mango?

13. avril 2011

Plusieurs sessions du Mix2011 sur le sujet de Windows Phone étaient marquées TBA (sans doute pour To Be Annonced).

La liste vient d’être mise à jour et il y a du lourd:

  • What’s Coming Next to the Windows Phone Application Platform
  • Going Mobile with Your Site on Internet Explorer 9 and Windows Phone 7
  • Mini-Session: Get Ready for Fast Application Switching in Windows Phone
  • What’s New in the Windows Phone Developer Tools?
  • Windows Phone Multitasking
  • Multitasking in the Next Version of Windows Phone, Part II: Using Background Agents
  • Sensor Access in the Next Version of Windows Phone
  • What’s New for Windows Phone Development with Silverlight?
  • Windows Phone Architecture – Deep Dive
  • What’s New for Windows Phone Development with the XNA Framework?
  • Enhanced Push Notifications & Live Tiles for Windows Phone
  • New Data Access Features Coming to Windows Phone

 

On peut donc voir apparaitre plusieurs fonctionnalités pour la future version de Windows Phone (Mango) qui font plaisir:

  • Amélioration (centralisation?) de l’accès aux senseurs
  • Support du multitasking et de l’application switching
  • Améliorations des outils de développement
  • Nouveautés autour de XNA (peut-être la possibilité de faire du Silverlight ET du XNA dans la même application)
  • Accès aux données

 

Bref du lourd comme on aime. Je suis déjà un grand fan du développement WP7 alors si en plus on m’offre de nouveaux jouets…

Divers, Mix2011, Windows Phone

NopControls

28. mars 2011

Je viens de mettre en ligne un nouveau projet Codeplex destiné à héberger mes contrôles Silverlight (pour Windows Phone et Desktop).

Le projet s’appelle NopControls et se trouve ici:

http://nopcontrols.codeplex.com

Le but est de fournir le code de quelques contrôles sympathiques que vous pouvez réutiliser dans vos projets si le cœur vous en dit.

Au programme pour le moment deux contrôles:

  • PolygonTabControl : Ce contrôle mappe ses enfants sur la face d’un volume à n faces. Par exemple si il a 5 enfants, il crée un pentagone extrudé et met chaque enfant sur une face.
  • ExpandableTab : Ce contrôle permet de créer des fiches qui se docke sur un bord et qui peuvent apparaitre lorsque l’on clique sur l’onglet qui les représente.

 

Pour en savoir plus, reportez vous au site Codeplex, il y a un exemple en Silverlight Sourire

.Net, Silverlight, Windows Phone

Moteur de particules pour Windows Phone 7

24. février 2011

L’objectif de ce tutoriel est essentiellement de nous amuser avec la partie 3D de XNA pour Windows Phone 7.

Pour que cela soit ludique, nous allons produire un applicatif qui permettra à l’utilisateur de bouger des émetteurs de particules. Cela dans un but uniquement contemplatif (un peu comme le simulateur d’eau sur la table Surface).

Le code complet est disponible juste là.

 

Qu’est-ce qu’un simulateur de particules?


Une particule est une entité autonome définie par plusieurs paramètres parmi lesquels comptent surtout la position et la direction de déplacement.

Ainsi un simulateur de particules est un ensemble de particules qui se déplacent simultanément. Selon ce que l’on souhaite gérer, on peut rajouter de nombreuses fioritures. Dans notre cas, nous allons gérer la position, le déplacement, la couleur, la taille, la gravité et même les collisions avec le sol.

La structure de notre particule va donc être:

    public class Particle
    {
        public Vector3 Position;
        public float Size;
        public Color Color;
        public int Age;
        public int DeadAge;
        public Vector3 Direction;
        public float Speed;
    }

On peut noter que la particule possède un Age et un DeadAge. En effet, lors de l’émission de la particules, cette dernière est âgée de 0 frame. A chaque update, son âge augmente de 1 et dès que l’âge = DeadAge, la particule est détruite et remplacée par une nouvelle.

 

Mise en place du mécanisme de rendu


Pour dessiner nos particules sur WP7, nous allons utiliser XNA 4.0. Ce dernier va nous permettre d’avoir un accès rapide au GPU car si l’on veut dessiner plus de 2000 particules en simultané, il faudra de la puissance.

Pour commencer nous allons initialiser un GraphicsDeviceManager. Ce dernier est notre intermédiaire avec le GPU et l’accélération graphique:

            graphics = new GraphicsDeviceManager(this)
                           {
                               PreferredBackBufferWidth = 480,
                               PreferredBackBufferHeight = 800,
                               PreferredDepthStencilFormat = DepthFormat.Depth24,
                               IsFullScreen = true
                           };
            Content.RootDirectory = "Content";

            // Frame rate is 30 fps by default for Windows Phone.
            TargetElapsedTime = TimeSpan.FromTicks(333333);

Nous demandons ici un rendu sur toute la surface (480x800). Il est à noter que nous pourrions demander moins pour avoir plus de puissance, sachant que le WP7 fournit un matériel dédié pour la remise à taille (en gros on dessine en 240x400 par exemple et le matériel du WP7 fait le zoom sans consommer de puissance CPU/GPU).

Nous demandons également un Depth Buffer de 24bits (le plus précis que l’on puisse avoir). Ce qui veut dire que chaque pixel aura sa profondeur stocké dans un flottant de 24 bits. Cette profondeur permet de savoir si le pixel que l’on va écrire est plus proche que celui qui a déjà été écrit. Ainsi comme il n’est pas possible de garantir que l’on écrive les pixels dans le bon ordre, le Depth Buffer garantit que c’est toujours le pixel le plus proche qui est conservé.

Pour dessiner nos particules, nous allons avoir également besoin d’une texture (celle qui sera dessinée sur chaque particule) et d’un effet. Il existe plusieurs effets dans XNA 4.0 pour WP7, ces derniers sont en fait des shaders (pixel + vertex) qui permettent de rendre des objets 3D avec diverses options supportées.
Dans notre cadre, nous allons utiliser un BasicEffect (l’effet de base qui gère une texture, un éclairage et la coloration par vertex):

            particleTexture = Content.Load<Texture2D>("part");

            // Effect
            effect = new BasicEffect(graphics.GraphicsDevice)
                         {
                             Alpha = 1.0f,
                             TextureEnabled = true,
                             FogEnabled = false,
                             VertexColorEnabled = true,
                             LightingEnabled = false,
                             World = Matrix.Identity
                         };
Ici, nous configurons notre effet pour qu’il ne gère que la texture et la couleur par vertex. Nous n’avons pas besoin de l’éclairage.

 

Stocker nos particules pour la carte graphique


Une notion importante dans le cadre de XNA est la séparation des mémoires. En effet, le CPU et le GPU ne travaillent pas sur les mêmes mémoires. Ainsi, tout ce que devra traiter le GPU doit être mis dans sa mémoire.

C’est ici qu’interviennent les vertex et index buffers. Ces buffers servent à stocker les données de rendus nécessaires au GPU.

Le vertex buffer contient la liste des vertices d’un objet 3D (les points dans l’espace). XNA est suffisamment souple pour permettre de stocker ce que l’on souhaite par vertex. Dans notre cas nous allons définir nos vertices ainsi:

    public struct ParticleVertex : IVertexType
    {
        public const int Stride = 24;

        public Vector3 Position;
        public Vector2 TextureCoordinates;
        public Color Color;

        public VertexDeclaration VertexDeclaration
        {
            get
            {
                return new VertexDeclaration(new[]
                                                 {
new VertexElement(0, VertexElementFormat.Vector3, 
VertexElementUsage.Position, 0),

new
VertexElement(12, VertexElementFormat.Vector2,
VertexElementUsage.TextureCoordinate, 0),

new
VertexElement(20, VertexElementFormat.Color,
VertexElementUsage.Color, 0), } ); } } }

Notre vertex contient donc une position (obligatoire), des coordonnées de textures (pour afficher notre texture) et une couleur (cela permettra de faire des particules multicolores).

Notons au passage que notre structure implémente l’interface IVertexType qui nous permet de décrire à XNA la structure (VertexDeclaration) d’un vertex. Ceci est en effet nécessaire pour que les shaders des effets lisent correctement nos structures.

Les index buffers contiennent la définition des faces sous la forme de 3 entiers par face qui donnent les numéros des vertices de chaque face. Ainsi l’index buffer [0, 1, 2, 0, 2, 3] définit deux faces qui pointent vers 4 vertices différents. La face 0 contient les vertices 0, 1 et 2. La face  contient les vertices 0, 2 et 3. Ainsi on peut définir une réutilisabilité pour défnir nos triangles. Sans index buffer, il faudrait 6 vertices pour faire 2 triangles. Avec un index buffer, il n’en faut plus que 4 puisque dans notre cas les 2 faces partagent 2 vertices.

Ainsi, chaque système de particules va donc posséder un vertex buffer et un index buffer.

Pour rendre les particules, le code donne donc:

  device.SetVertexBuffer(vertexBuffer);
  device.Indices = indexBuffer;
            
  device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 
currentParticlesCount * 4, 0, currentParticlesCount * 2);

On indique au système notre vertex buffer, notre index buffer et on demande le rendu d’une liste de triangles.

 

Préparation des buffers


Le principe de fonctionnement général de notre système est de faire évoluer nos particules sur le CPU et avant chaque rendu de préparer un vertex buffer avec les informations nécessaires pour le rendu sur le GPU.

Le vertex buffer devra contenir 2 triangles par particules (chaque particule est en effet un quad toujours orienté vers la caméra). Le vertex buffer mesurera donc 4 x nb_particules. (Dans les précédentes versions de XNA, il nous aurait été possible de mettre un vertex par particule en utilisant le PointList, mais cette topologie de rendu n’est plus supportée par XNA 4.0).

L’index buffer sera statique puisqu’il définit des suites de triangles fonctionnants sur 4 vertices:

indexBuffer = new IndexBuffer(device, typeof(short), 
maxParticlesCount * 6, BufferUsage.WriteOnly);
short[] indices = new short[maxParticlesCount * 6]; for (int index = 0; index < maxParticlesCount; index++) { indices[index * 6] = (short)(index * 4); indices[index * 6 + 1] = (short)(index * 4 + 1); indices[index * 6 + 2] = (short)(index * 4 + 2); indices[index * 6 + 3] = (short)(index * 4); indices[index * 6 + 4] = (short)(index * 4 + 2); indices[index * 6 + 5] = (short)(index * 4 + 3); } indexBuffer.SetData(indices);

 

La mise à jour va donc parcourir chaque particule pour effectuer le calcul de son évolution:

for (int i = 0; i < particles.Count; i++)
{
    Particle particle = particles[i];
    particle.Age++;

    if (particle.Age >= particle.DeadAge)
    {
        SpawnNewParticle(i);
        continue;
    }
    
particle.Position += particle.Direction * particle.Speed + Gravity;
// Ground collisions if (particle.Position.Y < GroundHeight + particle.Size) { particle.Position.Y = GroundHeight + particle.Size; particle.Direction = particle.Direction + Gravity; particle.Direction.Y = Math.Abs(particle.Direction.Y); }

On peut noter que dès que l'âge est supérieur ou égal au DeadAge, nous appelons SpawnNewParticle(i) qui permet remplacer la particule i par une nouvelle particule.

Une fois que chaque particule a été traitée, il faut la transférer dans le vertex buffer via le code suivant:

for (int i = 0; i < particles.Count; i++)
{
    …
// Update Color color = particle.Color; color.A = (byte) (255 * (particle.DeadAge - particle.Age)
/ (float) particle.DeadAge); Vector3 position = Vector3.Transform(particle.Position, viewMatrix); vertices[index * 4].Position = position + new Vector3(-1, -1, 0) *
particle.Size; vertices[index * 4].Color = color; vertices[index * 4 + 1].Position = position + new Vector3(1, -1, 0) *
particle.Size; vertices[index * 4 + 1].Color = color; vertices[index * 4 + 2].Position = position + new Vector3(1, 1, 0) *
particle.Size; vertices[index * 4 + 2].Color = color; vertices[index * 4 + 3].Position = position + new Vector3(-1, 1, 0) *
particle.Size; vertices[index * 4 + 3].Color = color; currentParticlesCount++; index++; }

Afin de faire en sorte que la particule regarde toujours la caméra, nous partons de la position de la particule et nous la transformons par la matrice de vue pour avoir directement sa position dans le monde de la caméra.

A partir de cette nouvelle position, il suffit de générer 4 vertices qui prennent pour centre cette position et qui se décalent pour former un carré.

De plus, pour être efficace, nous ne travaillons pas avec un VertexBuffer mais avec un DynamicVertexBuffer qui possède l’énorme avantage de pouvoir être modifié par le CPU autant que l’on le souhaite:

vertexBuffer.SetData(vertices, 0, currentParticlesCount * 4);

 

Génération d’une particule


Chaque particule est définie par un ensemble de propriétés que nous avons évoquées plus haut.

La génération d’une particule doit donc renseigner toutes ses propriétés et pour ajouter un peu de hasard dans notre système, nous tirons aléatoirement chaque valeur dans une plage définie par le système:

        void SpawnNewParticle(int index)
        {
            Particle particle = new Particle
{
  Color = Tools.GetRandomColor(MinColor, MaxColor),
Position = Center + new Vector3(Tools.GetRandomFloat(-EmitRadius, EmitRadius),
Tools.GetRandomFloat(-EmitRadius, EmitRadius),
Tools.GetRandomFloat(-EmitRadius, EmitRadius)),
Size = Tools.GetRandomFloat(MinSize, MaxSize), DeadAge = Tools.GetRandomInt(MinDeadAge, MaxDeadAge), Direction = Tools.GetRandomVector3(MinDirection, MaxDirection), Speed = Tools.GetRandomFloat(MinSpeed, MaxSpeed) }; if (index >= 0) { particles[index] = particle; } else particles.Insert(0, particle); }

Ainsi pour définir un système émettant 1000 particules bleus vers le bas, cela donne:

blueSystem = new ParticlesSystem(graphics.GraphicsDevice, 1000)
                {
                    Gravity = new Vector3(0, -0.09f, 0),
                    MinDirection = new Vector3(-1, -1, -1),
                    MaxDirection = new Vector3(1, 0, 1),
                    MinColor = Color.DarkBlue,
                    MaxColor = Color.Blue
                };

 

Gestion de la caméra


Finalement, pour interagir avec notre utilisateur nous allons fournir une caméra de type ArcRotate, c’est à dire qui se déplace sur la surface d’une sphère centrée sur un point cible.

Cette caméra a besoin de deux angles pour définir sa position sur la sphère (Alpha : angle de rotation autour de l’axe Y et Béta : angle de rotation autour de X):

float cosa = (float)Math.Cos(Alpha);
float sina = (float)Math.Sin(Alpha);
float cosb = (float)Math.Cos(Beta);
float sinb = (float)Math.Sin(Beta);

Vector3 cameraPosition = new Vector3(Radius * cosa * sinb, Radius * cosb, 
Radius * sina * sinb);
Vector3 target = new Vector3(0, 0, 0); viewMatrix = Matrix.CreateLookAt(cameraPosition, target, Vector3.Up);

Ce code permet de calculer la View Matrix (la matrice de la caméra) qui indique donc la position de la caméra et ce qu’elle regarde.

Rendu des systèmes


Pour conclure, dans le cadre du rendu de plusieurs systèmes, il nous suffit de régler notre effet pour:

  • Activer la transparence de type NonPremultiplied, c’est à dire que chaque pixel s’écrit avec la formule suivante : AlphaSource * ColorSource + (1 – AlphaSource) * DestinationColor
  • Mettre le DepthBuffer en mode ReadOnly pour que les particules ne se masquent pas entre elles tout en tenant compte des autres objets de la scène
  • Désactivation du culling pour voir les particules de chaque coté (pas d’élimination des faces cachées)

 

Donc cela donne:

// System
graphics.GraphicsDevice.DepthStencilState = DepthStencilState.DepthRead;
graphics.GraphicsDevice.BlendState = BlendState.NonPremultiplied;
graphics.GraphicsDevice.RasterizerState = RasterizerState.CullNone;

effect.View = Matrix.Identity;
effect.Projection = camera.ProjectionMatrix;
effect.Texture = particleTexture;

foreach (EffectPass pass in effect.CurrentTechnique.Passes)
{
    pass.Apply();

    blueSystem.Render();
    yellowSystem.Render();
}

Ajout du sol


Pour donner un point de référence visuel, nous allons ajouter un sol issu d’un modèle FBX (exporté depuis 3DS Max).

L’intégration est simplissime avec XNA:

ground = Content.Load<Model>("ground");

Dans la phase de rendu, avant de dessiner les particules, nous demandons le rendu de notre sol:

// Ground
graphics.GraphicsDevice.DepthStencilState = DepthStencilState.Default;
graphics.GraphicsDevice.BlendState = BlendState.Opaque;
graphics.GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;
graphics.GraphicsDevice.SamplerStates[0] = textureState;
ground.Draw(Matrix.CreateTranslation(0, groundHeight, 0), camera.ViewMatrix, 
camera.ProjectionMatrix);

Conclusion


La touche finale est mise en place avec la gestion du touchscreen pour déplacer le centre de chaque système. Quelques sprites pour faire l’interface utilisateur et le tour est joué.

Grâce à la simplicité de XNA, à l’accèlération matérielle et aux DynamicVertexBuffer, il est possible sur nos WP7 de faire tourner une scène avec plus de 2000 particules en simultané et cela de manière fluide;

Nous vivons une belle époque Sourire

.Net, Windows Phone, Tutorial

Collecto 1.7

9. janvier 2011

Collecto 1.7 est désormais disponible sur le marketplace.

Sauf que, suite à une erreur du marketplace, cette version est “dissociée'” de la 1.6.

Donc si vous voulez mettre à jour, pas de soucis, voila la procédure à suivre:

- Lancer Collecto 1.6
- Vérifier dans le menu Réglages(Settings) que vous êtes bien connecté 
- Le fermer réellement (sans passer par la touche WIndows)
- Désinstaller Collecto 1.6
- Installer Collecto 1.7 et utiliser toujours le même login/pass
- Le tour est joué!

Au programme de cette nouvelle version:

* Les éléments récents peuvent être filtrés
* Support de Amazon.ca
* Correction de bugs mineurs

Et bien sur le deep link: http://social.zune.net/redirect?type=phoneApp&id=ef2b8553-0a18-e011-9264-00237de2db9e

Collecto, Windows Phone

Vivez le keynote de la PDC avec Bewise

28. octobre 2010

Si vous ne savez pas quoi faire ce soir, je vous propose de venir avec moi voir la retranscription simultanée du keynote de la PDC dans les locaux de Microsoft à Toulouse.

Toute la team Bewise sera là et on pourra discuter ensemble de toutes les annonces qui ne manqueront pas de voir le jour.

Pour s’inscrire c’est par là:

http://www.facebook.com/event.php?eid=125098587545246&index=1

L’adresse :
Microsoft - 1 Rue Marie Curie - Parc Technologique du Canal - 31520 Ramonville St-Agne

Windows Phone, .Net, Windows Forms, Windows Mobile, WPF, Visual Studio, Silverlight, DirectX, Bewise

Collecto publié sur le MarketPlace

19. octobre 2010

Ca y est, c’est fait Sourire

Collecto est disponible gratuitement sur le MarketPlace du Windows Phone 7:

Untitled

Windows Phone, Collecto

Nouveau contrôle : VolumeGrid

18. octobre 2010

Je viens de publier un nouveau contrôle sur http://bewisephonecontrols.codeplex.com : le VolumeGrid.

Ce dernier permet d’avoir un pivot un peu différent. En effet, il propose que chacun de ses enfants apparaissent sur la face d’un objet en 3D (un cube si il y a 4 faces par exemple).

 

Exemple d’utilisation de la VolumeGrid.

 

N’hésitez pas à tester Sourire

Windows Phone, .Net

Améliorer le feedback visuel d’une ListBox sur Windows Phone 7

10. octobre 2010

Même si c’est un superbe outil, le Windows Phone 7 n’est pas un PC. C’est notamment vrai au niveau de son CPU qui n’a pas la puissance de celui de nos PC.

De facto, certaines opérations peuvent être couteuse sur le WP7. Par exemple, le binding initial sur une ListBox n’est pas aussi satisfaisant que sur WPF par exemple.

En effet, lorsque l’on va donner l’ItemsSource d’une ListBox sur WP7, l’utilisateur va ressentir un certain “lag” entre le moment ou les données sont affectées au contrôle et le moment ou le contrôle va les afficher. Ceci s’explique par le fait que ce dernier doit construire chaque item (ou au moins ceux qui seront virtualisés, ce qui donne entre 20 et 30 items).

Pour ne pas avoir ce temps d’attente, il suffit de mettre en place une PumpList. Son rôle est simple : Elle va envoyer les items les uns après les autres et ainsi laisser le contrôle s’afficher tout de suite.

Pour avoir un exemple concret, rendez-vous sur http://bewisephonecontrols.codeplex.com ou vous trouverez la classe suivante:

 public class PumpList<T>
    {
        public event EventHandler OnPumpCompleted;

        ObservableCollection<T> items = new ObservableCollection<T>();
        readonly DispatcherTimer timer = new DispatcherTimer();       
        readonly IList<T> source;

        readonly int totalPumpItems;
        int currentPumpItem;

        public ObservableCollection<T> Items
        {
            get { return items; }
        }

        public PumpList(IList<T> items)
        {
            source = items;

            totalPumpItems = source.Count;

            timer.Tick += timer_Tick;
        }
       
        public void StartPump()
        {
            timer.Start();
        }

        void timer_Tick(object sender, EventArgs e)
        {
            Items.Add(source[currentPumpItem]);
            currentPumpItem++;

            if (currentPumpItem >= totalPumpItems)
            {
                timer.Stop();

                if (OnPumpCompleted != null)
                    OnPumpCompleted(this, EventArgs.Empty);
            }
        }
    }

.Net, Windows Phone

Collecto, a killer app…

7. octobre 2010

Nouveau contrôle : RatingControl

3. octobre 2010

Nouveau contrôle pour la librairie Bewise.Phone.Controls (http://bewisephonecontrols.codeplex.com):

image.axd

.Net, Windows Phone ,