Instructables universum i Three.js (3 / 13 steg)
Steg 3: Kinetic.js: suggestiva konstellationer
När du Kisa, en konstellation ser ut som något.
Min strategi här var, återigen, att titta igenom stjärnbilderna. Konstellationerna såg jag sällan såg ut vissa saker (det föreföll mig att en persons lion kan vara en annan persons mus, eller bara en kvadrat med några rader som kommer ut av det), men de verkar dela en suggestiv kvalitet härledda från några enkla och konsekventa
Geometriska regler:
-Det finns ingen passage linjer
-Poäng (stjärnor) ansluta mestadels med angränsande eller nära intill stjärnor. Det är ovanligt att ha märkbart längre köer.
-Det tenderar att vara en (ibland noll, ibland två) sluten polygon... ett "organ" av något slag
-Poäng har en, två, tre eller fyra anslutningar. Det finns nästan aldrig fem anslutningar till en enda punkt.
-Konstellationerna består av cirka 3-20 stjärnor
I pseudo-kod är det ungefär så här:
Först passera:
-Börja med en random stjärna
-Föreslå en linje till den närmaste un-bifogade star
-Test att denna linje inte korsar någon befintliga linjer
-Dra denna linje om den passerar, inte om det inte
-Flytta till nästa närmaste stjärna
-Upprepa
Andra Pass:
-Hitta stjärnor med inga anslutningar
-Hitta åtminstone en icke-korsning rad att dra av dessa stjärnor att ansluta dem.
Tredje Pass:
-Lägg till en handfull icke-anslutande linjer
Och slutligen, den faktiska koden jag slutade med:
funktion ConstellationMaker3D(options) {
om (_.isUndefined(THREE) || _.isUndefined(Galaxy) || _.isUndefined(Galaxy.Utilities) || _.isUndefined(Galaxy.TopScene)) {
nya fel ("saknade beroenden för ConstellationMaker3D");
}
ConstellationMaker3D är en funktion av en kamera objekt för 2-dimensionella reglerna behöver en särskild projektion att arbeta från
This.init(options);
}
ConstellationMaker3D.prototype.init = function(options) {
var kamera = options.camera || Galaxy.Utilities.makeTemporaryCamera();
var noder = options.nodes;
_.bindAll (här, ' getConnections');
This.Camera = kamera; Three.js kamera objekt
This.Nodes = this.projectPoints(nodes); Vector2's (math--tillplattad representation av XYZ poäng)
This.segments = []; Line3's (math). Observera dessa är 2D linjesegment; de 3d som återges, men inte en del av stjärnbilden byggandet
This.Connections = []; Matris över anslutna instructable ids. dvs, [[id1, id2], [id2, id3]]
this.disconnectedNodes = [] ;// Vector3's ännu inte behandlat
this.lineObject = null; TRE. Line() objekt
this.calculateConstellation();
om (options.hidden! == true) this.displayConstellation();
};
ConstellationMaker3D.prototype.projectPoints = function(vector3List) {
var det =.
återgå _.map(vector3List,function(vec) {
var position = Galaxy.Utilities.vectorWorldToScreenXY(vec,that.camera),
vec2 = nya tre. Vector2(position.x,position.y);
vec2.instructableId = vec.instructableId;
återvända vec2;
});
};
ConstellationMaker3D.prototype.spatialPointsForConnections = function(connectionList) {
återgå _.map(connectionList,function(connectionPair) {
återvända Galaxy.Utilities.worldPointsFromIbleIds(connectionPair);
});
};
ConstellationMaker3D.prototype.displayConstellation = function(callback) {
Placera tre. JS objekt motsvarar de beräknade objekt i scenen
var connectedPoints3d = this.spatialPointsForConnections(this.connections);
var det =.
om (! _.isEmpty(connectedPoints3d)) {
Initiera geometri, lägga till första punkten
var lineGeometry = nya tre. Geometry();
Anslut följande punkter längs kedjan anslutna poäng
_.each(connectedPoints3d,function(pair) {
var closerPair = par;
lineGeometry.vertices.push (closerPair [0]);
lineGeometry.vertices.push (closerPair [1]);
});
Visa raden
var materialet = nya tre. LineBasicMaterial({
LineCap: "rund",
färg: 0xffffff,
LineWidth: 2,
öppet: sant,
opacitet: 0,5
});
this.lineObject = nya tre. Linje (lineGeometry, material, tre. LinePieces);
this.lineObject.name = "konstellation";
Galaxy.TopScene.add (this.lineObject);
}
om (typeof motringning === "funktion") {
callback();
}
};
ConstellationMaker3D.prototype.movePointsCloser = function(pair) {
del av visar stjärnbilden raderna förkorta segmenten för grafisk effekt.
var end1 = pair[0].clone();
var end2 = pair[1].clone();
flytta varje punkt lite mot andra
var diff = end2.clone().sub(end1.clone());
diff.multiplyScalar(0.08);
återkomst [end1.add(diff.clone()), end2.sub(diff.clone())];
};
ConstellationMaker3D.prototype.clear = function() {
om (! _.isNull(this.lineObject)) {
Galaxy.TopScene.remove(this.lineObject);
}
};
ConstellationMaker3D.prototype.calculateConstellation = function() {
var currentNode = this.nodes.shift(), som = detta.
samtidigt (this.nodes.length > 0) {
currentNode = this.addSegmentFromNode(currentNode);
}
};
ConstellationMaker3D.prototype.closestNodeToNodeFromNodeSet = function(testNode,nodesToTest) {
_.each(nodesToTest,function(potentialNextNode) {
potentialNextNode.distance = testNode.distanceTo(potentialNextNode);
});
var sorterade = _.sortBy(nodesToTest,"distance");
återvända sorterade, eller
}
ConstellationMaker3D.prototype.findLineLineIntersection = function(line1,line2) {
var eqn1, eqn2, intx, inty;
om de två linjerna som delar ett slut (dvs, de dras från samma nod), passera
om (this.shareEndpoint (line1, rad2) === sant) return false;
eqn1 = this.equationForLine(line1);
eqn2 = this.equationForLine(line2);
samma lutning = ingen korsning
om (eqn1.m == eqn2.m) return false;
x-värde på skärningspunkten
intx = (eqn2.b - eqn1.b) / (eqn1.m - eqn2.m);
y-värdet i skärningspunkten
Inty = eqn1.m * intx + eqn1.b;
om x eller y är utanför räckvidden för antingen linje, det finns ingen korsning
var intervall = {
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),
Jesper: Math.max(line1.start.y,line1.end.y)
};
om (intx < range.minx || intx > range.maxx) return false;
om (inty < range.miny || inty > range.maxy) return false;
rad = {
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),
Jesper: Math.max(line2.start.y,line2.end.y)
};
om (intx < range.minx || intx > range.maxx) return false;
om (inty < range.miny || inty > range.maxy) return false;
return true;
}
ConstellationMaker3D.prototype.equationForLine = function(line) {
eqn's lagra m & b från y = mx + b
var m, b;
lutning
m = (line.end.y - line.start.y) / (line.end.x - line.start.x);
y-axeln: b = y-mx. Sub i värden från en känd punkt.
b = line.end.y - m * line.end.x;
returnera {m: m, b: b};
}
ConstellationMaker3D.prototype.shareEndpoint = function(line1,line2) {
om (line1.start.x == line2.end.x & & line1.start.y == line2.end.y) return true;
om (line1.end.x == line2.start.x & & line1.end.y == line2.start.y) return true;
om (line1.end.x == line2.end.x & & line1.end.y == line2.end.y) return true;
om (line1.start.x == line2.start.x & & line1.start.y == line2.start.y) return true;
returnera false;
}
ConstellationMaker3D.prototype.addSegmentFromNode = function(node) {
var nextNodeList = this.closestNodeToNodeFromNodeSet(node,this.nodes);
var proposedLine = this.lineConnectingNodes2D(node,nextNodeList[0]);
om (this.lineIntersectsPriorLines(proposedLine) == true) {
this.disconnectedNodes.push(node);
} annat {
This.Connections.push([Node.instructableId,nextNodeList[0].instructableId]);
This.segments.push(proposedLine);
}
This.Nodes = _.without(this.nodes,nextNodeList[0]);
återgå nextNodeList [0];
}
ConstellationMaker3D.prototype.connectNodeMultipleTimes = function(node,times) {
var närmast = this.closestNodeToNodeFromNodeSet(node,this.allNodes),
lineCount = 0;
för (var jag = 2; jag < closest.length & & lineCount < gånger; i ++) {
var proposedLine = this.lineConnectingNodes2D(node,closest[i]);
om (! this.lineIntersectsPriorLines(proposedLine)) {
This.segments.push(proposedLine);
this.constellationLayer.add(proposedLine);
lineCount ++;
}
}
}
ConstellationMaker3D.prototype.lineIntersectsPriorLines = function(proposedLine) {
var som = denna, intersectionFound = false;
_.each(this.segments,function(testSegment) {
var skär = that.findLineLineIntersection.apply (, [testSegment, proposedLine]);
om (skär === sant) {
intersectionFound = sant;
}
});
återvända intersectionFound;
}
ConstellationMaker3D.prototype.lineConnectingNodes2D = function(node1,node2) {
returnera nya tre. Line3 (nya tre. Vector3 (node1.x,node1.y,0), nya tre. Vector3(node2.x,node2.y,0));
}
ConstellationMaker3D.prototype.getConnections = function(instructableId) {
Returnerar en matris med instructable id's som medföljande id har anslutningar.
var platt = _.uniq(_.flatten(this.connections));
var index = _.indexOf(flat,instructableId);
Switch(index) {
fall -1:
tillbaka [];
fall 0:
återkomst [platt [1]];
ärende flat.length-1:
återvändande platt [flat.length-2].
standard:
tillbaka [platta [index-1], platt [index + 1]];
}
Console.log (instructableId + "hittade på" + index + ' i ' + platt);
}
Övergången från KineticJS till ThreeJS avgjort komplicerar saker. Konstellationerna är fundamentalt 2d i naturen: de är anslutningar mellan punkter i 3 dimensioner (även om du frågar Ptolemy), men stjärnbilden sig biases ett särskilt perspektiv från jorden. Linjer som visas till oss inte att korsa kan faktiskt korsa när du visar dem från sidan, som de gör i den interaktiva demon.
Eftersom ThreeJS fungerar på 3d-objekt, blev en metod för att komprimera data till ett kamera plan nödvändigt. Jag införde några metoder för att få skärmen XY-koordinater för en världen XYZ punkt, med tanke på en kameraposition:
vectorWorldToScreenXY: function(vector,camera) {
Vector antas vara i världen xyz-koordinater kommer in.
var widthHalf = fönster. Galaxy.Settings.width / 2,
heightHalf = fönster. Galaxy.Settings.height / 2,
projektor = nya tre. Projector(),
screenPosition;
projector.projectVector (vektor, kamera);
screenPosition = {
x: (vector.x * widthHalf) + widthHalf,
y: - (vector.y * heightHalf) + heightHalf
};
återvända screenPosition;
},