JeuWeb - Crée ton jeu par navigateur

Version complète : [Optimisation] updateTransform
Vous consultez actuellement la version basse qualité d’un document. Voir la version complète avec le bon formatage.
Bonjour,

Avec l'aide d'un ami, nous essayons actuellement de "simuler" un plan texturé en 3D sur pixi.js.
Puisque la perspective n'est pas possible, nous avons fait de la transformation affine d'images.
Pour cela, nous découpons un plan en n*n morceaux, chacun de ces morceaux sont constitués de 2 triangles.
Chaque triangle est un masque permettant d'afficher son morceau du sprite correspondant (déformé).

La fonction updateTransform est une fonction de DisplayObject permettant a la base d'afficher un objet en prenant compte de sa rotation.

Voici la fonction initiale de pixi.js :
Code :
PIXI.DisplayObject.prototype.updateTransform = function()
{
    // TODO OPTIMIZE THIS!! with dirty
    if(this.rotation !== this.rotationCache)
    {

        this.rotationCache = this.rotation;
        this._sr =  Math.sin(this.rotation);
        this._cr =  Math.cos(this.rotation);
    }

   // var localTransform = this.localTransform//.toArray();
    var parentTransform = this.parent.worldTransform;//.toArray();
    var worldTransform = this.worldTransform;//.toArray();

    var px = this.pivot.x;
    var py = this.pivot.y;

    var a00 = this._cr * this.scale.x,
        a01 = -this._sr * this.scale.y,
        a10 = this._sr * this.scale.x,
        a11 = this._cr * this.scale.y,
        a02 = this.position.x - a00 * px - py * a01,
        a12 = this.position.y - a11 * py - px * a10,
        b00 = parentTransform.a, b01 = parentTransform.b,
        b10 = parentTransform.c, b11 = parentTransform.d;

    worldTransform.a = b00 * a00 + b01 * a10;
    worldTransform.b = b00 * a01 + b01 * a11;
    worldTransform.tx = b00 * a02 + b01 * a12 + parentTransform.tx;

    worldTransform.c = b10 * a00 + b11 * a10;
    worldTransform.d = b10 * a01 + b11 * a11;
    worldTransform.ty = b10 * a02 + b11 * a12 + parentTransform.ty;

    this.worldAlpha = this.alpha * this.parent.worldAlpha;
};

Nous avons donc réécrit cette fonction et nous l'avons appliquée a tous les objets concernés (Ici, c'est seulement la fonction pour les triangles inférieurs).

