Étape 3: Kinetic.js : Constellations Suggestive
Quand vous plisser les yeux, une constellation ressemble à quelque chose.
Ici, mon approche a été, encore une fois, de regarder à travers des constellations. Les constellations, j’ai vu rarement ressemblait à des choses précises (il me semblait que lion une personne pourrait être souris d’une autre personne, ou même juste un carré avec quelques lignes venant hors de lui), mais ils ne semblent pas partager une qualité suggestive provenant de quelques simples et cohérente
Règles géométriques :
-Il n’y a pas de lignes de croisement
-Points (étoiles) connecter surtout avec étoiles adjacents ou près adjacente. Il est rare d’avoir des lignes nettement plus longs.
-Il a tendance à être un (parfois zéro, parfois deux) polygone fermé... un « corps » d’un certain type
-Points ont un, deux, trois ou quatre connexions. Il y a presque jamais de cinq connexions à un seul point.
-Constellations se composent d’environ 20-3 étoiles
En pseudo-code, c’est quelque chose comme ceci :
D’abord passer :
-Commencez par une star au hasard
-Proposer une ligne à la plus proche étoile non attaché
-Test que cette ligne ne franchit pas les lignes existantes
-Tracer cette ligne si elle passe, pas si ce n’est pas
-Passer à la prochaine étoile la plus proche
-Répéter
Deuxième passage :
-Trouver les étoiles avec aucune connexion
-Trouver au moins une ligne de non franchissement de tirer de ces étoiles pour les relier
Troisième passage :
-Ajouter une poignée de lignes non-connexion
Et enfin, le code réel avec que je me suis retrouvé :
function ConstellationMaker3D(options) {si (_.isUndefined(THREE) || _.isUndefined(Galaxy) || _.isUndefined(Galaxy.Utilities) || _.isUndefined(Galaxy.TopScene)) {throw new Error ("dépendances manquantes pour ConstellationMaker3D");} / / ConstellationMaker3D est une fonction d’un objet de la caméra, parce que les règles 2 dimensions besoin une projection particulière au travail de la this.init(options);} ConstellationMaker3D.prototype.init = function(options) {var caméra = options.camera || Galaxy.Utilities.makeTemporaryCamera() ; nœuds de var = options.nodes ; _.bindAll (thises, ' getConnections') ; This.Camera = caméra ; Three.js caméra objet this.nodes = this.projectPoints(nodes) ; De Vector2 (math--représentation aplatie de points XYZ) this.segments = [] ; De la ligne 3 (mathématiques). Notez que ce sont des segments de ligne 2D ; les 3d sont rendus, mais ne fait pas partie de la constellation de la construction this.connections = [] ; Tableau d’ID instructable connectés. C’est à dire, [[id1, id2], [id2, id3]] this.disconnectedNodes = [];// de Vector3 pas encore traités, this.lineObject = null ; TROIS. Line() objet this.calculateConstellation() ; Si (options.hidden! == true) this.displayConstellation() ; }; ConstellationMaker3D.prototype.projectPoints = function(vector3List) {var qui = cela ; return _.map(vector3List,function(vec) {position var = Galaxy.Utilities.vectorWorldToScreenXY(vec,that.camera), vec2 = trois nouvelles. Vector2(position.x,position.y) ; vec2.instructableId = vec.instructableId ; retour vec2 ; }); }; ConstellationMaker3D.prototype.spatialPointsForConnections = function(connectionList) {return _.map(connectionList,function(connectionPair) {return Galaxy.Utilities.worldPointsFromIbleIds(connectionPair);});} ; ConstellationMaker3D.prototype.displayConstellation = function(callback) {/ / Place trois. Les objets JS correspondant aux objets calculés dans la scène var connectedPoints3d = this.spatialPointsForConnections(this.connections) ; var qui = cela ; Si (! _.isEmpty(connectedPoints3d)) {/ / initialiser la géométrie, ajouter la première lineGeometry var point = trois nouvelles. Geometry() ; relier les points suivants le long de la chaîne de points connectés _.each(connectedPoints3d,function(pair) {var closerPair = paire ; lineGeometry.vertices.push (closerPair [0]); lineGeometry.vertices.push (closerPair [1]);}) ; afficher la ligne var matériel = trois nouvelles. LineBasicMaterial ({linecap: « ronds », couleur : 0xffffff, largeur: 2, transparent : true, opacité : 0.5}) ; this.lineObject = trois nouvelles. Ligne (lineGeometry, matériel, trois. LinePieces) ; this.lineObject.name = « constellation » ; Galaxy.TopScene.add (this.lineObject) ; {Si (rappel typeof === "fonction") {callback();}} ; ConstellationMaker3D.prototype.movePointsCloser = function(pair) {/ / partie d’afficher les lignes de la constellation est raccourcir les segments pour var end1 graphique effet. = pair[0].clone() ; var end2 = pair[1].clone(); / / déplacer chaque point légèrement vers l’autre diff var = end2.clone().sub(end1.clone()) ; diff.multiplyScalar(0.08) ; return [end1.add(diff.clone()), end2.sub(diff.clone())];} ; ConstellationMaker3D.prototype.clear = function() {si ()! _.isNull(this.lineObject)) {Galaxy.TopScene.remove(this.lineObject);}} ; ConstellationMaker3D.prototype.calculateConstellation = function() {var currentNode = this.nodes.shift(), qui = cela ; while (this.nodes.length > 0) {currentNode = this.addSegmentFromNode(currentNode);}} ; ConstellationMaker3D.prototype.closestNodeToNodeFromNodeSet = function(testNode,nodesToTest) {_.each(nodesToTest,function(potentialNextNode) {potentialNextNode.distance = testNode.distanceTo(potentialNextNode);}); var tri = _.sortBy(nodesToTest,"distance") ; return triés;} ConstellationMaker3D.prototype.findLineLineIntersection = function(line1,line2) {var eqn1, eqn2, intx, inty; / / si les deux lignes partagent une extrémité (c’est à dire, ils sont tirés du même nœud), passer si (this.shareEndpoint (ligne1, ligne2) === vrai) return false ; eqn1 = this.equationForLine(line1) ; eqn2 = this.equationForLine(line2); / / la même pente ne = aucune intersection si (eqn1.m == eqn2.m) retournent false; / / valeur x d’intersection point intx = (eqn2.b - eqn1.b) / (eqn1.m - eqn2.m); / / la valeur y d’intersection point inty = eqn1.m * intx + eqn1.b; / / si x ou y sont hors de portée pour chaque ligne , il n’y a aucune gamme de var intersection = {minx : Math.min(line1.start.x,line1.end.x), maxx : Math.max(line1.start.x,line1.end.x), miny : Math.min(line1.start.y,line1.end.y), maxy : Math.max(line1.start.y,line1.end.y)} ; Si (intx < range.minx || intx > range.maxx) retournent false ; Si (inty < range.miny || inty > range.maxy) retournent false ; gamme = {minx : Math.min(line2.start.x,line2.end.x), maxx : Math.max(line2.start.x,line2.end.x), miny : Math.min(line2.start.y,line2.end.y), maxy : Math.max(line2.start.y,line2.end.y)} ; Si (intx < range.minx || intx > range.maxx) retournent false ; Si (inty < range.miny || inty > range.maxy) retournent false ; retourne la valeur true ; } ConstellationMaker3D.prototype.equationForLine = function(line) {/ / du eqn magasin de m & b de y = mx + b var m, b; / / pente m = (line.end.y - line.start.y) / (line.end.x - line.start.x); / / ordonnée: b = y-mx. Auxiliaire dans les valeurs d’un point connu. b = line.end.y - m * line.end.x ; Return {m: m, b: b} End Function } ConstellationMaker3D.prototype.shareEndpoint = function(line1,line2) {si (line1.start.x == line2.end.x & & line1.start.y == line2.end.y) renvoie true ; si (line1.end.x == line2.start.x & & line1.end.y == line2.start.y) renvoie true ; si (line1.end.x == line2.end.x & & line1.end.y == line2.end.y) renvoie true ; si (line1.start.x == line2.start.x & & line1.start.y == line2.start.y) retourne true ; return false;} ConstellationMaker3D.prototype.addSegmentFromNode = function(node) {var nextNodeList = this.closestNodeToNodeFromNodeSet(node,this.nodes) ; var proposedLine = this.lineConnectingNodes2D(node,nextNodeList[0]) ; if (this.lineIntersectsPriorLines(proposedLine) == true) {this.disconnectedNodes.push(node);} else {this.connections.push([node.instructableId,nextNodeList[0].instructableId]) ; this.segments.push(proposedLine);} this.nodes = _.without(this.nodes,nextNodeList[0]) ; return nextNodeList [0];} ConstellationMaker3D.prototype.connectNodeMultipleTimes = function(node,times) {var = plus proche this.closestNodeToNodeFromNodeSet(node,this.allNodes), lineCount = 0; pour (var i = 2; J’ai < closest.length & & lineCount < fois; i ++) {var proposedLine = this.lineConnectingNodes2D(node,closest[i]) ; if (! this.lineIntersectsPriorLines(proposedLine)) {this.segments.push(proposedLine) ; this.constellationLayer.add(proposedLine) ; lineCount ++;}}} ConstellationMaker3D.prototype.lineIntersectsPriorLines = function(proposedLine) {var qui = ceci, intersectionFound = false ; _.each(this.segments,function(testSegment) {var se croisent = that.findLineLineIntersection.apply (ce, [testSegment, proposedLine]); if (se croisent === vrai) {intersectionFound = true;}}); return intersectionFound;} ConstellationMaker3D.prototype.lineConnectingNodes2D = function(node1,node2) {return trois nouvelles. Ligne 3 (neuf trois. Vector3 (node1.x,node1.y,0), trois nouveaux. Vector3(Node2.x,Node2.y,0)) ; } ConstellationMaker3D.prototype.getConnections = function(instructableId) {/ / retourne un tableau d’id instructable s auquel l’id fourni a plat, connexions. var = _.uniq(_.flatten(this.connections)) ; var index = _.indexOf(flat,instructableId) ; switch(index) {affaire -1 : return []; case 0: retour [plat [1]]; affaire flat.length-1: retour plate [flat.length-2], la valeur par défaut : retourner [plat [index-1], plat [index + 1]];} console.log (instructableId + "trouvé à" + index + ' en ' + plat);}
Le passage de KineticJS à ThreeJS décidément complique les choses. Constellations sont fondamentalement 2d dans la nature : ils sont les connexions entre les points en 3 dimensions (même si vous demandez à Ptolémée), mais la constellation lui-même biaise une perspective particulière de la terre. Lignes qui nous paraissent ne pas traverser peuvent franchir en fait lorsque vous les consultez depuis le côté, comme ils le font dans la démo interactive.
Étant donné que ThreeJS fonctionne sur les objets 3d, une méthode de rétracter les données à un plan de caméra est devenue nécessaire. J’ai présenté quelques méthodes utilitaires pour obtenir l’écran coordonnées XY d’un point XYZ mondiale, étant donné une position de la caméra :