Three.js: zyklische Abhängigkeiten

Erstellt am 16. März 2015  ·  81Kommentare  ·  Quelle: mrdoob/three.js

Hallo allerseits.

@kumavis und ich haben hart daran gearbeitet, einen effizienten Weg zu finden, um THREE.js auf eine browserfähige Architektur umzustellen. Wir haben gute Fortschritte gemacht, sogar bis zu dem Punkt, an dem alle Dateien in ein Browserify-Build-System verschoben wurden und wir in der Lage waren, eine three.min.js mit gulp zu generieren.

Leider funktionieren die Beispiele nicht, da browserify im Gegensatz zu commonjs nicht mit zyklischen Abhängigkeiten umgehen kann, von denen es in THREE.js viele gibt.

Ich habe hier eine interaktive Grafik erstellt, die die Abhängigkeitsbeziehungen darstellt.

Solange diese nicht entwirrt sind, können wir THREE.js nicht auf einen Browserify-Build verschieben.

Ich halte das nicht für einen Mangel von browserify, sondern eher für ein Problem mit THREE.js. Zirkuläre Abhängigkeiten sind in Software im Allgemeinen eine schlechte Sache und führen zu allen möglichen Problemen.

Suggestion

Hilfreichster Kommentar

@Mugen87 Wow, 5 Jahre! glückwunsch, dass du es endlich geschafft hast :fire: :clap:
Es hat mir damals viel Spaß gemacht, diese Grafiken zu erstellen :smile_cat:

Alle 81 Kommentare

das ist ein ziemlicher Knoten zum entwirren
http://jsbin.com/medezu/2/edit?html ,js,Ausgabe
image

@coballast können Sie den Code posten, den Sie zum Generieren des Abhängigkeits-JSON verwendet haben?

Verwenden Sie einfach direkt die vorkompilierte Datei three.min.js. Es besteht keine Notwendigkeit, Three.js innerhalb von Browserfy in einzelne Dateien aufzuteilen, Sie machen sich nur das Leben schwerer, ohne wirklichen Nutzen.

Ich spreche aus Erfahrung, insofern verwenden wir das npm Modul von three.js und es funktioniert super. Wir packen es einfach als einzelne Datei und packen es in ein Modul im CommonJS-Stil. Dieser Ansatz würde für browserfy funktionieren und viele Leute tun dies bereits, soweit ich weiß.

Das Entwirren dieses Knotens ist für diesen Anwendungsfall nicht erforderlich.

@kumavis Ich habe gerade die Abhängigkeitsstruktur abgelegt. Der folgende Code generierte dann den Graphen:

var fs = require('fs-extra');
var unique = require('uniq');
var util = require('util');

function getRequiredObjects(dependencies){
  var result = [];
  for(var i = 0; i < dependencies.usedObjects.length; i++){
    var object = dependencies.usedObjects[i];
    if(dependencies.definedObjects.indexOf(object) == -1)
      result.push(object);
  }

  return result;
};

var dependencies = JSON.parse(fs.readFileSync('./dependencies.json'));

var objects = [];
for(var f in dependencies){
  objects = objects.concat(dependencies[f].usedObjects);
  objects = objects.concat(dependencies[f].definedObjects);
}

objects = unique(objects);


var nodes = objects.map(function(o){
  return {data: {id: o} };
});

var edges = [];
for(var f in dependencies){
  var dependency = dependencies[f];
  var requiredObjects = getRequiredObjects(dependency);
  for(var j = 0; j < dependency.definedObjects.length; j++){
    for(var k = 0; k < requiredObjects.length; k++){
      edges.push({ data: { source: dependency.definedObjects[j], target: requiredObjects[k] } });
    }
  }
}

var graph = {nodes: nodes, edges: edges};

var eliminateImpossibleCycleNodes = function(graph){
  graph.nodes = graph.nodes.filter(function(node){
    var source_edge = null;
    var dest_edge = null;
    for(var i = 0; i < graph.edges.length; i++){
      if(graph.edges[i].data.source == node.data.id)
        source_edge = graph.edges[i];
      if(graph.edges[i].data.target == node.data.id)
        dest_edge = graph.edges[i];
    }

    if(source_edge != null && dest_edge != null)
      return true;
    else
      return false;
  });

  graph.edges = graph.edges.filter(function(edge){
    var source_exists = false, target_exists = false;
    for(var i = 0; i < graph.nodes.length; i++){
      if(edge.data.source == graph.nodes[i].data.id)
        source_exists = true;
      if(edge.data.target == graph.nodes[i].data.id)
        target_exists = true;
    }

    return source_exists && target_exists;
  });
};

for(var i = 0; i < 500; i++)
  eliminateImpossibleCycleNodes(graph)


console.log(JSON.stringify(graph));

@bhouston es geht mehr um den Zustand der Codebasis von three.js

Ich weiß nur, dass in der Mathematikbibliothek, bei der ich ein bisschen geholfen habe, zyklische Abhängigkeiten in allen Sprachen die Norm sind. Weil Funktionen auf Matrix4 Vector3 als Parameter annehmen können und Vector3 möglicherweise von Matrix4 transformiert werden kann. Alle Abhängigkeiten in der mathematischen Bibliothek auf eine Weise zu erstellen, würde die Verwendung dieses Teils der Bibliothek lästig machen.

