Bonjour,
Je cherche à implémenter un graphe de scène en Javascript mais j'ai du mal à trouver une bonne définition, savoir ce qu'il y a dans geode, node ...
Je ne sais pas non plus si c'est possible en Javascript !
Pourriez vous m'aider ? Merci :)
Version imprimable
Bonjour,
Je cherche à implémenter un graphe de scène en Javascript mais j'ai du mal à trouver une bonne définition, savoir ce qu'il y a dans geode, node ...
Je ne sais pas non plus si c'est possible en Javascript !
Pourriez vous m'aider ? Merci :)
Salut,
Créer un arbre en JavaScript est tout à fait possible, le plus simple est d’ajouter une propriétée Childs et Parent a un objet pour l’insérer dans un arbre
un truc du genre :
Si tu cherche à étudier l’architecture d’un excellent moteur 3D en Javascript, je te conseil de jeter un œil a O3D de googleCode:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 function Attach(parent,child) { if(parent.Childs==undefined) parent.Childs = [ child ]; else parent.Childs.push(child); child.Parent = parent; } var earth = { Name : "Earth", Matrix : [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], }; var moon = { Name : "Moon", Matrix : [ 1, 0, 0, 384467, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], }; Attach(earth,moon); alert(earth.Childs[0].Name); //"Moon" alert(moon.Parent.Name); // "Earth"
En effet, O3D a l'air d'être pas mal foutu. Je vais essayer de comprendre un peu les détails et "mettre les mains dans le cambouis" lol pour développer mon propre moteur.
Faire un moteur de type scénographe est évidemment tout à fait possible en Javascript ; je me suis d'ailleurs amusé à en faire un pour développer des jeux sur la freebox (cf. ma signature).
Les quelques idées de base:
- si tu est un minimum familier avec d'autres langages orientés objet (genre C++, Java), je te conseille de passer par une librairie additionnelle pour te donner un moyen d'écrire du code 'simple et limpide' (pour qui a déjà touché aux langages sus-cités) avec les principales fonctionnalités 'objet' à portée de main: constructeurs, héritage, ... Perso je me suis tourné vers Base.js.
- l'architecture des classes est assez simple au premier abord. Exemple:
* un singleton qui va s'occuper de tout ce qui concerne le contexte (chargement, canvas, gestion du temps, ...). Appelons le 'Stage'.
* un singleton pour gérer tout ce qui touche aux inputs (clavier, souris, ...).
* une classe 'Scene' qui sera la classe de base qui contient le graphe de scène et les animations en cours dans la scène. Le Stage exécute et affiche une et une seule scène à la fois.
* une classe générique représentant un élément dans le graphe (noeud ou feuille) avec les caractéristiques qui y sont rattachées, genre: position, taille, visibilité, alpha. Appelons là 'Node'.
* une classe dérivant de 'Node' qui représente un noeud dans le graphe, et qui est donc capable de contenir des enfants (typiquement avec une liste chainées). Appelons la 'GroupNode'.
* des classes dérivant de 'Node' qui représentent les feuilles de ton graphe et qui -elles- sont capables de se dessiner: une BitmapNode, TextNode, RectangleNode, LineNode, ... ; chacune a en plus des propriétés communes (position, taille, ...) des paramètres spécifiques (url de l'image, couleur du rectangle, ...).
Voilà pour la partie 'graphe de scène' ; tu poruras ensuite raffiner:
- avec des objets graphiques plus complexes, par exemple une classe 'Button' qui dérive de GroupNode et qui est composé d'une image de fond (le bouton), et d'une texte par au dessus (le label).
- avec la gestion des animations et des événements dans le temps.
- etc...
Mais comme le dit p3ga5e, il est plus qu'opportun de s'inspirer des libs existantes, de s'en faire deux ou trois pour comprendre leur fonctionnement et leur architecture interne. Ca permet non seulement de voir ce qui est commun à toutes les libs et ce qui peut être amené à varier en fonction des sensibilités propres et des besoins de leurs auteurs.
Perso si tu connais un peu Java, je te conseille de jeter un oeil du côté de la façon dont est organisée la lib PulpCore que j'ai un peu pratiqué et dont je me suis directement inspiré pour ma propre lib JS. Pour moi, elle a une approche extrêmement intéressante pour abstraire la notion de 'propriété' associées aux sprites (couleur, alpha, position, visibilité, ...) pour ensuite pouvoir les réutiliser de façon très élégante et générique dans les animations.
My 2 cents.
Merci pour toutes ces informations. :)
Pour l'instant, j'ai créé 3 classes :
- WebGL_Node qui va plutôt gérer tout ce qui est arbre. J'ai pensé que dans un noeud, un a un objet et une transformation( 1 matrice). Chaque noeud possède 1 identifiant.
Au début, je pensais à grouper Node et GroupNode mais je ne sais pas si c'est une bonne idée. De plus, est ce que seul les feuilles sont capable de dessiner ? Si c'est le cas ma structure ne va pas.
- WebGL_Context qui serait la classe singleton dont tu parles. Elle spécifie la taille du canvas et le navigateur a utilisé ainsi que le contexte selon le navigateur
- WebGL_Object qui implémenterait les fonctionnalités de base. Construire un cube, une sphère ...
Je ne comprend pas trop l'utilisation de Base.js. En effet,dans les classes par prototypage, toutes les méthodes sont séparées et forment plusieurs blocks alors que dans l'autre méthode tout est réunit dans un seul block.
Du coup, cela serait plus lourd dans des appels de fonction de toujours appeler toute la classe alors que avec prototype on appele juste la méthode de la classe qui nous intéresse non ?
C'est sujet à débat ; pour moi le groupe et les feuilles sont des fonctionalités séparées. Mais ça peut se discuter.
C'est juste que les blocs distincts sont passés à Base.js au moment de la définition de la classe en les regroupant dans une collection de blocs pour ne faire qu'un appel à 'extend' pour que le code soit plus joli. Ca n'empêche que le résultat est le même.Citation:
toutes les méthodes sont séparées et forment plusieurs blocks alors que dans l'autre méthode tout est réunit dans un seul block
Non, c'est juste une couche syntaxique ; au niveau perfs pour les accès aux méthodes / fonctions c'est totalement équivalent.Citation:
Du coup, cela serait plus lourd dans des appels de fonction de toujours appeler toute la classe alors que avec prototype on appele juste la méthode de la classe qui nous intéresse non ?
Il y a (peut-être) juste une légère perte pour deux opérations beaucoup moins récurrentes: l'instanciation (new) et la conso mémoire ; à vérifier mais pour moi c'est plutôt négligeable,alors que je suis pourtant dans un environnement extrêmement contraint (un MIPS à 200MHz et quelques dizaines de Mo de RAM pour faire tourner l'ensemble du système).
Après, comme dit précédemment je partage juste cette lib qui m'a plu parce qu'elle correspondait bien à mon approche du JS: une grosse expérience en C++ et Java et très peu en JS. Mais c'est pas pour ça que tu dois te sentir obligé de l'utiliser (et ce n'est sûrement pas la seule qui existe non plus).
J'ai fait la classe GroupNode. J'aimerai te demander ton avis, si c'est un "truc" comme ca qu'il faut que je fasse :
tu en penses quoi ?Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72 /** * @author sylvain */ var WebGL_GroupNode = Base.extend({ //////////////////////////////////////////////////////////////////// // Constructeur de la classe Node // // // //////////////////////////////////////////////////////////////////// constructor : function() { this.children = []; }, //////////////////////////////////////////////////////////////////// // Méthode pour ajouter un objet à la liste chainée GroupNode // du node correspondant // Entrée : l'enfant // Sortie : le GroupNode mis à jour //////////////////////////////////////////////////////////////////// addChild : function(child) { var c = this.children.push(child); return c; }, //////////////////////////////////////////////////////////////////// // Méthode pour supprimer un élément d'enfant // // // //////////////////////////////////////////////////////////////////// removeChildren : function() { var children = this.children; if(children.length !=0) { this.children.length = 0; } }, removeChild : function(child) { for(var i = 0 ; i < this.children.length ; i++) { if(this.children[i]=== child) { this.children.splice(i,1); } } }, //////////////////////////////////////////////////////////////////// // Méthode pour supprimer un enfant // // // //////////////////////////////////////////////////////////////////// indexOfChild : function(id) { this.children.split(id,1); } });
Je ne suis pas tout a fait d’accord avec l’approche POO quant on travaille sur une platform JavaScript, car tous ces héritages et polymorphisme a un cout, sur la maintenance du code, la flexibilité et peut être aussi sur les performances.
De ce que j’ai constaté, en Javascript la reflexivité ne coute rien contrairement a des langages comme C#.n'est pas plus rapide queCode:var v= obj.Value
Il existe une autre approche, en utilisant le prototypage pour construire un abre:Code:var v= obj["Value"]
Cela a l’avantage d’ajouter n’importe quel type d’objet en tant que nœud ou feuille d’un arbre, même des objets WebGL :Code:
1
2
3
4
5
6
7
8
9 Object.prototype.AddAsChild = function(child) { if(this.Children==undefined) this.Children = [ child ]; else this.Children.push(child); child.Parent = this; };
Ensuite il est intéressant de pouvoir déterminer si un objet implémente une interface, pour cela pas besoin de d’une mécanique d’héritage lourde, la réflexivité est largement suffisante :Code:
1
2
3
4
5
6
7
8
9 var root = "Hello !"; var child = [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; root.AddAsChild(child); alert(child.Parent) // "Hello !"
Voila c’été juste pour dire que l’on se passe tres bien d’une approche POO en JavaScript.Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41 /*** * return true if this object implements the interface */ Object.prototype.Implements = function(interface) { for(var property in interface) { if( typeof interface[property] != "string") continue; if(this[property]==undefined || typeof this[property] != interface[property] ) return false; } return true; }; /** * Interface IRender */ var IRender = { Matrix : "object", Render : "function" }; var Shape = { Name : "House", Matrix : [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], }; alert(Shape.Implements(IRender));// false : il manque la methode Render! Shape.Render = function() { alert('Rendering ' + this.Name); }; alert(Shape.Implements(IRender)); // true
Nouknouk: pourquoi deux singletons?
Je suis embêté là.
Quelle est la meilleure méthode d'implémentation en Javascript ?
Voici une autre idée d'implémentation en utilisant Prototype.js
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70 /** * @author sylvain */ //////////////////////////////////////////////////////////////////// // Constructeur de la classe Node // // // //////////////////////////////////////////////////////////////////// var WebGL_GroupNode = Class.create( WebGL_Node, { //////////////////////////////////////////////////////////////////// // Méthode pour ajouter un objet à la liste chainée GroupNode // du node correspondant // Entrée : l'enfant // Sortie : le GroupNode mis à jour //////////////////////////////////////////////////////////////////// addChild : function(child) { var c = this.children.push(child); return c; }, //////////////////////////////////////////////////////////////////// // Méthode pour supprimer un élément d'enfant // // // //////////////////////////////////////////////////////////////////// removeChildren : function() { var children = this.children; if(children.length != 0) { this.children.length = 0; } }, removeChild : function(child) { for(var i = 0 ; i < this.children.length ; i++) { if(this.children[i]=== child) { this.children.splice(i,1); } } }, //////////////////////////////////////////////////////////////////// // Méthode pour supprimer un enfant // // // //////////////////////////////////////////////////////////////////// indexOfChild : function(id) { this.children.split(id,1); } });
Tout comme devoir prendre du temps pour apprendre une nouvelle syntaxe, approche, façon de faire, etc...Citation:
tous ces héritages et polymorphisme a un cout, sur la maintenance du code, la flexibilité et peut être aussi sur les performances.
Question perfs, ça n'a pas réellement d'incidence car les appels aux fonctions une fois l'objet créé sont totalement identiques en interne. A nouveau, la seule différence concerne la surcharge uniquement au moment de l'instanciation. Donc quand tu fais ton 'new MonObjet(params)'. Rien d'autre. Base ne fait que fournir une surcouche pour transformer le code écrit sous 'sa syntaxe' vers le Javascript 'normal'.
Quant à l'approche POO non flexible et maintenable, je te laisse la responsabilité de ces allégations.
On dévie du débat initial et j'ai peur qu'on pourrisse le topic de ce pauvre Sylvain.
Pour conclure là-dessus en un mot comme en 100: je n'ai jamais dit que c'était la solution idéale et encore moins universelle. Juste qu'elle pouvait éventuellement convenir pour qui est habitué à une approche POO (par exemple du fait d'une pratique antérieure de Java ou C++). Rien de plus.
Pars plutôt sur une collection implémentée au moyen d'une liste chaînée plutôt qu'un tableau.
Les tableaux sont uniquement intéressants quand on doit accéder à un élément par son index, mais vont être peu performants quand on va vouloir enlever un élément au milieu du tableau.
Pourtant l'objet Array de Javascript possède une méthode slice avec en paramètre l'identifiant et le nombre de cases à supprimer pour éviter les trous.
En fait depuis tout à l'heure je cherche à faire une liste chainée en Javascript mais comme il n'y a pas de pointeurs je ne vois pas trop comment faire. Je cherchais une alternative.
La librairie osg de Cédric Pinson et o3d n'utilisent pas de liste chainée mais après je ne sais pas si les algorithmes sont efficace.
C'est pas parce qu'une méthode toute faite existe qu'elle rend l'utilisation des tableaux la solution la plus efficace.
Un code brut de mon crû ; probablement très loin d'être parfait, mais ça te donne une idée:Citation:
En fait depuis tout à l'heure je cherche à faire une liste chainée en Javascript mais comme il n'y a pas de pointeurs je ne vois pas trop comment faire. Je cherchais une alternative.
Et son emploi:Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132 ListImpl = { constructor: function(itPrevNextPrefix) { this.prefix = itPrevNextPrefix; this.prefixNext = this.prefix+"_next"; this.prefixPrev = this.prefix+"_prev"; this.first = null; this.last = null; }, append: function(obj) { if (obj[this.prefixNext] || obj[this.prefixPrev]) { return null; } obj[this.prefixNext] = null; obj[this.prefixPrev] = this.last; if (this.last != null) { this.last[this.prefixNext] = obj; obj[this.prefixPrev] = this.last; } if (this.first == null) { this.first = obj; } this.last = obj; return obj; }, prepend: function(obj) { if (obj[this.prefixNext] || obj[this.prefixPrev]) { return null; } obj[this.prefixPrev] = null; obj[this.prefixNext] = this.first; if (this.first != null) { this.first[this.prefixPrev] = obj; } if (this.last == null) { this.last = obj; } this.first = obj; return obj; }, remove: function(obj) { if (obj[this.prefixNext] != null) { obj[this.prefixNext][this.prefixPrev] = obj[this.prefixPrev]; } else { this.last = obj[this.prefixPrev]; } if (obj[this.prefixPrev] != null) { obj[this.prefixPrev][this.prefixNext] = obj[this.prefixNext]; } else { this.first = obj[this.prefixNext]; } obj[this.prefixPrev] = null; obj[this.prefixNext] = null; return obj; }, clear: function() { while ( ! this.isEmpty()) { this.takeFirst(); } }, takeFirst: function() { if (this.first == null) { return null; } else if (this.first[this.prefixNext] == null) { var obj = this.first; this.first = null; this.last = null; return obj; } else { var obj = this.first; obj[this.prefixNext][this.prefixPrev] = null; this.first = obj[this.prefixNext]; obj[this.prefixNext] = null; return obj; } }, takeLast: function() { if (this.last == null) { return null; } else if (this.last[this.prefixPrev] == null) { var obj = this.last; this.first = null; this.last = null; return obj; } else { var obj = this.last; obj[this.prefixPrev][this.prefixNext] = null; this.last = obj[this.prefixPrev]; obj[this.prefixPrev] = null; return obj; } }, size: function(obj) { var i = 0; var it = this.first; while (it != null) { i++; it = it[this.prefixNext]; } return i; }, contains: function(obj) { var it = this.first; while (it != null) { if (it == obj) { return true; } it = it[this.prefixNext]; } return false; }, isEmpty: function(obj) { return (this.first == null); }, previous: function(obj) { return obj[this.prefixPrev]; }, next: function(obj) { return (obj) ? obj[this.prefixNext] : this.first; }, }; List = Base.extend(ListImpl);
"nomListe" permet d'avoir des objets qui appartiennent à plusieurs listes en même temps. Chaque liste doit avoir un nom unique.Code:
1
2
3
4
5
6
7
8 var l = new List("nomListe"); l.append("monObjet"); l.prepend("monAutreObjet"); for (var it = l.first ; it != null ; it = l.next(it)) { alert(it); }
oui il est bien ton algo je comprend le principe.
Il y a un truc qui me gène.
Que veut dire cette ligne ?
Code:obj[this.prefixNext][this.prefixPrev] = obj[this.prefixPrev];
si l'ordre n'est pas important un array est beaucoup plus efficace qu'une liste chainée.
Pour effacer au milieu, il suffit dans ce cas d'echanger l'objet a enlever avec celui a la fin, puis de retirer la fin.
sinon, nouknouk, pourquoi des singletons? :)
- "this.prefixNext" est une chaine de caractère qui représente le nom de l'attribut de obj qui contient une référence vers l'objet suivant.
- même chose pour "this.prefixPrev" sauf qu'il est le nom de l'attribut qui pointe sur l'élément précédent.
Est donc équivalent à quelque chose comme:Code:obj[this.prefixNext][this.prefixPrev] = obj[this.prefixPrev];
Sauf que dans mon implémentation les noms des attributs 'suivant' et 'precedent' sont pas fixes, mais dynmaiques et choisi de façon unique pour chaque liste (le fameux "nomListe" du post précédent) ; cela permet que deux listes qui contiennent un même objet aillent pas trifouiller dans les 'suivant' et 'precedent' de l'autre.Code:obj.suivant.precedent = objet.precedent
Mais l'ordre est important ici.
On ne peut pas afficher un objet dans la scène avant d'avoir fait les bonnes transformations.
C'est une implémentation d'une liste chaînée ; l'ordre est respecté (et heureusement: je me sers de ces itérateurs pour des listes d'objets 2D dans les GroupNodes pour lesquels l'ordre d'affichage est tout aussi important).
Concrètement c'est une partie de l'opération de suppression d'un élément d'une liste (méthode remove): (voir image en pj)
La suppression consiste à mettre à jour ce qui est en rouge dans le schéma. Il y a deux pointeurs à mettre à jour et la ligne de code que tu as donnée correspond à la moitié du boulot, ie. la flêche rouge qui part de 8 et va vers 3.
Concrètement, cette ligne dit: obj étant 15, il dit à 8 que son nouveau précédent n'est plus obj (15), mais le précédent de obj, à savoir 3.
Désolé d'avance pour le hors sujet ^^
pour moi prendre le temps d'apprendre les spécificités d'un language n'est pas une perte de temps mais un investissement !Citation:
Tout comme devoir prendre du temps pour apprendre une nouvelle syntaxe, approche, façon de faire, etc...
C'est juste que les developpeurs semble sous-estimer la liberté qu'offre le faite de s'affranchir de la declaration des meta-données (je parle de notion de class).
je rencontre ce probleme avec tous mes stagiaires Développeurs, ils semblent formatés pour appliquer des design-paterns apprit par coeurs de maniere mecanique ... c'est juste que cela me désole ...
Pour en revenir au sujet de la discutions , je tient a préciser que le type tableau en javascript est un gros FAKE !
en realité un tableau est un objet javascript comme un autre !
pour preuveen realité un type Array est une specialisation d'object :Code:typeof [] == "object" // true
il n'existe aucune difference entre les indices d'un tableau et les proprietés d'un objet !Code:
1
2
3
4
5 var tab = [ 0 ,1, 2, 3]; for(var property in tab) { alert(property == tab[property]); }
C'est pourquoi j'insiste sur le fait d'apprendre les notions d'un langage, un tableau en javascript ne permet aucune optimisation !
ne croyez pas alligner vos données en memoire en utilisant un tableau en Javascript.
tous fois l'approche liste chainées sur les fils d'un noeud peut apporter un gain de performance,car un parcour itératif semble plus performant qu'un parcours recursif en javasprtipt ( cela est difficelement mesurable ) !
Parce que les singletons c'est le mal ... mais dans certains cas bien précis, ne pas les utiliser c'est pire que le mal.
Les singletons ont leur utilité pour des fonctionnalités qui sont par nature uniques dans ton application et auxquelles tu as besoin d'accéder à beaucoup d'endroits épars dans ton code.
Ici, le cas des inputs est un bon exemple: même le plus petit élément de ton graphe de scène peut en avoir besoin (genre une implémentation d'un widget type bouton qui veut savoir si la souris le survole, ...). Vouloir éviter le singleton revient à vouloir se trimballer partout une référence vers ton instance d'Input qui pourtant ne servira à rien dans 90% des cas.
Pour le reste, je ne peux que te renvoyer à toute la prose dispo sur le net qui détaille sans aucun doute les choses bien mieux que moi.
A noter que quand je parle de singleton, je parle du concept de ressource accessible de façon globale depuis n'importe quel endroit du code ; dans cette acception, une simple variable avec une portée globale (et un minimum de rigueur) suffit ; pas besoin forcément de se taper l'implémentation classique avec une classe, sa méthode statique getInstance(), etc... :
Après, c'est sujet à débat (y compris mon choix pour ce cas ci) et d'une façon générale je reste contre le fait de conseiller les singletons car ils sont souvent utilisés à mauvais escient. Mais dans la pratique tout n'est jamais complètement noir ou blanc et ils ont leur utilité quand ils sont utilisés aux bons endroits.Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 // des 'objets globaux' STAGE et INPUT STAGE = { width: 640, height: 480 }; INPUT = { isMouseOver : function(node) { return /* true or false */ } } // blablabla // et somewhere dans mon code: monObjet = { mafonction: function() { this.setPosition(STAGE.width / 2, STAGE.height / 2); } onUpdate: function(tickTime) { if (INPUT.isMouseOver(this)) { doSomething(); } } }
Un autre truc (décidément, je suis en verve ce soir :aie:). qui m'a pas mal apporté de confort syntaxique. C'est peut-être évident pour les habitués du JS, mais en tant que débutant dans ce langage, ça m'a pris du temps avant de tilter (et ce fut coûteux en refactoring). Donc j'en parle dès le début, tant qu'à faire :
A propos du passage des paramètes aux constructeurs des (sous)classes de Node:
Premier jet: mon noeud a une position et une taille. C'est bête comme choux, je lui fait donc un joli constructeur:
Jusque là, c'est super.Code:
1
2
3
4
5
6 // ma classe Rectangle = Node.extend({ constructor: function(x, y, width, height) { /* ... */ } }); // et je l'instancie: monRectangle = new Rectangle(100, 100, 300, 200);
Ah et puis il peut aussi avoir une couleur et être visible ou pas:
Bon, 3 lignes c'est un peu long à écrire, et je suis fainéant.Code:
1
2
3 monRectangle = new Rectangle(100, 100, 300, 200); monRectangle.setColor(255,255,0); monRectangle.setVisible(true);
On va passer ces paramètres directement dans le constructeur et si la valeur est à null on dit qu'on prend la valeur par défaut (genre blanc pour la couleur).
Jusque là ça va encore...Code:monRectangle = new Rectangle(100, 100, 300, 200, null, false);
Puis on va vouloir faire des choses de plus en plus complexes où les paramètres possibles vont s'accumuler.
Genre pour un bouton: position + taille + visible + alpha + image normale + image en hover + image cliquée + label + couleur du label + ...
On se retrouvera avec un constructeur à 12 000 paramètres et des allers retours incessants dans notre doc parce qu'on se demande si "le paramètre de la couleur on le met en 12ème ou 13ème position dans le constucteur de Bouton et en 8ème pour le TextField ?". Je parle même pas de l'absence de clarté du code le jour où on se relit.
Bref, imbuvable à terme.
La petite solution à deux sous: un seul argument au constructeur (params) qui est un objet et contiendra seulement les paramètres nommés et spécifiques qu'on veut appliquer ; les autres seront pris par défaut par la classe elle-même mais aussi par ses classes parentes.
Avec addDefaultParams qui prend les params et va ajouter ceux par défaut pour cette classe là.Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 Button = GroupNode.extend({ constructor: function(params) { // on peuple params avec les valeurs manquantes par défaut: addDefaultParams(params, Button.BUTTON_DEFAULT_PARAMS); // appel du constructeur parent this.base(params); // récup' des paramètres propres à bouton this.image = params.image; this.hoverImage = params.hoverImage; } },{ BUTTON_DEFAULT_PARAMS: { image: "/img/button_default.png", hoverImage: "/img/button_hover_default.png", alpha: 127, // utilisé par la classe PARENTE de Button, width: 300 // utilisé par la classe PARENTE de Button, } });
Si c'est également organisé de la même façon dans chacune des classes parentes , on va pouvoir:Code:
1
2
3
4
5
6
7 addDefaultParams = function(params, defaultParams) { for (var paramName in defaultParams) { if ( typeof(params[paramName]) == "undefined") { params[paramName] = defaultParams[paramName]; } } }
- spécifier des valeurs par défaut spécifique à cette classe là (image, hoverImage)
- surcharger des valeurs par défaut pour notre classe spécifique mais pour des paramètres utilisés par les classes parentes (alpha, width)
- avoir une syntaxe au moment de l'instanciation qui reste simple, concise et limpide et sans souci d'ordre de passage des paramètres, il faut juste se rappeler de leur nom (bien plus facile à retenir):
Et le tour est joué ;)Code:
1
2 var but1 = new Button({x:300, y:500, color:[255,255,0]}); var but2 = new Button({y:600, x:300, width: 150, height: 50, image: "/img/bouton_rond.png", visible: false});
D'autant qu'on va pouvoir passer n'importe quoi dans ces paramètres, y compris par exemple des fonction callback (merci JS pour le coup):
Et encore mieux: le jour où je décide de changer l'image de mon bouton par défaut pour l'ensemble de mes boutons de mon appli, j'ai un seul endroit à changer (ou surcharger), genre au début de mon appli:Code:var but1 = new Button({ y:300, x:500, onClicked: function(btn) {alert("i am button "+btn.label);} });
Code:
1
2
3
4 include("buttonClass.js"); Button.BUTTON_DEFAULT_PARAMS.image: "/img/mon_autre_button_default.png"; // désormais, tous les boutons utilisant l'image par défaut auront cette nouvelle 'skin':
Je suis stagiaire p3ga5e :) !!
Merci des infos nouknouk :) !!
Donc la solution la plus pratique est de faire un arbre n-aire avec pour chaque node un GroupNode.
Du coup GroupNode extends Node !!
Et dans GroupNode on aura toutes les transformations spécifiques à un objet puis son affichage.
C'est probablement pas la seule possibilité ; mais si peu ou prou l'ensemble des libs graphiques retombent sur cette organisation à la fin, c'est pas juste un hasard ou à cause un manque pathologique de créativité de la part de leurs auteurs ;)
A nouveau: je te conseille vraiment en parallèle de jeter un oeil à d'autres libs existantes, que ce soit dans le même langage ou même dans un autre (C++ / Java / ...) ; c'est vraiment comme ça qu'on apprend le plus vite amha.
Comme osgjs ou o3d par exemple ?
Perso, je ne les connais pas. Mais d'une façon générale tout est bon à regarder, d'autant plus si la librairie à déjà 'fait ses preuves' (projet actif, communauté importante, ....) car c'est souvent un gage de qualité.
Encore moi. Perso je galère beaucoup pour faire un arbre n-aire en Javascript.
Que penses tu de ca ?
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79 /** * @author sylvain */ // Classe de gestion des graphes de scènes //////////////////////////////////////////////////////////////////// // Constructeur de noeud du graphe de scène // Arguments : Les parents, les enfants, un identifiant, un objet // et une transformation // //////////////////////////////////////////////////////////////////// var WebGL_Node = Class.create({ initialize : function(obj, node) { this.obj = obj; this.node = node; this.nb_elem = 0; node = new WebGL_GroupNode(null); }, //////////////////////////////////////////////////////////////////// // Méthode pour ajouter un enfant à l'arbre // Entrée : Un objet WebGL, le parent, la matrice de transformation // Sortie : Le nouveau parent // //////////////////////////////////////////////////////////////////// addNode : function ( node) { // L'arbre est nul, on le crée if(node == null) { node = new WebGL_Node(this.obj, this.node); } else { this.node = node; this.nb_elem = this.nb_elem + 1; } return node; }, //////////////////////////////////////////////////////////////////// // Méthode pour supprimer un noeud // // // //////////////////////////////////////////////////////////////////// removeParent : function (id_node) { }, //////////////////////////////////////////////////////////////////// // Méthode pour rechercher un noeud avec son identifiant // Entrée : l'arbre, et l'identifiant // Sortie : le nouvel arbre // //////////////////////////////////////////////////////////////////// searchNode : function (id_node, root) { }, //////////////////////////////////////////////////////////////////// // Méthode pour savoir si un arbre est vide ou non // // // //////////////////////////////////////////////////////////////////// isEmpty : function () { return (this.root == null); } });
J'ai trouvé 2 articles très intéressants
http://www.nczonline.net/blog/2009/0...-linked-lists/
http://www.nczonline.net/blog/2009/0...h-tree-part-1/
:)
j'utiliserais un scenegraph sous forme de DOM ou JSON pour pouvoir importer ma scène plus facilement ... sinon ton scenegraph il te servira pas à grand chose. Pour un scengraph dynamique le DOM est peut-être plus approprié.
il me semble que three.js s'occupe assez bien de ce genre de chose