Three.js: ketergantungan siklik

Dibuat pada 16 Mar 2015  ·  81Komentar  ·  Sumber: mrdoob/three.js

Hai semuanya.

@kumavis dan saya telah bekerja keras mencoba menemukan cara yang efisien untuk memindahkan THREE.js ke arsitektur browserify. Kami membuat kemajuan yang baik, bahkan sampai semua file dipindahkan ke sistem build browserify dan mampu menghasilkan three.min.js dengan gulp.

Sayangnya, contoh tidak berfungsi, karena tidak seperti commonjs, browserify tidak dapat menangani dependensi siklik, yang banyak terdapat di THREE.js.

Saya telah membuat grafik interaktif yang menggambarkan hubungan ketergantungan di sini .

Kecuali dan sampai semua ini terurai, kami tidak akan dapat memindahkan THREE.js ke build browserify.

Saya tidak menganggap ini sebagai kekurangan browserify, melainkan masalah dengan THREE.js. Ketergantungan melingkar adalah hal yang buruk untuk dimiliki dalam perangkat lunak secara umum, dan menyebabkan segala macam masalah.

Suggestion

Komentar yang paling membantu

@Mugen87 wow, 5 tahun! selamat akhirnya berhasil :fire: :clap:
Saya sangat senang membuat grafik itu saat itu :smile_cat:

Semua 81 komentar

itu cukup rumit untuk diurai
http://jsbin.com/medezu/2/edit?html ,js,output
image

@cobalast dapatkah Anda memposting kode yang Anda gunakan untuk menghasilkan json ketergantungan?

Cukup gunakan file three.min.js yang telah dikompilasi secara langsung. Tidak perlu memecah Three.js menjadi file individual di dalam Browserfy, Anda hanya membuat hidup Anda lebih sulit tanpa manfaat nyata.

Saya berbicara dari pengalaman, karena kami menggunakan modul npm dari three.js dan berfungsi dengan baik. Kami hanya mengemasnya sebagai satu file dan membungkusnya dalam modul gaya CommonJS. Pendekatan ini akan bekerja untuk browserfy dan banyak orang sudah melakukannya, saya mengerti.

Mengurai simpul ini tidak diperlukan untuk kasus penggunaan ini.

@kumavis Saya baru saja membuang struktur ketergantungan. Kode berikut kemudian menghasilkan grafik:

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 ini lebih tentang kesehatan basis kode three.js

Saya hanya tahu bahwa di perpustakaan matematika, yang saya bantu dengan cukup baik, ketergantungan siklik adalah norma dalam semua bahasa. Karena fungsi pada Matrix4 dapat mengambil Vector3 sebagai parameter, dan Vector3 mungkin dapat diubah oleh Matrix4. Untuk membuat semua dependensi satu arah di perpustakaan matematika akan membuat bagian perpustakaan itu mengganggu untuk digunakan.

Sekarang saya menganjurkan bahwa perpustakaan matematika tidak tahu tentang bagian lain dari perpustakaan -- tipe yang lebih kompleks seharusnya tidak benar-benar bocor ke modul itu. Jadi dalam pengertian itu saya menganjurkan untuk mencoba mengurangi ketergantungan siklik antar-modul, tetapi tidak menghapus semua ketergantungan siklik antara file individual dalam sebuah modul.

Berikut adalah kasus yang menggambarkan komplikasi halus. Agar jelas, di sini saya tidak mengkritik implementasi itu sendiri, tetapi efek sampingnya.

Vector3 dan Matrix4 membentuk ketergantungan siklis karena mereka mengekspos berbagai fungsi yang menggunakan satu sama lain sebagai tipe input atau output. Keduanya diimplementasikan dengan gaya yang umum untuk Three.js, mendefinisikan fungsi melalui IIFE untuk menyertakan variabel awal untuk melakukan perhitungan.

Matrix4#lookAt dapat langsung menginisiasi awal, sebagai bagian dari definisi fungsi.

lookAt: function () {

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

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

Vector3#project namun, harus membuat instance awal pada saat pertama kali dijalankan.

project: function () {

  var matrix;

  return function ( camera ) {

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

    /* ... */

Mengapa? karena saat mendefinisikan Kelas, belum semua Kelas didefinisikan. Saat mendefinisikan Vector3 , Matrix4 belum ada. Sekarang waktu instantiasi aktual dari variabel awal tidak terlalu penting. Kesimpulan sebenarnya di sini adalah bahwa implementasi saat ini bergantung pada urutan sistem build yang menggabungkan file bersama-sama. Itu adalah kopling yang sangat jauh, dan perubahan pada sistem build atau penggantian nama file sedemikian rupa sehingga mengubah urutan concat dapat menghasilkan build yang tidak valid, tanpa koneksi yang jelas.

Ini hanyalah salah satu cara simpul ini bermanifestasi menjadi serangga. Namun, meskipun kami dapat mengatasi masalah khusus ini, saya tidak memiliki solusi umum yang tidak memerlukan banyak perubahan pada API.

Hmm... Saya telah melihat perpustakaan matematika C++ ILM, yang saya anggap sebagai standar emas dalam hal perpustakaan matematika. Anehnya, mereka tidak siklik. Mereka pada dasarnya memiliki urutan yang jelas dari tipe sederhana hingga kompleks yang mereka definisikan dan saya kira jika Anda melakukannya dengan sangat hati-hati, ini berfungsi:

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

Matematika dan kemudian Vec tampaknya menjadi yang paling sederhana.

Pengamatan lebih lanjut pada grafik ketergantungan:

Material s memiliki deps dua arah dengan kelas dasarnya
image
Agak sulit dilihat, tetapi Geometry s tampaknya memiliki deps satu arah yang bagus di kelas dasar
image
Light s dan Camera s memiliki situasi yang sama -- baik dalam kaitannya dengan kelas dasar mereka, tetapi ketergantungan Object3D _pada mereka_ tampaknya tidak perlu.
image
image
Curve s Path s Line s tampak bagus, tapi Shape agak kusut.
image

@koballast terima kasih! ini adalah wawasan yang bagus.

Menambahkan diri saya untuk komentar :)

Btw, saya telah melihat cara Material bergantung pada MeshDepthMaterial, misalnya. Ini sederhana

if ( this instanceof THREE.MeshDepthMaterial )

yang sepele untuk diubah menjadi

if ( this.type == 'MeshDepthMaterial' )

dan voila - tidak ada ketergantungan. Saya kira setengah dari grafik menakutkan ini adalah tingkat masalah yang sama.

lucunya adalah ketergantungan ini terjadi dalam metode toJSON tunggal. Maksud saya, tidak bisakah diganti di MeshDepthMaterial saja? sesuatu seperti

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 Secara umum di mana pun kita melakukan instanceof kita harus memindahkan kode itu ke kelas tertentu itu sendiri. Itu akan membantu menghilangkan banyak simpul.

hanya ingin mengatakan sementara AMD tidak mendukung referensi melingkar, modul ES6 mendukung https://github.com/ModuleLoader/es6-module-loader/wiki/Circular-References-&-Bindings

saya hanya ingin tahu selain dari masalah penyelesaian ketergantungan (yang dapat diselesaikan dalam implementasi pemuat sistem modul misalnya system.js ), masalah apa yang dibuat oleh referensi melingkar di three.js?

Untungnya sepertinya kita bisa menyerang ini secara bertahap. Saya pikir banyak perubahan yang melanggar api telah dibuat di rilis sebelumnya, jadi saya tidak yakin itu tidak mungkin.

Untuk kasus instanceof (mungkin sebagian besar) mereka harus dapat diselesaikan tanpa melanggar perubahan.

Saya juga berlangganan di sini. Mari kita pergi selangkah demi selangkah di sini.
Saya setuju kita harus menghapus semua dependensi siklik yang tidak perlu seperti yang material.
Saya juga setuju dengan @bhouston bahwa perpustakaan matematika sangat bergantung satu sama lain karena interaksi itulah yang membuat perpustakaan matematika bermanfaat.

Dapatkah seseorang memetakan yang mudah?? Memiliki lebih sedikit ketergantungan siklik selalu merupakan ide bagus jika tidak menghambat perpustakaan. Nanti kita bisa melihat apa yang harus dilakukan dengan yang lain.

@ zz85 Saya juga mengalami masalah dependensi melingkar. Ini sebagian besar merupakan masalah ketika kami mencoba membuat objek tertentu di dalam file referensi melingkar.

6252 harus menghapus banyak deps melingkar pada Material dan Object3D .

Berikut tampilan Mesh . Mungkin beberapa deps asing tapi tidak terlalu gila.
image

Edaran dengan Object3D dan Geometry . Referensi Object3D -> Mesh dibahas dalam PR di atas. Referensi Mesh -> Geometry baik-baik saja, b/c Mesh mengontrol instance Geometry . Itu masih bisa dipatahkan karena melakukan pemeriksaan tipe untuk perilaku khusus Kelas ( Geometry / BufferGeometry ).

Adapun referensi Geometry -> Mesh adalah untuk memberikan geometry.mergeMesh( mesh ) . Geometry adalah konsep tingkat yang lebih rendah dari Mesh , jadi saya akan membalikkannya sebagai mesh.mergeIntoGeometry( geo ) dan mencela mergeMesh .

Jika seseorang mendapatkan pr gabungan yang memperbaiki beberapa di antaranya, beri tahu saya dan saya akan memperbarui grafik untuk mencerminkan keadaan saat ini.

@ bhouston @ gero3 Saya tidak yakin bahwa dependensi siklik diperlukan untuk mendapatkan tingkat kegunaan/utilitas yang sama untuk perpustakaan matematika. Saya bisa saja salah, tetapi tidak bisakah kita membuat Vector3 benar-benar terisolasi/tidak peduli dengan sisa sistem dan memodifikasi prototipenya untuk mengakomodasi Matrix4 dalam modul Matrix4? Itu masuk akal bagi saya secara konseptual, karena Matriks lebih kompleks daripada Vektor. Saya pikir lebih baik memiliki urutan yang jelas di mana prototipe dan kelas dibangun untuk mencegah kecelakaan.

@bhouston @gero3 Saya pikir kita mungkin bisa melakukannya tanpa mengubah api sama sekali. Aku akan melihat-lihat dan melihat ada apa.

mengenai hal matematika, Anda bisa meletakkan semua "bantalan awal" di satu tempat, saya kira. tapi saya yakin tidak akan ada grafik 3js tunggal yang bisa digunakan yang tidak memiliki Vector3 dan Matrix4

Jika ada solusi yang tidak mengubah kinerja atau API perpustakaan matematika, saya siap untuk itu.

@cobalast tidak dapat menghapus dep siklus tanpa perubahan API, karena keduanya menyediakan metode yang menggunakan tipe lain. Vector3 Matrix4

Sedangkan untuk browserify compat, satu-satunya persyaratan kami adalah memindahkan instantiasi pada vars awal dari waktu definisi kelas (membuat mereka membuat instantiate saat pertama kali dijalankan). Jadikan ini malas seperti ini . Itu tidak akan berdampak pada API atau kinerja.

Saya pikir jenis perubahan itu baik-baik saja.

@kumavis Ah! Ya. Oke, saya mengerti sekarang. Itu cukup mudah.

Saya sepenuhnya mendukung TIGA dipecah menjadi modul yang lebih kecil dengan struktur yang diperlukan, karena alasan berikut:

  1. TIGA sangat besar. Jika pengguna hanya dapat meminta apa yang mereka butuhkan, itu mungkin mengurangi ukuran build klien. Misalnya react-bootstrap memungkinkan Anda melakukan hal-hal seperti var Alert = require('react-bootstrap/lib/Alert'); yang tidak menggabungkan setiap modul bootstrap.
  2. Ada beberapa "plugin", seperti OrbitControls.js yang memodifikasi TIGA objek global itu sendiri, menempatkan dirinya pada THREE.OrbitControls . Ini adalah anti-pola dalam kerangka kerja javascript modern karena membutuhkan TIGA untuk hadir di namespace global dalam proses pembuatan, alih-alih diperlukan oleh file yang membutuhkannya. TIGA juga melakukan ini secara internal, selalu memodifikasi ruang nama TIGA, yang tidak ideal untuk menyertakan TIGA modul tertentu.

menempatkan dirinya di THREE.OrbitControls

tetapi setiap bagian kode dalam 3js melakukan itu?

@DelvarWorld menulis:

Saya sepenuhnya mendukung TIGA dipecah menjadi modul yang lebih kecil dengan struktur yang diperlukan, karena alasan berikut:

Dulu saya pikir itu ide yang bagus untuk memecahnya, tetapi ada kesederhanaan untuk ThreeJS seperti sekarang. Ini lebih dapat digunakan oleh mereka yang baru mengenal 3D dalam bentuk yang sekarang dan yang telah menjadi prioritas bagi tim pengembang. Anda dapat menggunakan ThreeJS tanpa memerlukan sistem modul (yang jumlahnya banyak, dan tidak semuanya kompatibel sepenuhnya.)

Seperti @makc , saya juga bingung dengan saran @DelvarWorld untuk tidak meletakkan sesuatu di ruang nama TIGA.

Di mana / bagaimana mereka?

Bagi saya sepertinya pola yang baik untuk membuat hanya satu objek global, TIGA, di mana semua bagian (dan mungkin beberapa ekstensi/plugin) darinya.

Saya setuju dengan @DelvarWorld bahwa teknik put-it-on-the-global tidak baik untuk kesehatan basis kode - ini semacam argumen halus b/c meletakkannya di global itu sendiri bukanlah masalahnya, itu masalahnya grafik ketergantungan tersembunyi, dan praktik lain yang muncul dari ketersediaan global.

Tetapi argumen itu sebagian besar terbatas pada pengembangan internal dan struktur kode. Adapun memberikan perpustakaan sebagai bundel kode statis, menempatkan semua kelas di TIGA global masuk akal bagi saya.

Argumen kontra adalah bahwa ketika deserializing adegan THREE.js json, entri hanya dapat mencantumkan kelas mereka sebagai string yang dapat ditarik dari global seperti: THREE[ obj.type ] . Ini berfungsi dengan kelas yang tidak ada dalam standar three.js lib selama Anda mendefinisikannya di THREE sebelum Anda membatalkan serialisasi. Tidak yakin cara terbaik untuk mengganti perilaku ini tanpa THREE global.

Ini berfungsi dengan kelas yang tidak dalam standar three.js lib selama Anda mendefinisikannya di TIGA sebelum Anda membatalkan serialisasi. Tidak yakin cara terbaik untuk mengganti perilaku ini tanpa TIGA global.

Anda dapat melakukan pola ini (atau beberapa variannya) jika semuanya adalah modul:

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

Ada banyak perubahan yang datang dengan ES6 berkaitan dengan modul. Saya akan mengunjungi kembali modularitas ThreeJS pada saat itu.

Versi tiga yang dibangun (file javascript yang dapat diunduh orang secara manual) akan tetap memiliki semuanya di tiga ruang nama. anda bisa melakukan ini dengan file titik masuk untuk build adalah:

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

dll, yang masih berguna bagi pendatang baru, dan mudah untuk memulai.

Bagi mereka yang menggunakan tiga dari npm dan requirejs/browserify/webpack dalam build javascript modern, kita dapat melakukan sesuatu seperti

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

dll, yang mungkin mengurangi beberapa ukuran tiga yang dibangun ke dalam bundel ukuran klien. Saya bisa salah karena saya tidak tahu berapa banyak dari tiga "inti". Namun, saat ini, ini tidak mungkin karena tiga tidak menggunakan pernyataan require.

bagaimanapun pola modern memerlukan modul, dan alih-alih membuat semua kode Anda memodifikasi perpustakaan lain (memodifikasi TIGA global, buruk), kode Anda independen dan modular, dan menentukan apa yang diperlukan dengan pernyataan require , seperti kode sumber React .

Saya tidak berpikir bahwa saya mencoba membuat argumen lengkap untuk menggunakan sintaks require/modul akan membantu, karena ada banyak sumber bagus online tentang itu. Tetapi adalah buruk untuk mendorong orang lain untuk memodifikasi ruang nama TIGA untuk menambahkan plugin seperti OrbitControls.

Perlu diketahui @DelvarWorld bahwa ES6 memperkenalkan modul secara resmi ke dalam JavaScript dengan sintaks yang sangat spesifik dan berbeda:

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

@bhouston oh ya sama sekali, saya agnostik untuk membutuhkan vs impor (impor mungkin adalah pilihan yang lebih baik), hanya mendukung pola modul secara umum.

@bhouston @DelvarWorld @kumavis Salah satu proyek jangka panjang saya adalah menulis es5 -> es6 converter otomatis yang dapat mengakomodasi dan mengonversi modul commonjs/amd ke es6, dan semoga mengidentifikasi dan menulis ulang banyak javascript menggunakan konstruksi es6 seperti kelas/generator dan seterusnya. Seseorang dapat menggunakan transformasi browserify seperti es6ify sementara browser mengejar standar untuk menyiapkan kode untuk konsumsi. Memindahkan TIGA ke browserify hanya di tingkat internal adalah langkah pertama yang baik untuk mempersiapkannya untuk masukan ke alat semacam itu.

Ini di samping poin yang saya (tampaknya sangat buruk) coba buat. Saya ingin menghapus sebanyak mungkin ketergantungan siklik ini, terlepas dari masalah modularitas apa pun, karena saya yakin itu akan membuat THREE lebih stabil, fleksibel, dan mungkin akan menghilangkan banyak bug sebagai efek samping yang menyenangkan.

@cobalast https://github.com/mrdoob/three.js/pull/6252 digabungkan, harus mengurangi banyak siklus deps. Pikirkan Anda dapat menghasilkan grafik dep baru? Mungkin menjadikannya utilitas di repo alat konversi

selanjutnya adalah: membuat vars awal di Vector3 Matrix4 didefinisikan dengan malas pada penggunaan pertama, bukan pada waktu definisi

ada yang mau jadi relawan? harus cepat

Grafik telah diperbarui. http://jsbin.com/medezu/3/

Berikut adalah tangkapan layar:

snapshot3

Saya senang untuk melaporkan bahwa sejumlah besar circ deps Object3Ds telah dihilangkan. Kerja bagus @kumavis!

wow apakah itu basis kode yang sama? gila

Akan bekerja membuat pembuatan grafik menjadi bagian dari utilitas.

Pada pemeriksaan grafik saja, Shape dan Geometry tampaknya merupakan pohon kelas yang mungkin dapat diurai.

@cobalast pikir Anda bisa menerima ini?

membuat vars awal di Vector3 Matrix4 didefinisikan dengan malas pada penggunaan pertama, bukan pada waktu definisi

itu akan menjadi PR terhadap hulu dev sebagai lawan dari perubahan otomatis

Saya juga berpikir kita dapat menurunkan judul masalah dari "Masalah ketergantungan siklik yang serius" menjadi "ketergantungan siklik" -- situasinya telah jauh lebih baik!

@kumavis Tentu saja. Akan bekerja di atasnya ketika waktu memungkinkan.

Inilah keadaan pembersihan interdependensi saat ini seperti yang saya lihat:
( panah menunjukkan koneksi yang harus dihapus jika sesuai )

  • [x] Bahan
  • [x] Geometri
  • [x] Objek3D
  • [x] Matematika
  • [x] Bentuk

    • [x] Bentuk -> FontUtils

    • [x] Bentuk -> ExtrudeGeometry

    • [x] Bentuk -> BentukGeometri

    • [x] Jalur -> Bentuk

  • [ ] Kotak3

    • [ ] Kotak3 -> BufferGeometry

    • [ ] Kotak3 -> Geometri

Bentuk:

image

Kotak3:

image

Matematika:

Node ini saling berhubungan, tetapi memberikan kenyamanan yang cukup melalui ini.
image

Bentuk tampaknya memiliki dependensi dengan ExtrudeGeometry dan ShapeGeometry melalui kode seperti ini:

// 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;

};

Sekarang tampaknya Shape adalah subkelas dari Path , dan ExtrudeGeometry dan ShapeGeometry keduanya adalah subkelas dari Geometry . Jadi, Anda dapat mengetahui dependensi mana yang perlu dihilangkan dalam kasus yang ideal.

Ya ini termasuk dalam kategori yang sama dengan Vector3 <-> Matrix4 . Mereka terhubung untuk kenyamanan. Saya pikir itu ide yang buruk tetapi tidak layak untuk diperjuangkan. Saya akan menandainya sebagai selesai

Shape -> FontUtils dapat dihapus dengan menggunakan metode pemindahan seperti triangulate ke Utils yang lebih umum. Tapi bukan kemenangan besar untuk melakukan itu. Akan menandainya sebagai selesai.

Box3 -> BufferGeometry dan Box3 -> Geometry keduanya dapat dibersihkan.

Ini adalah kasus lain dari tidak menempatkan perilaku yang bergantung pada kelas pada kelas itu sendiri.

sumber :

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 ) {
    /* ... */
  }

  /* ... */

}

Dalam kedua kasus itu hanya mencoba untuk beralih melalui simpul dunia/posisi geometri. Saya ingin tahu apakah itu akan sangat menyederhanakan kode untuk membuat objek vertices yang malas pada BufferGeometry yang mencari nilai seperti yang diminta. Tidak yakin tentang dampak kinerja.

Sebagai alternatif, kita bisa menggunakan Geometry.computeBoundingBox :
Geometry
BufferGeometry

Box3 adalah titik masalah saat menjalankan browserify build. Lihat catatan di coballast/threejs-browserify-conversion-utility#21.

@kumavis Maukah Anda menguraikan solusi yang Anda rekomendasikan untuk menangani Box3/Geometry/BufferGeometry? Jika cepat, saya bisa menerapkannya.

Saya tidak dapat melihatnya sekarang, tetapi saya akan mulai dengan saran saya di atas untuk menggunakan geo.computeBoundingBox seperti yang diterapkan pada Geometry dan BufferGeometry sebagai ganti if/else di sini . Sebaliknya box3.setFromObj harus memanggil geometry.computeBoundingBox dan kemudian mengatur params berdasarkan box3 yang dihasilkan.

Itu akan menghapus Box3 -> BufferGeometry dan Box3 -> Geometry akhir dari deps melingkar. Beri tahu saya jika saya melewatkan sesuatu.

Hmm mungkin kode yang dihasilkan agak berbelit-belit, apa sebenarnya yang masuk akal di sini? Box3.setFromObject seharusnya tidak ada, tapi itu bukan pilihan. Geo seharusnya bisa menghasilkan box3, saya tidak punya masalah dengan itu. Ya saya kira Box3.setFromObject harus meminta geo untuk kotak pembatas / batas, tapi mungkin mereka harus meminta Object3D / Mesh untuk kotak pembatas / batas.

maaf agak cerewet. biar tahu apa yang Anda pikirkan.

Mungkin relevan: #6546

Tanpa sesuatu seperti ini, tidak mungkin untuk menganalisis dependensi dinamis tersebut dari skrip loader.

Berdasarkan pengujian saya, dependensi siklik tidak menjadi masalah dengan commonJSification. Mereka perlu ditangani dengan benar, dan seperti yang dinyatakan sebelumnya di utas ini, mereka membuat grafik ketergantungan cukup berantakan, tetapi mereka tidak mencegah THREE.js untuk bekerja di lingkungan commonJS (saat diubah, tentu saja).

Saya baru saja menerbitkan versi commonjs lengkap di npm sebagai three.cjs menggunakan tiga transpiler commonjs saya

Catatan: agar ini berfungsi, saya harus melakukan cherrypick #6546 secara manual pada master. Sementara dependensi dinamis bekerja dengan baik di node.js, mereka tidak dapat bekerja di Browserify (atau alat cjs ke browser lainnya) karena mereka perlu melakukan analisis dependensi statis.

Bukti browserify: http://requirebin.com/?gist=b7fe528d8059a7403960

@kamicane FYI - Di sinilah TIGA ditambahkan sebagai argumen fungsi anonim ke Raycaster (sebelumnya Ray ).

Saya memahami perlunya fungsi anonim (untuk mencegah kebocoran global di browser), namun argumennya berlebihan, dan membuat seluruh file termasuk dalam kategori dependensi yang dihitung. Sementara argumen dapat dihapus secara dinamis dengan modifikasi AST, itu tidak akan pernah menjadi solusi antipeluru (tergantung pada apa yang ditulis ke argumen, dibaca dari argumen, dll.). Analisis statis menjadi hampir tidak mungkin. Intervensi manual diperlukan dalam kasus ini.

Sekarang Raycaster lebih ringan, kita bisa mencobanya untuk membuatnya seperti kelas lainnya.

@mrdoob , @Mugen87 kami menggunakan Rollup untuk hanya menggunakan bagian Three.js yang benar-benar kami butuhkan. Saat kami menjalankan yang dibangun, kami masih mendapatkan peringatan berikut:

(!) 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

Apakah masih ada dependensi melingkar di Three.js atau apakah kita melakukan sesuatu yang salah?

Vector3 dan Matrix4 terikat satu sama lain, jika Anda menarik satu, Anda harus menarik yang lain. Ketergantungan melingkar secara teknis harus diizinkan.

@bhouston ya saya mengerti, terima kasih atas petunjuknya. Ya, dependensi melingkar diizinkan dan rollup tidak membuat masalah, tetapi saya tidak yakin apakah praktik yang baik untuk memiliki dependensi melingkar. Vector3 hanya bergantung pada Matrix4 karena multiplyMatrices dan getInverse , untuk lebih jelasnya lihat (https://github.com/mrdoob/three.js/ gumpalan/dev/src/math/Vector3.js#L315)

@roomle-build Idk, man, hanya karena secara eksplisit mereferensikan konstruktor Matrix4? bagaimana dengan

    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;

},

?

Anda dapat mengatakan bahwa Anda dapat melewati { elemen: [....] } dan itu akan berhasil, tetapi kita semua tahu itu mengharapkan Matrix4 di sana

Mari kita mulai dengan Vector3 .

Vector3 tergantung pada Matrix4 karena project dan unproject :

    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 tergantung pada Quaternion karena applyEuler dan applyAxisAngle :

    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 ) );

        };

    }(),

Saran?

Saya tidak yakin apakah kita perlu menghapus dependensi melingkar dengan segala cara. Tapi saya bisa membayangkan untuk memindahkan multiplyMatrices ke modul Math . Tanda tangan kemudian akan berubah menjadi multiplyMatrices( a: Matrix4, b: Matrix4, result: Matrix4 ): Matrix4 . Di dalam Vector3 Anda kemudian dapat import { multiplyMatrices } from './Math'; hal yang sama dapat dilakukan di Matrix4 (untuk menjaga permukaan API Matrix4 tetap sama).

Saya baru saja melihat sekilas (tidak melihat kasus Quaternian - hanya Vec3/Mat4 ) dan saya juga tidak yakin tentang implikasi kinerja dan konsekuensi untuk sisa basis kode. Selain itu, saya juga tidak yakin bahwa benar-benar perlu untuk menghapus dependensi melingkar ini. Hanya ingin berbagi pemikiran saya karena @mrdoob meminta saran

@roomle-build jadi pada dasarnya buat lebih banyak modul demi tidak ada ketergantungan melingkar, tetapi Anda masih akan menggunakan semua modul itu? ini mungkin bisa lebih masuk akal jika setiap metode matematika akan menjadi modulnya sendiri, maka Anda hanya menarik yang Anda gunakan, tetapi itu akan menjadi banyak modul.

@makc tidak juga. Ini akan menjadi satu modul besar dengan banyak fungsi "pembantu" kecil. Ini juga akan membantu untuk goyangan pohon dll. Modul matematika dapat terlihat seperti:

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

Dan modul yang mengkonsumsi akan melakukan sesuatu seperti:

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

Saat menggabungkan semuanya, rollup melakukan keajaiban dan membuat bundel yang paling efisien.

Inilah yang dilakukan banyak perpustakaan populer. Sebenarnya RxJS mengalihkan "logika" import mereka juga ke pola yang saya jelaskan. Di sana terlihat seperti:

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

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

Anda dapat membaca tentang "mengapa dan bagaimana" mereka mengubah hal ini di RxJS 6 di beberapa posting blog misalnya: https://auth0.com/blog/whats-new-in-rxjs-6/

Tapi seperti yang saya katakan, itu hanya sebuah pemikiran dan saya tidak yakin tentang semua implikasinya terhadap seluruh basis kode. Juga modul math saat ini tidak "disiapkan" untuk digunakan seperti ini. Saat ini semua metode pada modul matematika dilampirkan "agak statis". Ini juga mencegah rollup mendeteksi apa yang benar-benar dibutuhkan...

@roomle-build hmm jadi Anda mengatakan bahwa rollup dapat memahami jika kode dalam cakupan yang sama sebenarnya tidak membutuhkan seluruh cakupan, bagus.

Anda berbicara tentang bergerak menuju pendekatan fungsional (fungsi mengambil objek) daripada pendekatan berorientasi objek (objek yang memiliki fungsi anggota.) Ini adalah hal yang nyata tetapi mengingat bahwa Three.JS sepenuhnya berorientasi objek, mengusulkan jenis perubahan ini adalah yang cukup besar dan itu akan merusak semua kode yang ada.

Saya tidak yakin bahwa argumen yang mendukung perubahan ini begitu signifikan pada titik ini untuk membenarkan pemutusan semua kompatibilitas ke belakang.

@makc tidak juga. Ini akan menjadi satu modul besar dengan banyak fungsi "pembantu" kecil. Ini juga akan membantu untuk goyangan pohon dll. Modul matematika dapat terlihat seperti:

Jika ini yang diusulkan, itu harus dijelaskan dengan benar. Ini adalah perubahan Three.JS dari gaya desain berorientasi objek ke desain fungsional.

@roomle-build hmm jadi Anda mengatakan bahwa rollup dapat memahami jika kode dalam cakupan yang sama sebenarnya tidak membutuhkan seluruh cakupan, bagus.

ya rollup memahami bagaimana semua impor berhubungan satu sama lain dan melakukan pengguncangan pohon, penghapusan kode mati dll. Versi baru dari rollup juga dapat melakukan "chunking" dan banyak hal bagus lainnya. Tetapi struktur proyek saat ini tidak memanfaatkan fitur-fitur ini sepenuhnya.

Anda berbicara tentang bergerak menuju pendekatan fungsional (fungsi mengambil objek) daripada pendekatan berorientasi objek (objek yang memiliki fungsi anggota.) Ini adalah hal yang nyata tetapi mengingat bahwa Three.JS sepenuhnya berorientasi objek, mengusulkan jenis perubahan ini adalah yang cukup besar dan itu akan merusak semua kode yang ada.

Saya tidak berpikir kedua paradigma ini saling eksklusif. Saya pikir Anda dapat mencampur dan mencocokkan dua paradigma ini. Saya juga tidak mengusulkan untuk mengubah ke pemrograman fungsional. Saya hanya ingin menjelaskan cara untuk menghilangkan ketergantungan siklik. Anda juga dapat melampirkan metode multiplyMatrices ke objek Math . Tetapi jika seseorang menulis ulang hal semacam ini, masuk akal untuk mempertimbangkan menggunakan fitur modul ES6. Tapi seperti yang saya katakan, saya bukan ahli dari basis kode Three.js dan itu hanya pemikiran bagaimana menghilangkan ketergantungan siklik. Saya pikir Three.js adalah proyek yang luar biasa dengan basis kode yang hebat dan saya tidak ingin mengomel. Jadi saya harap tidak ada yang merasa tersinggung dengan komentar saya

Saya tidak yakin apakah kita harus mendiskusikan keputusan desain dalam suatu masalah. Apakah Anda memiliki tempat di mana hal semacam ini lebih cocok?

BTW gl-matrix adalah perpustakaan matematika fungsional: https://github.com/toji/gl-matrix/tree/master/src/gl-matrix

@roomle-build

Saat ini semua metode pada modul matematika dilampirkan "agak statis".

Bagaimana?

@mrdoob Saya percaya bahwa dengan desain fungsional gl-matrix setiap fungsi say vec3 (dalam file vec3 yang saya tautkan dalam komentar saya sebelumnya) diekspor satu per satu. Ini memungkinkan Anda untuk memilih dan memilih fungsi mana yang akan diimpor. Anda tidak perlu membawa semua vec3.

Sedangkan dengan Three.JS, karena menggunakan desain berorientasi objek, semua fungsi matematika untuk Vector3 dilampirkan ke prototipe objek Vector3 dan Anda hanya mengimpor kelas Vector3 itu sendiri.

Jadi impor di Three.JS adalah seluruh kelas sedangkan dengan pendekatan fungsional Anda mengimpor fungsi individu.

(Hal lain yang sangat rapi tentang pustaka gl-matrix adalah bahwa semua fungsi individual tidak menggunakan fungsi lain, @toji pada dasarnya telah memasukkan versi optimal dari semua matematika ke dalam setiap operasi individual. Ini mungkin cukup efisien dalam hal kecepatan tetapi itu mengarah ke perpustakaan yang sulit dipelihara.)

Saya tidak berpikir kita perlu memfaktorkan ulang bagian Three.JS ini kecuali mungkin untuk menghilangkan referensi di /math ke direktori lain di three.js. Pustaka matematika cukup kecil dan tidak pernah benar-benar muncul dalam tes profil saya hari ini. Ya, ini tidak efisien secara maksimal, tetapi cukup dekat dengan keterbacaan yang dapat dipertahankan dan kemudahan penggunaan.

@bhouston Mengerti. Terima kasih banyak atas penjelasannya! 😊

Saya hanya ingin menindaklanjuti topik tersebut. Tetapi saya ingin kembali dari importing function vs importing classes ke topik penyelesaian cyclic dependencies . (Saya juga tidak mengerti mengapa import { someFunction } from 'SomeModule' kurang dapat dipelihara daripada import SomeClass from 'SomeModule' , tapi itu jelas bukan topik masalah/percakapan ini.

Untuk mengatasi ketergantungan siklik, dimungkinkan untuk menempatkan fungsionalitas ke dalam Kelas yang terpisah. Anda dapat melampirkan metode multiplyMatrices ke Math-Class atau membuat Multiplier-Class yang memiliki metode multiplyMatrices . Tetapi seperti yang saya katakan sebelumnya, saya tidak yakin apakah kita harus menghapus dependensi siklik. Jika keputusannya adalah untuk tidak menghapusnya, saya pikir masalah ini bisa selesai

Setelah menyelesaikan #19137, ini dapat ditutup sekarang .

@Mugen87 wow, 5 tahun! selamat akhirnya berhasil :fire: :clap:
Saya sangat senang membuat grafik itu saat itu :smile_cat:

Apakah halaman ini membantu?
0 / 5 - 0 peringkat