Code :
            scope.sprite[k][l].updateTransform = function(){
                
                    var i = this.idI;
                    var j = this.idJ;
                    var n = scope.n;
                  
                    
                        var w = scope.w;
                        var h = scope.h;
                        var xm = scope.xm;
                        var ym = scope.ym;
                        var zm = scope.zm;    
                        var c = Math.cos(scope.angle);
                        var s = Math.sin(scope.angle);
                        var axe = scope.axe;
                        var ux = axe[0]/Math.sqrt(axe[0]*axe[0]+axe[1]*axe[1]+axe[2]*axe[2]);
                        var uy = axe[1]/Math.sqrt(axe[0]*axe[0]+axe[1]*axe[1]+axe[2]*scope.axe[2]);
                        var uz = axe[2]/Math.sqrt(axe[0]*axe[0]+axe[1]*axe[1]+axe[2]*axe[2]);
                    
                    
                        var r00 = ux*ux+(1-ux*ux)*c,
                            r01 = ux*uy*(1-c)-uz*s,
                            r02 = ux*uz*(1-c)+uy*s,
                            r10 = ux*uy*(1-c)+uz*s,
                            r11 = uy*uy+(1-uy*uy)*c,
                            r12 = uy*uz*(1-c)-ux*s,
                            r20 = ux*uz*(1-c)-uy*s,
                            r21 = uy*uz*(1-c)+ux*s,
                            r22 = uz*uz+-(1-uz*uz)*c;
                    
                        var x = -r00*w/2-r01*h/2+xm;
                        var y = -r10*w/2-r11*h/2+ym;
                        var z = -r20*w/2-r21*h/2+zm;
                        
                        var imagetriangle = [transformPoint([r00*(-w/2+i*(w/n))+r01*(h/2-(n-j)*h/n)+r02*0+xm,r10*(-w/2+i*(w/n))+r11*(h/2-(n-j)*h/n)+r12*0+ym,r20*(-w/2+i*(w/n))+r21*(h/2-(n-j)*h/n)+r22*0+zm]),transformPoint([r00*(-w/2+(i+1)*(w/n))+r01*(h/2-(n-j)*h/n)+r02*0+xm,r10*(-w/2+(i+1)*(w/n))+r11*(h/2-(n-j)*h/n)+r12*0+ym,r20*(-w/2+(i+1)*(w/n))+r21*(h/2-(n-j)*h/n)+r22*0+zm]),transformPoint([r00*(-w/2+i*(w/n))+r01*(h/2-(n-j-1)*h/n)+r02*0+xm,r10*(-w/2+i*(w/n))+r11*(h/2-(n-j-1)*h/n)+r12*0+ym,r20*(-w/2+i*(w/n))+r21*(h/2-(n-j-1)*h/n)+r22*0+zm])];
                    
                        this.myMask.clear();
                        this.myMask.beginFill();
                        this.myMask.moveTo(imagetriangle[0][0],imagetriangle[0][1]);
                        this.myMask.lineTo(imagetriangle[1][0],imagetriangle[1][1]);
                        this.myMask.lineTo(imagetriangle[2][0],imagetriangle[2][1]);
                        this.myMask.endFill();

                        this.position.x = imagetriangle[2][0]+(n-j-1)*(imagetriangle[2][0]-imagetriangle[0][0])-i*(imagetriangle[1][0]-imagetriangle[0][0]);  
                        this.position.y = imagetriangle[2][1]-i*(imagetriangle[1][1]-imagetriangle[0][1])+(n-j-1)*(imagetriangle[2][1]-imagetriangle[0][1]);
                        
                        var parentTransform = this.parent.worldTransform;
                        var worldTransform = this.worldTransform;
                        
                        
                        var a00 = (imagetriangle[1][0]-imagetriangle[0][0])*n/w,
                            a10 = (imagetriangle[1][1]-imagetriangle[0][1])*n/w,
                            a01 = -(imagetriangle[2][0]-imagetriangle[0][0])*n/h,
                            a11 = -(imagetriangle[2][1]-imagetriangle[0][1])*n/h,
                            a02 = this.position.x,
                            a12 = this.position.y,
                            b00 = parentTransform.a, b01 = parentTransform.b,
                            b10 = parentTransform.c, b11 = parentTransform.d;
                        
                        worldTransform.a = b00 * a00 + b01 * a10;
                        worldTransform.b = b00 * a01 + b01 * a11;
                        worldTransform.tx = b00 * a02 + b01 * a12 + parentTransform.tx;
                        
                        worldTransform.c = b10 * a00 + b11 * a10;
                        worldTransform.d = b10 * a01 + b11 * a11;
                        worldTransform.ty = b10 * a02 + b11 * a12 + parentTransform.ty;
                        
                        this.worldAlpha = this.alpha * this.parent.worldAlpha;    
                        
                        
                    
                }

Voici une petite demo: http://ks3355883.kimsufi.com/Train3/test2.html
(Demo avec une découpe = 8, soit 8² x2 = 128 triangles)

Malheureusement, les performances ne sont pas bonnes (fps inferieurs a 60, sur une marchine basique, avec une découpe = 3), y aurait-il un moyen d'optimiser ce code ?

Il semblerait que l'utilisation de masques y soit pour beaucoup, mais nous n'avons pas trouvé d'autres moyen pour découper un sprite (De la meme manière que le clip(), de canvas).






Merci d'avance


Ps: Escusez pour l'indentation, elle est propre sur notepad++ mais ici elle est horrible x)
Voici le lien de l'exemple en zip: http://ks3355883.kimsufi.com/Train3.zip

Les web workers sont-ils intéréssants pour ce genre de choses?
Je n'ai pas envie de plonger dans le code, donc je réponds un peu à coté de la question, sur la base de l'exemple proposé: as-tu envisagé d'utiliser CSS3 plutôt que canvas? La transformation avec l'effet de perspective est une propriété (perspective, perspective-origin et transform) en CSS3
Merci pour ta réponse Smile
Le résultat avec CCS3 est très sympa. (Bien qu'il y ait quelques freezes). Très joli ours soit dit en passant Big Grin

Je n'utilise pas vraiment Canvas, Pixi.js utilise WebGL quand le navigateur le permet, ce qui permet d'avoir "normalement" un résultat très fluide.
Dans mon code, je n'utilise Canvas qu'une seule fois, pour redimensionner la texture.

Le truc, c'est que, pour découper en triangle, Canvas utilise clip(), alors que l'on n'a pas trouvé d'autre moyen que d'utiliser des masques avec pixi.js (Au final on découpe pas vraiment, on cache ce que l'on ne veut pas). Et je doute que ce soit la meilleure solution.


Pour ce qui est de CSS3, notre but et d'utiliser des plans a la fois pour simuler un plateau de jeu, et pour des animations de cartes.
Le plateau de jeu pourra etre pivoté dans tous les sens a la souris, et les animations des cartes seront certainement complexes.
Penses tu que CSS3 est adapté a ce genre de choses, et sera suffisamment performant?

Merci encore
CSS devait tenir la route, oui, la question sera plutôt de savoir si le DOM HTML pourra suivre. Je te conseille de tester, c'est le mieux à faire Wink Je ne sais pas combien de cases le plateau contient, donc je ne peux pas te dire si cela passera ou pas en CSS.
Quand je dis "au lieu de canvas", cela s'applique aussi à WebGL. WebGL n'est que l'API qui s'affiche dans canvas. L'autre API, "2d", fait pareil et s'affiche dans le canvas. Donc, l'API change, mais les deux cas utilisent canvas Wink

Un autre atout intéressant sera que les vieux navigateurs afficheront le HTML normal, sans la perspective, les nouveaux afficheront le HTML en perspective. Avec canvas, les vieux navigateurs n'afficheraient rien du tout. Mais bon, par "vieux", j'entends en fait surtout IE<=8...

Je ne sais pas si SVG propose ce genre de transformations... faudrait que je regarde (mais de mémoire, je ne crois pas, car seules les transformations affines existent en SVG).
Le plateau sera de 13/7 cases.
On va essayer en CSS3, mais ca a l'air plus compliqué de gérer le "monde 3D" derrière, j'entends ici le point d'observation, le plan de projection, et le plan a projeter. D'autant plus que nous comptons projeter nous même le quadrillage pour plus de visibilité.
Les pions du plateau seront des Spines (http://esotericsoftware.com) lorsque le plan sera bloqué en position perspective, et des icones planes lorsque le plan sera en mouvement libre. Du fait de l'utilisation des Spines, l'utilisation de canvas sera de toute manière incontournable.

Deux solutions sont donc envisageable:
-Utiliser CSS3 pour calculer les images en perspective et les récupérer pour les afficher dans le canvas. (Certains utilisent CSS3 pour gerer les feuilles de sprites plutot que Canvas, c'est donc potentiellement intéressant)
-Optimiser le code (Pour le moment je n'ai que l'utilisation de workers en tete, ainsi que contourner l'utilisation de masques, peut etre réduire les appels de scope aussi)

Pour SVG, il suffirait de faire le même script que le notre, découper en plusieurs petites transformations affines pour donner la perspective. Mais la aussi, il faudrait en plus utiliser Canvas pour afficher les Spines.

On va essayer les deux solutions et comparer. Si tu en vois d'autres n'hésites pas Big Grin

Merci encore !
13*7 sera parfaitement acceptable Smile
Le point d'observation se place par rapport à "perspective-origin" (left/center/right top/center/bottom) et par rapport à la distance verticale (perspective, souvent en px, autres unités CSS acceptés je crois).
Pourquoi "projeter nous même pour plus de lisibilité"? La projection CSS fera toujours mieux (ou pareil) que celle faite main, la seule différence sera le design de la chose à projeter. D'ailleurs, pour faire une quadrillage, il suffit d'utiliser les propriétés "border" du CSS, t'y gagneras en temps Wink
Pour l'animation, dès l'instant où elle est affichable dans le DOM (en SVG par exemple), elle passera sans soucis. Tu peux aussi utiliser des petits canvas dans la perspective SVG Wink Donc, dans le DOM, il y aurait 13*7 cases (genre table/tr/td), dans chaque case avec un pion, un canvas (pour afficher le pion), et le CSS met la table complète en perspective. Le javascript peut alors servir à changer le point d'observation de la perspective, à tourner la table (plateau), etc.