Ejtőernyő feladat leírása
Megoldás menete
Teljes forráskód
Készítse el a következő Three.js modellezést.
- 0, Kezdeti lépések
- A, Hozzon létre egy html fájlt, ami a megoldás keretét adja.
- B, Inicializálja a Three.js keretrendszert.
- 1, Talaj
- A, Modellezzen talajt sík lapként az XZ síkban.
- B, A talajra feszítse rá a marstex2.jpg képet textúraként.
- 2, Készítsen egy ejtőernyőt az alábbi részekből: ernyő, kötélzet, csomag.
- A, Az ernyő egy kúp legyen.
- B, A kötélzet vonalakból álljon, egy kúpot formálva, ami a kúp alapkörét a kocka középpontjával köti össze. (Lehet drótvázas kúp is.)
- C, A csomag egy kocka legyen.
- D, Ezeket foglalja egy közös csoportba.
- 3, Animáció
- A, Az ernyő billegjen, azaz végezzen oda-vissza forgó mozgást a csúcspontja körül.
- B, Az ernyő folyamatosan zuhanjon; ha leért, induljon újra a képernyő tetejéről.
- C, Figyeljen a mozgás paramétereinek megfelelő intervallumaira!
- 4, Megvilágítás
- A, Definiáljon sötétszürke színű ambiens fényt.
- B, Hozzon létre felső, kicsit oldalsó irányból reflektorfényt.
- C, Az ejtőernyő részei vessenek árnyékot a talajra.
- 5, Egyebek
- A, A nevét jelenítse meg az ablak bal felső részében.
Pontozási szempontok
A részpontszámok összege 22. Ezekből elegendő 20 pontnyit elkészíteni, tetszőleges módon összeválasztva.
- 1 pont: Talaj megfelelő pozícióban
- 2 pont: Textúra betöltése és alkalmazása a talajra
- 1 pont: Ejtőernyő kúp
- 1 pont: Csomag kocka
- 2 pont: Kötelek
- 1 pont: Csoportba foglalás
- 2 pont: Ejtőernyő billegő mozgása
- 2 pont: Ejtőernyő lefelé mozgása
- 2 pont: Animáció megfelelő intervallumokban
- 1 pont: Megvilágításhoz megfelelő anyagmodell
- 1 pont: Ambiens fény
- 2 pont: Reflektorfény
- 2 pont: Árnyékolás bekapcsolása
- 2 pont: A bal felső sarokba szövegesen írja ki a nevét
Megoldás
0A, Hozzuk létre a megoldást tartalmazó html fájlt.
A mintapéldában az 5A feladatot is megoldottuk. Ez az egyetlen feladat, amihez HTML és CSS ismeretekre van szükség, viszont egyáltalán nem kell hozzá JavaScript.
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
<title>Minta ZH</title>
<script src="three.min.javascript"></script>
<style>
body {
margin: 0;
padding:0;
overflow: hidden;
position:relative;
}
canvas {
width: 100%;
height: 100%
z-index: 0;
}
#caption {
position: absolute;
top: 10px;
left: 10px;
width: 50%;
height: 50%;
text-align: left;
z-index: 100;
display: block;
color: yellow;
}
</style>
</head>
<body onload="init()">
<div id="caption">Elektro M. Ágnes</div>
<script>
function init(){
"use strict";
//-----------
// ide kerül majd a feladatot
// megoldó script
//-----------
}
</script>
</body>
</html>
0B, Inicializálja a Three.js-t.
Kelleni fog az ablak szélessége (WIDTH), és magassága (HEIGHT), valamint az, hogy a render és handleWindowResize függvények ehhez hozzáférjenek. Ezért az init függvényen belül hozzuk létre az összes többi függvényt, hogy a globális névteret ne szennyezzük be.
Kell egy renderer, ami majd mindent kirajzol a képernyőre, egy scene, egy kamera, és egy textúra betöltő. Ez utóbbi miatt szükséges a saját gépünkön is webszerverrel kiszolgálni a fájlokat, mert a böngészők nem engednek a helyi fájlrendszerhez hozzáférést a javascriptnek.
A scene legyártása után hozzáadjuk az ablak átméretezését figyelő eseménykezelőt, és elindítjuk a renderelést.
function init(){
"use strict";
var HEIGHT = window.innerHeight;
var WIDTH = window.innerWidth;
var aspectRatio = WIDTH / HEIGHT;
var renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize( WIDTH, HEIGHT );
renderer.setClearColor( 0x000000 );
document.body.appendChild( renderer.domElement );
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 75, aspectRatio, 0.1, 100000 );
camera.position.x = 200
camera.position.y = 300;
camera.position.z = 400;
camera.lookAt( scene.position );
var textureLoader = new THREE.TextureLoader();
//-----------------------------------------------------
// ide kerül majd a scene felépítése
//-----------------------------------------------------
window.addEventListener( 'resize', handleWindowResize, false );
render();
function radian(fok){
return fok * Math.PI / 180;
}
function handleWindowResize() {
HEIGHT = window.innerHeight;
WIDTH = window.innerWidth;
renderer.setSize( WIDTH, HEIGHT );
aspectRatio = WIDTH / HEIGHT;
camera.aspect = aspectRatio;
camera.updateProjectionMatrix();
}
function render() {
requestAnimationFrame( render );
//-----------------------------------------------------
// ide kerül majd az animációt vezérlő kód
//-----------------------------------------------------
renderer.render( scene, camera );
}
}
1A, Modellezzen talajt sík lapként az XZ síkban.
A PlaneGeometry alapértelmezetten az XY síkba teszi a síkot. Mivel az xyz jobbsodrású koordinátarendszer, ezért az x tengely mentén -90 fokkal kell elforgatni, hogy a helyére kerüljön.
var groundGeometry = new THREE.PlaneGeometry(1024, 1024);
var groundMaterial = new THREE.MeshPhongMaterial( { color: 0xffffff } );
var groundMesh = new THREE.Mesh( groundGeometry, groundMaterial );
groundMesh.rotation.x=radian(-90);
1B, A talajra feszítse rá a marstex2.jpg képet textúraként.
Nem lenne szükség arra, hogy kétoldalas legyen az alakzat, lehetne hátulról átlátszó is, de ha véletlenül +90 fokkal forgattuk el a síkot, így sem fog meglepetés érni.
A végén adjuk hozzá a scene-hez az objektumot. Minden alakzatot hozzá kell adni, kivéve amiket csoportba foglaltunk, ott csak a csoporthierarchia tetején lévőt kell.
groundMaterial.map = textureLoader.load( 'marstex2.jpg' );
groundMesh.material.side = THREE.DoubleSide;
scene.add( groundMesh );
2, Készítsen egy ejtőernyőt az alábbi részekből: ernyő, kötélzet, csomag.
2A, Az ernyő egy kúp legyen.
Két értéket később is fel fogunk használni, ezért ki lettek vezetve változóba. A kocka középpontjától az ernyőt alkotó kúp alapkörének középpontjáig 60 magas lesz az alakzat,
az ernyő kúpja pedig 10 magas.
Mindegy, hogy az elején hova pozicionáljuk az objektumokat, egymáshoz képest kell jó helyen lenniük, majd a render függvényben átírjuk a pozíciójukat.
var object_height=60;
var para_height=10;
var coneGeometry = new THREE.ConeGeometry( 30, para_height, 32 );
var coneMaterial = new THREE.MeshPhongMaterial( { color: 0x0000ff } );
var coneMesh = new THREE.Mesh( coneGeometry, coneMaterial );
coneMesh.position.x=0;
coneMesh.position.y=0;
coneMesh.position.z=0;
2B, A kötélzet vonalakból álljon, egy kúpot formálva, ami a kúp alapkörét a kocka középpontjával köti össze. (Lehet drótvázas kúp is.)
Meglepően egyszerű bekapcsolni egy alakzaton, hogy drótvázas legyen. A kúp referencia pontja a magassága felénél van, ezért kerül magasság/-2-be a kötélzet.
A negatív magasság érték miatt fejjel lefele fog megjelenni, ami pont jó nekünk.
var wireGeometry = new THREE.ConeGeometry( 30, 0-object_height, 8 );
var wireMaterial = new THREE.MeshBasicMaterial( { color: 0xff0000 , wireframe:true} );
var wireMesh = new THREE.Mesh( wireGeometry, wireMaterial );
wireMesh.position.x=0;
wireMesh.position.y=0-object_height/2;
wireMesh.position.z=0;
2C, A csomag egy kocka legyen.
Hatszor hatos kocka, zöld, és az egész objektum magasságával lejjebb van tolva a középpontja.
var boxGeometry = new THREE.BoxGeometry( 6, 6, 6 );
var boxMaterial = new THREE.MeshPhongMaterial( { color: 0x00ff00 } );
var boxMesh = new THREE.Mesh( boxGeometry, boxMaterial );
boxMesh.position.x=0;
boxMesh.position.y=0-object_height;
boxMesh.position.z=0;
2D, Ezeket foglalja egy közös csoportba.
Nem tudom, hogy ez mennyire szép vagy csúnya megoldás, de az ernyő kúpja lesz egyben a csoport legfelső tagja is. Hogy a billegő mozgás forgáspontja jó helyen legyen,
ezért para_height/2 hosszal eltoljuk a kúp referencia pontját, hogy pont a csúcsára kerüljön. Majd hozzáadjuk az ernyő többi részét is, ezeket nem kell a scene-hez adni, elég csak a kúpot.
coneGeometry.applyMatrix( new THREE.Matrix4().makeTranslation( 0, para_height/2, 0 ) );
coneMesh.add( boxMesh );
coneMesh.add( wireMesh );
scene.add( coneMesh );
4, Megvilágítás
4A, Definiáljon sötétszürke színű ambiens fényt.
Ez kb. sötétszürke, és az intenzitását is lejjebb kell venni, hogy ne nyomja el a reflektor fényét.
var ambientLight = new THREE.AmbientLight( 0x708090, 0.4 );
scene.add( ambientLight );
4B, Hozzon létre felső, kicsit oldalsó irányból reflektorfényt.
Azért került x irányban balra a reflektor, mert így a megvilágítás sacc/kb. egybeesik a betöltött talaj textúrán látható árnyékokkal. Ügyelni kell a distance jó megadására is, ehhez érdemes használni a SpotLightHelper objektumot.
var spotLight = new THREE.SpotLight( 0xffffff, 1 );
spotLight.position.set( -1000,1000,0 );
spotLight.angle = radian(11.25);
spotLight.distance = 2000;
spotLight.penumbra = 0.8;
scene.add( spotLight );
4C, Az ejtőernyő részei vessenek árnyékot a talajra.
A reflektorfénynél is be kell kapcsolni, hogy vessen árnyékot, illetve az egyes objektumokon is, hogy lehessen rájuk árnyékot vetni.
renderer.shadowMap.enabled = true;
groundMesh.receiveShadow=true;
boxMesh.receiveShadow=true;
boxMesh.castShadow = true;
wireMesh.castShadow = true;
coneMesh.castShadow = true;
spotLight.castShadow = true;
3, Animáció
function render() {
3C, Figyeljen a mozgás paramétereinek megfelelő intervallumaira!
Egyrészt a render függvényt csak animációs frame-ben hívjuk meg, amit a böngésző biztosít számunkra.
Másrészt, az inga képletében méter és másodperc szerepel, a javascript viszont millisecben adja vissza az aktuális időt, ezt konvertálni kell.
Matematikai inga
requestAnimationFrame( render );
var seconds = new Date().getTime()/1000;
var omega=Math.sqrt(object_height/10);
3A, Az ernyő billegjen, azaz végezzen oda-vissza forgó mozgást a csúcspontja körül.
A maximális kitérés (4 fok) és az idő függvényében kiszámítjuk az inga aktuális szögét. Úgy fog körmozgást végezni, ha egyszerre két irányban is "billeg", de ellentétes fázisban, ezért lett szinusz a második szög képletében.
var fi = radian(4)*Math.cos(omega*seconds);
var ro = radian(4)*Math.sin(omega*seconds);
coneMesh.rotation.z=fi;
coneMesh.rotation.x=ro;
3B, Az ernyő folyamatosan zuhanjon; ha leért, induljon újra a képernyő tetejéről.
Erre a legegyszerűbb megoldás az, ha az aktuális időt maradékosan osztjuk az ejtés kezdeti magasságával, és hogy kellően gyorsan essen, a tizedmásodperceket is felhasználjuk a magasság számítására.
A drop értéke 30 másodperc alatt fogja elérni újra a nullát, ekkor visszakerül a kezdőpozícióba az objektum.
var drop = (10*seconds)%300;
var posY=object_height+300-drop;
coneMesh.position.y=posY;
Végül kirajzoljuk a módosított scene-t.
renderer.render( scene, camera );
}