Paso 3: Kinetic.js: constelaciones sugerentes
Cuando entrecerrar los ojos, una constelación se parece a algo.
Mi enfoque aquí fue, otra vez, mirar a través de las constelaciones. Las constelaciones VI rara vez veía como cosas concretas (me pareció que el León de una persona podría ser ratón de otra persona, o incluso sólo un cuadrado con unas líneas que salen de él), pero parecen compartir una sugestiva calidad derivada de algunos simples y consistente
Reglas geométricas:
-Hay no hay líneas de cruce
-Puntos (estrellas) conectan sobre todo con estrellas adyacentes o cerca adyacente. Es inusual tener líneas perceptiblemente más de largo.
-Tiende a ser uno (a veces cero, a veces dos) polígono cerrado... un "cuerpo" de algún tipo
-Puntos tienen uno, dos, tres o cuatro conexiones. Casi nunca hay cinco conexiones a un único punto.
-Constelaciones consisten en aproximadamente 20 3 estrellas
En pseudo código, es algo como esto:
De primer paso:
-Comenzar con una estrella al azar
-Proponer una línea a la más cercana estrella sin adjuntos
-Prueba de que esta línea no cruza las líneas existentes
-Dibujar esta línea si pasa, no si no
-Pasar a la siguiente estrella más cercana
-Repetición
Segundo paso:
-Encontrar estrellas sin conexiones
-Encontrar al menos una línea de cruce de no sacar de estas estrellas para conectarlos
Tercer paso:
-Agregar un puñado de líneas sin conexión
Y por último, el código real que terminó con:
function ConstellationMaker3D(options) {si (_.isUndefined(THREE) || _.isUndefined(Galaxy) || _.isUndefined(Galaxy.Utilities) || _.isUndefined(Galaxy.TopScene)) {throw new Error ("dependencias falta de ConstellationMaker3D");} / / ConstellationMaker3D es una función de un objeto de la cámara porque las reglas 2 dimensiones necesitan una proyección particular al trabajo de this.init(options);} ConstellationMaker3D.prototype.init = function(options) {var cámara = options.camera || Galaxy.Utilities.makeTemporaryCamera(); var nodos = options.nodes; _.bindAll (este, ' getConnections'); this.Camera = Cámara; Three.js cámara objeto this.nodes = this.projectPoints(nodes); De Vector2 (math--representación aplanada de XYZ puntos) this.segments = []; De línea 3 (matemáticas). Estos son segmentos de línea 2D; los 3d son prestados, pero no forma parte de la constelación construcción this.connections = []; Matriz de identificadores instructable conectados. es decir, [[id1, id2], [id2, id3]] this.disconnectedNodes = [] ;// de Vector3 aún no tratados con this.lineObject = null; TRES. Line() objeto this.calculateConstellation(); Si (options.hidden! == true) this.displayConstellation(); }; ConstellationMaker3D.prototype.projectPoints = function(vector3List) {var que = esto; volver _.map(vector3List,function(vec) {var posición = Galaxy.Utilities.vectorWorldToScreenXY(vec,that.camera), vec2 = tres nuevos. Vector2(Position.x,position.y); vec2.instructableId = vec.instructableId; volver vec2; }); }; ConstellationMaker3D.prototype.spatialPointsForConnections = function(connectionList) {volver _.map(connectionList,function(connectionPair) {return Galaxy.Utilities.worldPointsFromIbleIds(connectionPair);});}; ConstellationMaker3D.prototype.displayConstellation = function(callback) {/ / lugar de tres. Objetos JS correspondientes a los objetos calculados en la escena var connectedPoints3d = this.spatialPointsForConnections(this.connections); var que = esto; Si (! _.isEmpty(connectedPoints3d)) {/ / inicializar geometría, añadir el primer punto var lineGeometry = tres nuevos. Geometry(); Conectar puntos posterior a lo largo de la cadena de puntos conectados _.each(connectedPoints3d,function(pair) {var closerPair = par; lineGeometry.vertices.push (closerPair [0]); lineGeometry.vertices.push (closerPair [1]);}); Mostrar el línea var material = tres nuevos. LineBasicMaterial ({linecap: "redondo", color: 0xffffff, grosor de línea: 2, transparente: cierto, opacidad: 0.5}); this.lineObject = tres nuevos. Línea (lineGeometry, material, tres. LinePieces); this.lineObject.name = "constelación"; Galaxy.TopScene.add (this.lineObject); } Si (typeof callback === "función") {callback();}}; ConstellationMaker3D.prototype.movePointsCloser = function(pair) {/ / parte de mostrar las líneas de constelación es acortamiento de los segmentos para efecto gráfico. end1 var = pair[0].clone(); var alfinal2 = pair[1].clone(); / / Mueva cada punto hacia el otro 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(), que = esto; mientras que (this.nodes.length > 0) {currentNode = this.addSegmentFromNode(currentNode);}}; ConstellationMaker3D.prototype.closestNodeToNodeFromNodeSet = function(testNode,nodesToTest) {_.each(nodesToTest,function(potentialNextNode) {potentialNextNode.distance = testNode.distanceTo(potentialNextNode);}); var ordenado = _.sortBy(nodesToTest,"distance"); retorno clasificadas;} ConstellationMaker3D.prototype.findLineLineIntersection = function(line1,line2) {var eqn1, eqn2, intx, inty; / si las dos líneas comparten un fin (es decir, se dibujan del mismo nodo), pasar si (this.shareEndpoint (line1, line2) === true) return false; eqn1 = this.equationForLine(line1); eqn2 = this.equationForLine(line2); / / mismo cuesta = hay intersección si (eqn1.m == eqn2.m) devolver false; / / valor x del punto de intersección intx = (eqn2.b - eqn1.b) / (eqn1.m - eqn2.m); / / valor y de intersección punto inty = eqn1.m * intx + eqn1.b; / Si x o y están fuera del alcance de cualquier línea de , no es ninguna intersección var = {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) devuelven el valor false; Si (inty < range.miny || inty > range.maxy) devuelven el valor false; rango = {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) devuelven el valor false; Si (inty < range.miny || inty > range.maxy) devuelven el valor false; verdaderas; } ConstellationMaker3D.prototype.equationForLine = function(line) {/ / de ecuación almacenar m & b de y = mx + b var m, b; / / pendiente m = (line.end.y - line.start.y) / (line.end.x - line.start.x); / / intersección: b = y-mx. Sub en valores desde un punto conocido. b = line.end.y - m * line.end.x; volver {m: m, b: b}; } ConstellationMaker3D.prototype.shareEndpoint = function(line1,line2) {si (line1.start.x == line2.end.x & & line1.start.y == line2.end.y) devuelve verdadero; si (line1.end.x == line2.start.x & & line1.end.y == line2.start.y) devuelve verdadero; si (line1.end.x == line2.end.x & & line1.end.y == line2.end.y) devuelve verdadero; si (line1.start.x == line2.start.x & & line1.start.y == line2.start.y) devuelve true; devolver 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]); volver nextNodeList [0];} ConstellationMaker3D.prototype.connectNodeMultipleTimes = function(node,times) {var = más cercano this.closestNodeToNodeFromNodeSet(node,this.allNodes), lineCount = 0; para (var me = 2; i < closest.length & & lineCount < veces; 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 que = este, intersectionFound = false; _.each(this.segments,function(testSegment) {var se cruzan = that.findLineLineIntersection.apply (, [testSegment proposedLine]); si (se cruzan === true) {intersectionFound = true;}}); vuelta intersectionFound;} ConstellationMaker3D.prototype.lineConnectingNodes2D = function(node1,node2) {return tres nuevos. Línea 3 (tres nuevos. Vector3 (node1.x,node1.y,0), tres nuevos. Vector3(node2.x,node2.y,0)); } ConstellationMaker3D.prototype.getConnections = function(instructableId) {/ / devuelve una matriz de instructable id's id suministrado tiene conexiones. var plana = _.uniq(_.flatten(this.connections)); var index = _.indexOf(flat,instructableId); switch(index) {caso -1: volver []; caso 0: retorno [plano [1]]; flat.length-1 caso: retorno plana [flat.length-2]; por defecto: volver [plano [índice-1], plana [índice + 1]];} console.log (instructableId + ' encontrado en ' + índice + ' en ' + plano);}
El paso de KineticJS a ThreeJS decididamente complica las cosas. Las constelaciones son fundamentalmente 2d en la naturaleza: son las conexiones entre puntos en 3 dimensiones (incluso si le preguntas a Ptolomeo), pero la constelación sí mismo predispone una perspectiva particular de la tierra. Líneas que parecen no poder cruzar de hecho pueden cruzar cuando se los ve desde el lado, como lo hacen en la demostración interactiva.
Puesto que ThreeJS funciona en objetos 3d, un método de colapsar los datos a un plano de cámara llegó a ser necesario. Introduje algunos métodos de utilidad para obtener la pantalla de coordenadas XY de un punto XYZ del mundo, dado una posición de cámara: