Skip navigation

Külső erőforrások aszinkron betöltése

Feladat

Modellező programokban gyakran előfordul, hogy külső fájlból vagy URL címről töltünk be adatot, mint például:

  • textúra kép,
  • 3D geometria és anyag,
  • elmentett színtér objektum definíciók.

Emlékeztető: A webböngészők tiltják, hogy a weboldalunk hozzáférjen a lokális fájlrendszerhez, ezért mindenképpen webszerveren keresztül kell ezt intéznünk. Vagy be kell állítanunk sajátot, vagy a WebStrom-ból indítva eleve webszerveren keresztül indul a programunk. További technikai információk a korábbi anyagrészben találhatók.

Az általános megközelítés az, hogy a betöltés aszinkron módon történik, vagyis a betöltés elindítása után a függvény rögtön visszatér, a programunk futása folytatódik. A probléma az, hogy az erőforrás ekkor még nem áll rendelkezésre! A betöltés végeztével egy visszahívható (callback) függvény aktiválódik, amiben reagálni tudunk erre az eseményre, például hozzáadjuk a betöltött objektumot a színtérhez, hozzárendeljük a betöltött textúra képet az anyag objektumhoz, stb. A kezelésre többfelé megközelítés is lehetséges.

  • Az animáló/renderelő részben figyeljük, hogy mely objektumok érhetők már el, és csak ezeket használjuk fel. A betöltés előrehaladtával egyre több objektum jelenik meg. Ez az ellenőrzés plusz kódot jelent, valamint a modellezési élmény is fura lesz, így ez ritkán jó megoldás.
  • A programunk elején elvégezzük a betöltést, és amikor minden elem rendelkezésre áll, akkor indítjuk el a modellezést. Ebben az esetben figyelnünk kell arra, hogy ha a folyamat időigényes, akkor tájékoztassuk a felhasználót erről, illetve az előrehaladás folyamatáról.
  • Ha több erőforrás betöltésére is szükség van, akkor kezelni kell, hogy mindegyik betöltődjön a modellezés indítása előtt. Itt a probléma az, hogy a betöltés nem feltétlenül a hívás sorrendjében fog elvégződni! Ezt külön kezelni kell. A Three.js biztosít erre a feladatra betöltés kezelő (LoadingManager) objektumot, amit lentebb ismertetünk.

FileLoader és leszármazottjai

Az URL címről való betöltéseket megfelelő betöltő (loader) objektummal végezhetjük. Az alap osztály a FileLoader, amely az alap funkcionalitást biztosítja, ebből származnak le a különféle erőforrás-típusok betöltő objektumai. Néhány ide kapcsolódó objektum dokumentációja:

  • FileLoader: alap objektum, közvetlenül ritkán használjuk.
  • TextureLoader: textúra kép betöltésére.
  • OBJLoader: Wavefront OBJ formátumú felszínhálók és anyagok betöltésére, például Blender-ből mentve.
  • Korábbi Three.js verziókban elérhető volt egy JSONLoader objektum, amely a Three.js saját formátumát tudta olvasni, ami egy színtérgráf objektumainak paramétereit írta le. Ez az r99 verzióban eltávolításra került.

Példánkban a textúra betöltésen keresztül mutatjuk be a folyamatot, de a többi objektum működése is ennek megfelelő. Első lépésként egy betöltő objektumot kell létrehoznunk, majd a load függényét kell megfelelő módon felparamétereznünk az alábbiak szerint:

  • Az első paraméter a betöltendő fájl neve vagy URL címe.
  • A második az a függvényünk, amely akkor hívódik meg, amikor a betöltés véget ér. Paraméterként megkapjuk a betöltött THREE.Texture objektumot. Más típusú betöltő objektumok az adott típusnak megfelelő objektumot adnak vissza eredményül.
  • A harmadik paraméter egy újabb függvényünk, amellyel a betöltés előrehaladásáról tudunk tájékoztatást adni (konzol szöveg, overlay szöveg, progress bar, ...).
  • A negyedik paraméter egy hiba függvény, amely akkor hívódik meg, ha a betöltés sikertelen, például érvénytelen a fájlnév vagy az URL cím.
var loader = new THREE.TextureLoader();
loader.load(
'assets/tc-earth_daymap_surface_rs.jpg',
function ( texture ) {
// Ha a betöltés kész...
var geometry = new THREE.SphereGeometry( 8, 80, 80 );

var material = new THREE.MeshPhongMaterial();
material.map = texture;
mesh = new THREE.Mesh( geometry, material );
// Aszinkron betöltés, egy ideig nem lesz érvényes mesh!
scene.add( mesh );

},
// Betöltés előrehaladása
function ( xhr ) {

console.log( (xhr.loaded / xhr.total * 100) + '% betöltve.' );
},
// Hiba betöltés közben
function ( xhr ) {

console.log( 'Hiba történt textúra betöltés közben!' );
}
);

Ne felejtsük el, hogy a fájl betöltése aszinkron módon történik, vagyis a program futása folytatódik a betöltés közben. Emiatt elő fog fordulni, hogy az animate() függvény meghívódik, mielőtt a mesh hozzáadásra kerülne a színtérhez. Az animate() függvényben csak akkor próbáljunk hozzáférni a mesh objektumhoz, ha az már létezik. Például:

if( mesh ) {
// Ha már létezik az objektum...
mesh.rotation.y += 0.01;

}

Textúra betöltés esetén megtehetjük azt is, hogy az objektumot a textúra betöltése előtt létrehozzuk és a színtérhez adjuk. Ekkor a rendelesénél textúrázatlanul ugyan, de meg fog jelenni. Ha a betöltés kész, hozzárendelhetjük az anyaghoz. Ha ennek ellenére nem jelenik meg a mintázat, az anyag objektumnak jelezzük, hogy változott, frissítése szükséges.

material.needsUpdate = true;

A legáltalánosabb, és egyben a leginkább javasolt megoldás az, ha a renderelést csak akkor indítjuk el, amikor már minden szükséges erőforrás sikeresen betöltésre került, a betöltési műveletek közben pedig informáljuk a felhasználót az előrehaladásról. Erről a következő, LoadingManager részben olvashatunk. Ez a megközelítés képek és objektumok párhuzamos betöltésére is használható.

Példák a használatára:

LoadingManager

Több erőforrás betöltése esetén célszerű egy betöltést vezérlő menedzser objektumot használni. Ez követi a folyamatban lévő betöltéseket, és akkor hívja meg az onLoad() függvényét, ha minden elem sikeresen betöltődött.

Használatára a 08_02_ThreeJsLoadingManagerTester program mutat példát.

Először definiáljuk a menedzser objektumot és rendeljük hozzá a kezelőfüggvényeket.

var manager = new THREE.LoadingManager();
manager.onProgress = function ( item, loaded, total ) {
console.log( 'Manager onProgress: loading of', item, 'finished; ', loaded, ' of ', total, ' objects loaded.' );
var progressString = 'Loading of ' + item + ' finished;<br/>' + loaded + ' of ' + total + ' objects loaded.';
appendInfoPanelText( progressString );
};
manager.onLoad = function () {
console.log( 'Manager onLoad called, render started.' );
appendInfoPanelText( 'Manager onLoad called, rendering started.' );
animate();
};

A textúrák és felszínháló adatok betöltése az eddigiekhez hasonlóan megy, a betöltő objektumok konstruktorának át kell adni paraméterként a manager objektumot.

var textureLoader = new THREE.TextureLoader( manager );
var texture = textureLoader.load( 'assets/texture/tc-earth_daymap.jpg' );
var moonTexture = textureLoader.load( 'assets/texture/moonmap1k.jpg' );
var objLoader = new THREE.OBJLoader( manager ); objLoader.load( 'assets/models/uvsphere.obj', function ( loaded ) {
// ...
} );