Jetzt befürworte ich, dass die Mathematikbibliothek nichts über andere Teile der Bibliothek weiß – komplexere Typen sollten nicht wirklich in dieses Modul eindringen. In diesem Sinne befürworte ich daher den Versuch, zyklische Abhängigkeiten zwischen Modulen zu reduzieren, aber nicht alle zyklischen Abhängigkeiten zwischen einzelnen Dateien innerhalb eines Moduls zu entfernen.

Hier ist ein Fall, der die subtilen Komplikationen veranschaulicht. Um es klar zu sagen, hier kritisiere ich nicht die Implementierung selbst, sondern die Nebenwirkungen.

Vector3 und Matrix4 bilden eine zyklische Abhängigkeit, da sie eine Reihe von Funktionen offenlegen, die sich gegenseitig als Eingabe- oder Ausgabetypen verwenden. Beide sind mit einem Stil implementiert, der Three.js gemeinsam ist, und Funktionen über IIFEs definieren, um Scratch-Variablen zum Ausführen von Berechnungen einzuschließen.

Matrix4#lookAt kann den Scratch als Teil der Funktionsdefinition sofort instanziieren.

lookAt: function () {

  var x = new THREE.Vector3();
  var y = new THREE.Vector3();
  var z = new THREE.Vector3();

  return function ( eye, target, up ) {
    /* ... */

Vector3#project muss den Scratch jedoch beim ersten Lauf instanziieren.

project: function () {

  var matrix;

  return function ( camera ) {

    if ( matrix === undefined ) matrix = new THREE.Matrix4();

    /* ... */

Wieso den? weil bei der Definition der Klasse noch nicht alle Klassen definiert wurden. Bei der Definition Vector3 existiert Matrix4 noch nicht. Jetzt ist die tatsächliche Instanziierungszeit der Scratch-Variablen nicht wirklich wichtig. Der eigentliche Vorteil hier ist, dass die aktuelle Implementierung von der Reihenfolge abhängt, in der das Build-System Dateien miteinander verkettet. Das ist eine wirklich entfernte Kopplung, und Änderungen am Build-System oder das Umbenennen von Dateien in einer Weise, dass die Concat-Reihenfolge geändert wird, kann zu ungültigen Builds ohne offensichtliche Verbindung führen.

Dies ist nur eine der Möglichkeiten, wie sich dieser Knoten in Bugs manifestiert. Obwohl wir dieses spezifische Problem angehen können, habe ich keine allgemeine Lösung, die nicht viele Breaking Changes an der API erfordert.

Hmm ... Ich habe mir die C++-Mathematikbibliothek von ILM angesehen, die ich als Goldstandard in Bezug auf Mathematikbibliotheken betrachte. Überraschenderweise sind sie nicht zyklisch. Sie haben im Grunde eine gut definierte Reihenfolge von einfachen bis komplexen Typen, die sie definieren, und ich denke, wenn Sie es sehr sorgfältig machen, funktioniert es:

https://github.com/openexr/openexr/tree/master/IlmBase/Imath

Math und dann Vec scheint am einfachsten zu sein.

Weitere Beobachtungen zum Abhängigkeitsgraphen:

Material s haben wechselseitige Abhängigkeiten von ihrer Basisklasse
image
Etwas schwer zu erkennen, aber Geometry s scheinen gute One-Way-Deps auf der Basisklasse zu haben
image
Light s und Camera s haben eine ähnliche Situation – gut in Bezug auf ihre Basisklasse, aber die Abhängigkeit von Object3D _von ihnen_ scheint unnötig.
image
image
Curve s Path s Line s scheinen gut zu sein, aber Shape ist etwas verworren.
image

@coballast danke! das ist eine tolle erkenntnis.

Füge mich für die Kommentare hinzu :)

Übrigens habe ich mir angesehen, wie Material zum Beispiel von MeshDepthMaterial abhängt. Es ist einfach

if ( this instanceof THREE.MeshDepthMaterial )

was trivial zu ändern ist

if ( this.type == 'MeshDepthMaterial' )

und voila - keine Abhängigkeit. Ich schätze, die Hälfte dieses beängstigenden Diagramms ist die gleiche Problemebene.

Das Lustige ist, dass diese Abhängigkeit in der Single-to-JSON-Methode stattfindet. Ich meine, könnte es nicht stattdessen einfach in MeshDepthMaterial ersetzt werden? etwas wie

THREE.MeshDepthMaterial.prototype.toJSON =  function () {
    var output = THREE.Material.prototype.toJSON.apply(this);
    if ( this.blending !== THREE.NormalBlending ) output.blending = this.blending;
    if ( this.side !== THREE.FrontSide ) output.side = this.side;

@makc Im Allgemeinen sollten wir überall dort, wo wir instanceof machen, diesen Code in die spezifische Klasse selbst verschieben. Das wird helfen, viele Knoten zu entfernen.

Ich wollte nur sagen, dass AMD zwar keine Zirkelverweise unterstützt, ES6-Module jedoch https://github.com/ModuleLoader/es6-module-loader/wiki/Circular-References-&-Bindings

Ich bin nur neugierig zu wissen, abgesehen von dem Problem der Abhängigkeitsauflösung (das bei der Implementierung eines Modulsystemladers, z. B. system.js ), gelöst werden kann, welche Probleme entstehen durch Zirkelverweise in three.js?

Glücklicherweise sieht es so aus, als könnten wir dies schrittweise angehen. Ich denke, dass in früheren Versionen viele API-Breaking-Änderungen vorgenommen wurden, daher bin ich mir nicht sicher, ob dies ausgeschlossen ist.

Für die instanceof -Fälle (vielleicht die Mehrheit) sollten sie ohne Breaking Changes gelöst werden können.

Ich melde mich auch hier an. Gehen wir hier Schritt für Schritt vor.
Ich stimme zu, dass wir alle unnötigen zyklischen Abhängigkeiten wie die materielle entfernen sollten.
Ich stimme @bhouston auch zu, dass die Mathematikbibliothek einfach sehr voneinander abhängig ist, weil die Interaktion die Mathematikbibliothek nützlich macht.

Kann jemand die einfachen abbilden?? Weniger zyklische Abhängigkeiten zu haben ist immer eine gute Idee, wenn es die Bibliothek nicht behindert. Wir können später sehen, was wir mit den anderen machen.

@zz85 Ich bin auch auf das Problem der zirkulären Abhängigkeiten gestoßen. Dies ist meistens ein Problem, wenn wir versuchen, bestimmte Objekte in zirkulär referenzierten Dateien vorab zu erstellen.

6252 sollte viele zirkuläre Deps auf Material und Object3D aufklären.

So sieht Mesh aus. Vielleicht ein paar Nebensächlichkeiten, aber nicht zu verrückt.
image

Rundschreiben mit Object3D und Geometry . Die Object3D -> Mesh -Referenz wird in der PR oben angesprochen. Die Referenz Mesh -> Geometry ist in Ordnung, b/c Mesh steuert eine Instanz von Geometry . Es könnte immer noch abgebrochen werden, weil es eine Typprüfung für klassenspezifisches Verhalten durchführt ( Geometry / BufferGeometry ).

Was die Geometry -> Mesh -Referenz anbelangt, so ist geometry.mergeMesh( mesh ) . Geometry ist ein niedrigeres Konzept als Mesh , also würde ich es als mesh.mergeIntoGeometry( geo ) umkehren und mergeMesh .

Wenn jemand ein PR zusammengeführt bekommt, das einige davon behebt, lassen Sie es mich wissen und ich werde die Grafik aktualisieren, um den aktuellen Stand der Dinge widerzuspiegeln.

@bhouston @gero3 Ich bin nicht davon überzeugt, dass zyklische Abhängigkeiten erforderlich sind, um das gleiche Maß an Benutzerfreundlichkeit/Nützlichkeit für die Mathematikbibliothek zu erreichen. Ich könnte mich irren, aber könnten wir Vector3 nicht vollständig isoliert/unwissend vom Rest des Systems halten und seinen Prototyp modifizieren, um Matrix4 im Matrix4-Modul unterzubringen? Das macht für mich konzeptionell Sinn, da Matrizen komplexer sind als Vektoren. Ich denke, es ist besser, eine klar definierte Reihenfolge zu haben, in der Prototypen und Klassen erstellt werden, um Pannen zu vermeiden.

@bhouston @gero3 Ich denke, wir können das tun, ohne die API überhaupt zu ändern. Ich werde herumstochern und sehen, was was ist.

In Bezug auf Mathematik könnten Sie einfach alle "Notizblöcke" an einem Ort sitzen lassen, denke ich. aber ich wette, es wird keinen einzigen brauchbaren Graphen von 3js geben, der nicht sowohl Vector3 als auch Matrix4 hätte

Wenn es eine Lösung gibt, die die Leistung oder API der Mathematikbibliothek nicht ändert, bin ich dafür.

@coballast kann die zyklische Abhängigkeit nicht ohne API-Änderung entfernen, b/c bieten beide Methoden, die den anderen Typ verwenden. Vector3 Matrix4

Was Browserify Compat betrifft, so besteht unsere einzige Anforderung darin, die Instanziierung der Scratch-Variablen aus der Zeit der Klassendefinition zu verschieben (sie beim ersten Lauf instanziieren zu lassen). Machen Sie das so faul . Das hätte keine Auswirkungen auf API oder Leistung.

Solche Änderungen finde ich in Ordnung.

@kumavis Ah! Ja. Okay, ich verstehe jetzt. Das ist einfach genug.

Ich unterstütze voll und ganz die Aufteilung von DREI in kleinere Module mit einer Anforderungsstruktur aus den folgenden Gründen:

  1. DREI ist sehr groß. Wenn ein Benutzer nur das benötigen könnte, was er benötigt, könnte dies die Client-Build-Größe reduzieren. React-Bootstrap lässt Sie beispielsweise Dinge wie var Alert = require('react-bootstrap/lib/Alert'); tun, was nicht jedes Bootstrap-Modul bündelt.
  2. Es gibt einige "Plugins", wie OrbitControls.js , die die DREI globalen Objekte selbst modifizieren und sich selbst auf THREE.OrbitControls setzen. Dies ist ein Anti-Pattern in modernen Javascript-Frameworks, da es erfordert, dass THREE im globalen Namespace in einem Build-Prozess vorhanden ist, anstatt von den Dateien benötigt zu werden, die es benötigen. THREE tut dies auch intern, indem es immer den THREE-Namespace modifiziert, was nicht ideal ist, um bestimmte THREE-Module einzubinden.

sich auf THREE.OrbitControls setzen

aber jeder Code in 3js tut das?

@DelvarWorld schrieb:

Ich unterstütze voll und ganz die Aufteilung von DREI in kleinere Module mit einer Anforderungsstruktur aus den folgenden Gründen:

Früher hielt ich es für eine gute Idee, es aufzulösen, aber ThreeJS, so wie es jetzt ist, ist einfach. Es ist für diejenigen, die neu in 3D sind, in der Form, in der es sich gerade befindet, besser nutzbar, und das war eine Priorität für das Entwicklerteam. Sie können ThreeJS verwenden, ohne ein Modulsystem zu benötigen (von denen es viele gibt und nicht alle vollständig kompatibel sind).

Wie @makc bin auch ich verwirrt über den Vorschlag von @DelvarWorld , Dinge nicht in den Namensraum THREE zu stellen.

Wo/wie wären sie stattdessen?

Für mich scheint es das gute Muster zu sein, nur ein globales Objekt, DREI, zu erstellen, in dem alle Teile (und möglicherweise einige Erweiterungen / Plugins) davon enthalten sind.

Ich stimme @DelvarWorld zu, dass die Put-it-on-the-Global-Technik nicht gut für die Gesundheit der Codebasis ist - es ist eine Art subtiles Argument, weil es nicht das Problem ist, es auf die Globale selbst zu setzen, sondern das Hidden Dependency Graph und andere Praktiken, die sich aus der Verfügbarkeit des Globalen ergeben.

Dieses Argument beschränkt sich jedoch hauptsächlich auf die interne Entwicklung und die Codestruktur. Was die Bereitstellung der Bibliothek als statisches Codebündel betrifft, so macht es für mich Sinn, alle Klassen auf die globale THREE zu setzen.

Ein Gegenargument ist, dass beim Deserialisieren eines Szenen-JSONs von THREE.js die Einträge ihre Klasse einfach als Zeichenfolge auflisten können, die aus der globalen abgeleitet werden kann, wie z. B.: THREE[ obj.type ] . Dies funktioniert mit Klassen, die nicht in der standardmäßigen Three.js-Bibliothek enthalten sind, solange Sie sie vor der Deserialisierung auf THREE definieren. Ich bin mir nicht sicher, wie ich dieses Verhalten am besten ohne das globale THREE ersetzen kann.

Dies funktioniert mit Klassen, die nicht in der standardmäßigen Three.js-Bibliothek enthalten sind, solange Sie sie auf THREE definieren, bevor Sie deserialisieren. Ich bin mir nicht sicher, wie ich dieses Verhalten am besten ohne die THREE global ersetzen kann.

Sie können dieses Muster (oder eine Variante davon) ausführen, wenn alles ein Modul ist:

var objectType = require( "THREE." + obj.type );

Es gibt viele Änderungen, die mit ES6 in Bezug auf Module kommen. An diesem Punkt würde ich die Modularität von ThreeJS noch einmal überdenken.

Die gebaute Version von three (die Javascript-Datei, die manuell heruntergeladen werden kann) hätte immer noch alles im Namensraum three. Sie könnten dies mit der Einstiegspunktdatei für den Build tun:

var THREE = {
    Geometry: require("./geometry"),

usw., die für Neulinge immer noch nützlich und für den Einstieg leicht zu handhaben wären.

Für diejenigen, die drei von npm und requirejs/browserify/webpack in einem modernen Javascript-Build verwenden, könnten wir so etwas tun wie

var Scene = require("three/scene"),
     Camera = require("three/camera"),

usw., wodurch ein Teil der Größe von drei, die in ein Client-Size-Bundle integriert sind, eingespart werden kann. Ich könnte mich irren, weil ich nicht weiß, wie viel von drei "Kern" ist. Im Moment ist dies jedoch unmöglich, da three keine require-Anweisungen verwendet.

So oder so erfordert das moderne Muster Module, und anstatt Ihren gesamten Code dazu zu bringen, eine andere Bibliothek zu ändern (Ändern der globalen DREI, schlecht), ist Ihr Code unabhängig und modular und spezifiziert, was er mit require -Anweisungen erfordert. wie der React-Quellcode .

Ich glaube nicht, dass der Versuch, ein vollständiges Argument für die Verwendung der require/module-Syntax vorzubringen, hilfreich sein wird, da es online viele gute Ressourcen dazu gibt. Aber es ist schlecht, andere zu ermutigen, den THREE-Namespace zu ändern, um Plugins wie OrbitControls hinzuzufügen.

Beachten Sie jedoch @DelvarWorld , dass ES6 Module offiziell mit einer sehr spezifischen und unterschiedlichen Syntax in JavaScript einführt:

http://www.2ality.com/2014/09/es6-modules-final.html

@bhouston oh ja total, ich bin agnostisch, ob ich vs import verlangen muss (import ist wahrscheinlich die bessere Wahl), sondern nur das Modulmuster im Allgemeinen zu unterstützen.

@bhouston @DelvarWorld @kumavis Eines meiner langfristigen Projekte ist es, einen automatischen es5 -> es6-Konverter zu schreiben, der gemeinsame js/amd-Module aufnehmen und in es6 konvertieren und hoffentlich eine Menge Javascript mithilfe von es6-Konstrukten wie Klassen/Generatoren identifizieren und neu schreiben kann und so weiter. Man könnte eine Browserify-Transformation wie es6ify verwenden, während Browser den Standard einholen, um den Code für die Nutzung vorzubereiten. Die Umstellung von THREE auf browserify nur auf interner Ebene ist ein guter erster Schritt, um es für die Eingabe in ein solches Tool vorzubereiten.

Dies ist jedoch neben dem Punkt, den ich (anscheinend sehr schlecht) zu erreichen versuchte. Ich möchte so viele dieser zyklischen Abhängigkeiten wie möglich entfernen, unabhängig von Bedenken hinsichtlich der Modularität, weil ich glaube, dass dies THREE stabiler und flexibler machen und wahrscheinlich als angenehmen Nebeneffekt viele Fehler beseitigen wird.

@coballast https://github.com/mrdoob/three.js/pull/6252 wurde zusammengeführt, sollte viele der zyklischen Tiefen reduzieren. Denken Sie, Sie können ein neues Tiefendiagramm erstellen? Machen Sie es vielleicht zu einem Dienstprogramm im Repo des Konvertierungstools

Das nächste ist: Die Scratch-Variablen in Vector3 Matrix4 faul bei der ersten Verwendung zu definieren, nicht bei der Definitionszeit

möchte sich jemand ehrenamtlich engagieren? sollte schnell gehen

Grafik wurde aktualisiert. http://jsbin.com/medezu/3/

Hier ist ein Screenshot:

snapshot3

Ich freue mich, Ihnen mitteilen zu können, dass eine große Anzahl von Object3D-Circ-Deps eliminiert wurden. Gute Arbeit @kumavis!

Wow, ist das die gleiche Codebasis? verrückt

Wird daran arbeiten, die Graphenerstellung zu einem Teil des Dienstprogramms zu machen.

Allein bei Betrachtung des Graphen scheinen Shape und Geometry mögliche Klassenbäume zu sein, die enträtselt werden können.

@coballast denkst du, du kannst das übernehmen?

Erstellen der Scratch-Variablen in Vector3 Matrix4, die bei der ersten Verwendung faul definiert werden, nicht zur Definitionszeit

das wäre ein PR gegen Upstream dev im Gegensatz zu einer automatisierten Änderung

Ich denke auch, dass wir den Titel des Problems von "Schwerwiegende zyklische Abhängigkeitsprobleme" auf "zyklische Abhängigkeiten" herabstufen können - die Situation hat sich stark verbessert!

@kumavis Klare Sache. Werde daran arbeiten, wenn es die Zeit zulässt.

Hier ist der aktuelle Stand der Interdependenzbereinigung, wie ich ihn sehe:
(Pfeile zeigen Verbindungen, die ggf. entfernt werden sollten)

  • [x] Materialien
  • [x] Geometrien
  • [x] Object3Ds
  • [x] Mathe
  • [x] Formen

    • [x] Form -> FontUtils

    • [x] Shape -> ExtrudeGeometry

    • [x] Form -> FormGeometrie

    • [x] Pfad -> Form

  • [ ] Feld3

    • [ ] Box3 -> BufferGeometry

    • [ ] Box3 -> Geometrie

Formen:

image

Feld3:

image

Mathematik:

Diese Knoten sind miteinander verbunden, bieten dadurch aber ausreichend Komfort.
image

Shape scheint Abhängigkeiten mit ExtrudeGeometry und ShapeGeometry durch Code wie diesen zu haben:

// Convenience method to return ExtrudeGeometry

THREE.Shape.prototype.extrude = function ( options ) {

  var extruded = new THREE.ExtrudeGeometry( this, options );
  return extruded;

};

// Convenience method to return ShapeGeometry

THREE.Shape.prototype.makeGeometry = function ( options ) {

  var geometry = new THREE.ShapeGeometry( this, options );
  return geometry;

};

Jetzt scheint es, dass Shape eine Unterklasse von Path ist und ExtrudeGeometry und ShapeGeometry beide Unterklassen von Geometry sind. Sie können also herausfinden, welche Abhängigkeiten im Idealfall wegfallen müssten.

Ja, das fällt unter dieselbe Kategorie wie Vector3 <-> Matrix4 . Sie sind der Einfachheit halber verlinkt. Ich denke, es ist eine schlechte Idee, aber es lohnt sich nicht, dagegen anzukämpfen. Ich werde es als abgeschlossen markieren

Shape -> FontUtils könnte entfernt werden, indem Methoden wie triangulate zu allgemeineren Utils verschoben werden. Aber kein großer Gewinn dafür. Werde es als abgeschlossen markieren.

Box3 -> BufferGeometry und Box3 -> Geometry könnten beide bereinigt werden.

Es ist ein weiterer Fall, der Klasse selbst kein klassenabhängiges Verhalten zuzuweisen.

Quelle :

setFromObject: function () {

  // Computes the world-axis-aligned bounding box of an object (including its children),
  // accounting for both the object's, and childrens', world transforms

  /* ... */

  if ( geometry instanceof THREE.Geometry ) {
    /* ... */
  } else if ( geometry instanceof THREE.BufferGeometry && geometry.attributes[ 'position' ] !== undefined ) {
    /* ... */
  }

  /* ... */

}

In beiden Fällen wird nur versucht, die Scheitelpunkte/Positionen der worldCoordinate der Geometrie zu durchlaufen. Ich frage mich, ob es den Code erheblich vereinfachen würde, ein faules vertices -Objekt auf BufferGeometry zu erstellen, das die Werte nachschlägt, wenn sie angefordert werden. Ich bin mir nicht sicher, was die Auswirkungen auf die Leistung betrifft.

Alternativ könnten wir Geometry.computeBoundingBox verwenden:
Geometry
BufferGeometry

Box3 ist ein Problempunkt beim Ausführen des Browserify-Builds. Siehe Hinweise in coballast/threejs-browserify-conversion-utility#21.

@kumavis Würde es Ihnen etwas ausmachen, Ihre empfohlene Lösung für den Umgang mit Box3/Geometry/BufferGeometry zu skizzieren? Wenn es schnell geht, kann ich es umsetzen.

Ich kann es mir gerade nicht ansehen, aber ich würde mit meinem obigen Vorschlag beginnen, geo.computeBoundingBox wie in Geometry und BufferGeometry implementiert anstelle von if/else hier zu verwenden . Stattdessen sollte box3.setFromObj geometry.computeBoundingBox aufrufen und dann Parameter basierend auf der produzierten Box3 setzen.

Das sollte das Box3 -> BufferGeometry und Box3 -> Geometry Ende der kreisförmigen Tiefen entfernen. Lassen Sie mich wissen, wenn ich etwas vermisse.

Hmm, vielleicht ist der resultierende Code etwas verworren, was macht hier eigentlich Sinn? Box3.setFromObject sollte nicht existieren, aber das ist keine Option. Geos sollten in der Lage sein, Box3s zu produzieren, damit habe ich kein Problem. Ja, ich denke, Box3.setFromObject sollte die Geos nach einem Begrenzungsrahmen / Umfang fragen, aber vielleicht sollten sie die Object3D / Mesh nach dem Begrenzungsrahmen / Umfang fragen.

Entschuldigung, wurde ein bisschen weitschweifig. Lass mich wissen, was du denkst.

Möglicherweise relevant: #6546

Ohne so etwas ist es unmöglich, diese dynamischen Abhängigkeiten von Ladeskripten zu analysieren.

Basierend auf meinen Tests sind zyklische Abhängigkeiten kein Problem mit commonJSification. Sie müssen korrekt gehandhabt werden, und wie bereits in diesem Thread erwähnt, machen sie den Abhängigkeitsgraphen ziemlich durcheinander, aber sie hindern THREE.js nicht daran, in gewöhnlichen JS-Umgebungen zu funktionieren (natürlich wenn sie transformiert sind).

Ich habe gerade eine vollständige Commonjs-Version auf npm als three.cjs veröffentlicht , indem ich meinen Drei-Commonjs-Transpiler verwendet habe

Hinweis: Damit dies funktioniert, musste ich manuell #6546 auf dem Master auswählen. Während dynamische Abhängigkeiten in node.js gut funktionieren, können sie nicht in Browserify (oder einem anderen cjs to browser-Tool) funktionieren, da sie eine statische Abhängigkeitsanalyse durchführen müssen.

Browserify-Beweis: http://requirebin.com/?gist=b7fe528d8059a7403960

@kamicane FYI - Hier wurde DREI als Argument einer anonymen Funktion zu Raycaster (früher Ray ) hinzugefügt.

Ich verstehe die Notwendigkeit einer anonymen Funktion (um globale Lecks in Browsern zu verhindern), das Argument ist jedoch überflüssig und lässt die gesamte Datei in die Kategorie der berechneten Abhängigkeiten fallen. Während das Argument mit AST-Modifikationen dynamisch entfernt werden könnte, kann es niemals eine kugelsichere Lösung sein (abhängig davon, was in das Argument geschrieben, aus dem Argument gelesen usw. wird). Eine statische Analyse wird fast unmöglich. In diesem Fall ist ein manueller Eingriff erforderlich.

Jetzt, da Raycaster leichter ist, könnten wir es versuchen, es wie die anderen Klassen zu machen.

@mrdoob , @Mugen87 wir verwenden Rollup, um nur die Teile von Three.js zu verwenden, die wir wirklich brauchen. Wenn wir den Build ausführen, erhalten wir immer noch die folgende Warnung:

(!) Circular dependency: node_modules/three/src/math/Vector3.js -> node_modules/three/src/math/Matrix4.js -> node_modules/three/src/math/Vector3.js
(!) Circular dependency: node_modules/three/src/math/Vector3.js -> node_modules/three/src/math/Quaternion.js -> node_modules/three/src/math/Vector3.js
(!) Circular dependency: node_modules/three/src/math/Sphere.js -> node_modules/three/src/math/Box3.js -> node_modules/three/src/math/Sphere.js
(!) Circular dependency: node_modules/three/src/objects/LineSegments.js -> node_modules/three/src/objects/Line.js -> node_modules/three/src/objects/LineSegments.js

Gibt es noch zirkuläre Abhängigkeiten in Three.js oder machen wir etwas falsch?

Vector3 und Matrix4 sind miteinander verbunden, wenn Sie einen ziehen, müssen Sie den anderen ziehen. Zirkuläre Abhängigkeiten sollten technisch erlaubt sein.

@bhouston ja verstehe, danke für den Hinweis. Ja, zirkuläre Abhängigkeiten sind erlaubt und Rollup macht keine Probleme, aber ich bin mir nicht sicher, ob es eine gute Praxis ist, zirkuläre Abhängigkeiten zu haben. Vector3 Matrix4 nur wegen multiplyMatrices und getInverse von Matrix4 ab, für weitere Details siehe (https://github.com/mrdoob/three.js/ blob/dev/src/math/Vector3.js#L315)

@roomle-build Idk, Mann, nur weil es explizit auf den Matrix4-Konstruktor verweist? wie wäre es mit

    applyMatrix4: function ( m ) {

        var x = this.x, y = this.y, z = this.z;
        var e = m.elements;

        var w = 1 / ( e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] );

        this.x = ( e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] ) * w;
        this.y = ( e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] ) * w;
        this.z = ( e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] ) * w;

        return this;

},

?

Sie können sagen, dass Sie { elements: [....] } übergeben könnten und es funktioniert, aber wir alle wissen, dass dort Matrix4 erwartet wird

Beginnen wir mit Vector3 .

Vector3 hängt von Matrix4 wegen project und unproject ab:

    project: function () {

        var matrix = new Matrix4();

        return function project( camera ) {

            matrix.multiplyMatrices( camera.projectionMatrix, matrix.getInverse( camera.matrixWorld ) );
            return this.applyMatrix4( matrix );

        };

    }(),

    unproject: function () {

        var matrix = new Matrix4();

        return function unproject( camera ) {

            matrix.multiplyMatrices( camera.matrixWorld, matrix.getInverse( camera.projectionMatrix ) );
            return this.applyMatrix4( matrix );

        };

    }(),

Vector3 hängt von Quaternion wegen applyEuler und applyAxisAngle ab:

    applyEuler: function () {

        var quaternion = new Quaternion();

        return function applyEuler( euler ) {

            if ( ! ( euler && euler.isEuler ) ) {

                console.error( 'THREE.Vector3: .applyEuler() now expects an Euler rotation rather than a Vector3 and order.' );

            }

            return this.applyQuaternion( quaternion.setFromEuler( euler ) );

        };

    }(),

    applyAxisAngle: function () {

        var quaternion = new Quaternion();

        return function applyAxisAngle( axis, angle ) {

            return this.applyQuaternion( quaternion.setFromAxisAngle( axis, angle ) );

        };

    }(),

Anregungen?

Ich bin mir nicht sicher, ob wir zirkuläre Abhängigkeiten unbedingt entfernen müssen. Aber ich könnte mir vorstellen multiplyMatrices in ein Math Modul zu verschieben. Die Signatur würde sich dann natürlich in multiplyMatrices( a: Matrix4, b: Matrix4, result: Matrix4 ): Matrix4 ändern. Innerhalb Vector3 könnten Sie dann import { multiplyMatrices } from './Math'; das Gleiche in Matrix4 tun (um die API-Oberfläche von Matrix4 gleich zu halten).

Ich habe nur einen kurzen Blick darauf geworfen (habe mir den Fall Quaternian nicht angesehen - nur Vec3/Mat4 ) und bin mir auch nicht sicher, was die Auswirkungen auf die Leistung und die Konsequenzen für den Rest der Codebasis angeht. Außerdem bin ich auch nicht davon überzeugt, dass es unbedingt notwendig ist, diese zirkulären Abhängigkeiten zu entfernen. Ich wollte nur meine Gedanken teilen, weil @mrdoob nach Vorschlägen gefragt hat

@roomle-build Erstellen Sie also im Grunde mehr Module, um keine zirkulären Abhängigkeiten zu vermeiden, aber Sie werden trotzdem alle diese Module verwenden? Dies könnte vielleicht sinnvoller sein, wenn jede mathematische Methode ein eigenes Modul wäre, dann ziehen Sie nur die ein, die Sie verwenden, aber das werden viele Module sein.

@makc nicht wirklich. Es wäre ein großes Modul mit vielen kleinen "Hilfsfunktionen". Dies würde auch beim Tree-Shaking etc. helfen. Ein Mathe-Modul könnte so aussehen:

export const multiplyMatrices( a, b, result ) { // ... DO STUFF ... // }
export const getInverse( /* ... */ ) { // ... DO STUFF ... // }
// ...
// ...

Und das konsumierende Modul würde so etwas tun:

import { Matrix4 } from './Matrix4.js';
import { multiplyMatrices } from './math';
const result = new Matrix4( );
multiplyMatrices( a, b, result );

Wenn Sie alles zusammen bündeln, macht Rollup seine Magie und erstellt das effizienteste Bündel.

Dies ist, was viele beliebte Bibliotheken tun. Tatsächlich hat RxJS ihre import "Logik" auch auf das von mir beschriebene Muster umgestellt. Da sieht es ungefähr so ​​aus:

 import { flatMap, map, tap } from 'rxjs/operators';

myObject.run().pipe(
  tap(result => doSomething()), 
  flatMap(() => doSomethingElse()), 
  map(() => doAnotherThing())
);

Sie können zum Beispiel in mehreren Blogposts nachlesen, „warum und wie“ sie diese Dinge in RxJS 6 geändert haben: https://auth0.com/blog/whats-new-in-rxjs-6/

Aber wie gesagt, es ist nur ein Gedanke und ich bin mir nicht sicher, welche Auswirkungen dies auf den Rest der Codebasis haben würde. Auch das aktuelle math -Modul ist nicht "vorbereitet", um so verwendet zu werden. Derzeit sind alle Methoden des Mathematikmoduls "art statisch" angehängt. Dies verhindert auch, dass Rollup erkennt, was wirklich benötigt wird ...

@roomle-build hmm Sie sagen also, dass Rollup verstehen kann, ob der Code im selben Bereich nicht den gesamten Bereich benötigt, schön.

Sie sprechen eher von einem funktionalen Ansatz (Objekte mit Objekten) als von einem objektorientierten Ansatz (Objekte mit Mitgliedsfunktionen). ziemlich groß und es würde den gesamten vorhandenen Code brechen.

Ich bin mir nicht sicher, ob die Argumente für diese Änderung an dieser Stelle so aussagekräftig sind, dass sie es rechtfertigen würden, jegliche Abwärtskompatibilität zu brechen.

@makc nicht wirklich. Es wäre ein großes Modul mit vielen kleinen "Hilfsfunktionen". Dies würde auch beim Tree-Shaking etc. helfen. Ein Mathe-Modul könnte so aussehen:

Wenn dies vorgeschlagen wird, sollte es korrekt beschrieben werden. Es ist der Wechsel von Three.JS von einem objektorientierten Designstil zu einem funktionalen Design.

@roomle-build hmm Sie sagen also, dass Rollup verstehen kann, ob der Code im selben Bereich nicht den gesamten Bereich benötigt, schön.

Ja, Rollup versteht, wie alle Importe miteinander in Beziehung stehen, und führt Tree-Shaking, Eliminierung von totem Code usw. durch. Die neuen Versionen von Rollup können auch "Chunking" und viele andere nette Sachen. Die derzeitige Projektstruktur nutzt diese Merkmale jedoch nicht vollständig aus.

Sie sprechen eher von einem funktionalen Ansatz (Objekte mit Objekten) als von einem objektorientierten Ansatz (Objekte mit Mitgliedsfunktionen). ziemlich groß und es würde den gesamten vorhandenen Code brechen.

Ich glaube nicht, dass sich diese beiden Paradigmen gegenseitig ausschließen. Ich denke, Sie können diese beiden Paradigmen mischen und aufeinander abstimmen. Ich schlage auch nicht vor, zur funktionalen Programmierung zu wechseln. Ich wollte nur einen Weg beschreiben, um die zyklische Abhängigkeit loszuwerden. Sie könnten auch die Methode multiplyMatrices an das Objekt Math anhängen. Aber wenn jemand diese Art von Zeug umschreibt, wäre es sinnvoll, die Verwendung der Funktionen von ES6-Modulen in Betracht zu ziehen. Aber wie gesagt, ich bin kein Experte der Three.js-Codebasis und es war nur ein Gedanke, wie man die zyklische Abhängigkeit beseitigen kann. Ich denke, Three.js ist ein großartiges Projekt mit einer großartigen Codebasis, und ich möchte nicht herumnörgeln. Daher hoffe ich, dass sich niemand durch meine Kommentare angegriffen fühlt 😉

Ich bin mir nicht sicher, ob wir Designentscheidungen in einer Ausgabe diskutieren sollten. Haben Sie einen Ort, an dem solche Sachen besser passen?

BTW gl-matrix ist eine funktionale Mathematikbibliothek: https://github.com/toji/gl-matrix/tree/master/src/gl-matrix

@roomle-build

Derzeit sind alle Methoden des Mathematikmoduls "art statisch" angehängt.

Wieso das?

@mrdoob Ich glaube, dass mit dem funktionalen Design von gl-matrix jede Funktion von sagen wir vec3 (in der vec3-Datei, auf die ich in meinem vorherigen Kommentar verlinkt habe) einzeln exportiert wird. Auf diese Weise können Sie auswählen, welche Funktionen importiert werden sollen. Sie müssen nicht alle vec3 mitbringen.

Da wie bei Three.JS ein objektorientiertes Design verwendet wird, werden alle mathematischen Funktionen für Vector3 an den Vector3-Objektprototypen angehängt, und Sie importieren nur die Vector3-Klasse selbst.

Die Importe in Three.JS beziehen sich also auf ganze Klassen, während Sie bei einem funktionalen Ansatz einzelne Funktionen importieren.

(Die andere sehr nette Sache an der gl-matrix-Bibliothek ist, dass alle einzelnen Funktionen keine anderen Funktionen verwenden, @toji hat im Grunde eine optimierte Version der gesamten Mathematik in jede einzelne Operation integriert. Dies ist wahrscheinlich ziemlich effizient in Bezug auf die Geschwindigkeit aber es führt zu einer schwer zu pflegenden Bibliothek.)

Ich glaube nicht, dass wir diesen Teil von Three.JS umgestalten müssen, außer vielleicht, um alle Verweise in /math auf andere Verzeichnisse in three.js loszuwerden. Die Mathematikbibliothek ist ziemlich klein und taucht heutzutage in meinen Profiling-Tests nie wirklich auf. Ja, es ist nicht maximal effizient, aber es ist nah genug, während die Lesbarkeit und Benutzerfreundlichkeit aufrechterhalten werden können.

@bhouston Verstanden . Vielen Dank für die Erklärung! 😊

Ich wollte das Thema nur weiter verfolgen. Aber ich möchte von importing function vs importing classes auf das Thema der Lösung von cyclic dependencies zurückkommen. (Ich sehe auch nicht ein, warum import { someFunction } from 'SomeModule' weniger wartbar ist als import SomeClass from 'SomeModule' , aber das ist definitiv nicht das Thema dieses Problems/Gesprächs.

Um die zyklische Abhängigkeit aufzulösen, wäre es möglich, die Funktionalität in eine separate Klasse zu packen. Sie könnten eine multiplyMatrices -Methode an die Math-Klasse anhängen oder eine Multiplikator-Klasse erstellen, die die multiplyMatrices -Methode hat. Aber wie gesagt, ich bin mir nicht sicher, ob wir zyklische Abhängigkeiten entfernen müssen. Wenn die Entscheidung lautet, sie nicht zu entfernen, könnte dieses Problem meiner Meinung nach nahe sein 😃

Nach Auflösung von #19137 kann diese nun geschlossen werden 🎉.

@Mugen87 Wow, 5 Jahre! glückwunsch, dass du es endlich geschafft hast :fire: :clap:
Es hat mir damals viel Spaß gemacht, diese Grafiken zu erstellen :smile_cat:

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen