Kolmemõõtmeline graafika Kolmes mõõtmes kujundite ekraanile paigutamiseks, liigutamiseks ning keeramiseks on Java keelde loodud abivahendite komplekt nimega Java 3D. See tuleb lisaks kompilaatorile/interpretaatorile masinasse installeerida ning seejärel saab temasse kuuluvaid vahendeid kasutada. Kui soovida, et seilur suudaks oma ekraanil selle paketi abil loodud rakendeid näidata, siis tuleb 3D ka brauseri Java virtuaalmasina juurde installeerida. Pea kõike siinse paketi abil loodut õnnestub ka tavavahendite ning keskkooli matemaatika abil lahendada, sest sisuliselt on ju ikka tegemist mingil hetkel ekraanipunktide värvimise ning hiire- ja klaviatuurisündmustele reageerimisega, kuid siin õnnestub veidi arvutamisvaeva kokku hoida ning samuti aitab operatsioonisüsteemi kaudu graafikakaardi/kiirendiga suhtlemine pildi arvutamist ja liigutamist sujuvamaks muuta. Nagu iga uus asi, on seegi API arenemisjärgus (2001), kuid juba on siinsete vahenditega täiesti võimalik midagi ette võtta. Programmeerija tarvis lisatakse mõniteist paketti hulga klasside ning meetoditega. "Valmis" pakettideks on javax.media.j3d üldisemate klassidega kolmemõõtmeliste kujundite koostamiseks, liigutamiseks ja valgustamiseks. javax.vecmath aitab hoida ja töödelda kolme mõõtme kohta käivaid andmeid. Abipaketid algavad com.sun.j3d-ga ning sealt leiab valmis kujundeid ning muid realisatsioone. Nende pakettide nimed võivad tulevikus tõenäolisemalt muutuda, kuid võimalused ja põhimõtted peaksid ka edaspidi samasuguseks jääma. Kuna Java3D kasutab operatsioonisüsteemist sõltuvaid vahendeid, siis kuulub kolmemõõtmeline lõuend Canvas3D AWT (mitte Swingi) komponentide hulka. Selle lõuendi sisse saabki oma kolmemõõtmelist programmi ehitama hakata. Lõuendi sees paiknevad objektid tuleb asetada puukujulisse hierarhiasse. Puu juureks on BranchGroup. Sinna külge saab lisada nii kujundeid kui edaspidi ka sõlmi kujundite nihutamiseks ja keeramiseks. SimpleUniverse loob kesta, mille abil ühendab kujundipuu 3D lõuendiga ning hoolitseb kasutaja asukoha eest. Järgnevas näites ilmub ekraanile värviline kuup (õigemini üks külg temast). import java.applet.Applet; import java.awt.*; import com.sun.j3d.utils.geometry.ColorCube; import com.sun.j3d.utils.universe.SimpleUniverse; import javax.media.j3d.*; public class Kuup1 extends Applet { public Kuup1() { setLayout(new BorderLayout()); Canvas3D c = new Canvas3D(SimpleUniverse.getPreferredConfiguration()); add(c, BorderLayout.CENTER); BranchGroup juur = new BranchGroup(); juur.addChild(new ColorCube(0.5)); juur.compile(); SimpleUniverse u = new SimpleUniverse(c); u.getViewingPlatform().setNominalViewingTransform(); u.addBranchGraph(juur); } public static void main(String[] args) { Frame f=new Frame("Kuup"); f.add(new Kuup1()); f.setSize(300, 300); f.setVisible(true); } } Nüüd veidi lähem seletus. Canvas3D c = new Canvas3D(SimpleUniverse.getPreferredConfiguration()); loob 3D lõuendi vaikeparameetritega, et saaks järgnevalt loodud universumi panna oma andmeid sellele lõuendile saatma. setLayout(new BorderLayout()); add(c, BorderLayout.CENTER); määravad paigutushalduriks BorderLayout'i mille tulemusena õnnestub loodud lõuend paigutada rakendi (graafikakomponendi) keskele ning kuna sellesse rakendisse muid komponente paigutatud pole, siis venitatakse kolmemõõtmelise graafikakomponendi tarvis loodud lõuend üle kogu rakendile eraldatud pinna laiali. BranchGroup juur = new BranchGroup(); loob hierarhia alguse, mille külge saan edasi kujundeid paigutada. Isendile annan nimeks juur, sest tegemist olekski nagu puu juurega. juur.addChild(new ColorCube(0.5)); loob värvilise kuubi küljepikkusega 0,5 suhtelist pikkusühikut ning paigutab selle juure järglaseks (leheks). juur.compile(); optimeerib loodud süsteemi. Ühe lihtsa komponendi puhul ei pruugi olulist kiirusevõitu olla, kuid vähegi rohkemate või keerulisemate kujundite puhul luuakse selle käsu tulemusena seosed, mille tulemusena saab pilti märgatavalt kiiremini ekraanile manada ning liigutada või valguse peegeldusi arvutada. SimpleUniverse u = new SimpleUniverse(c); tulemusena luuakse ruum (universum), mis oma sees paikneva vaataja asukohast paistva pildi saadab eelnevalt loodud lõuendile nimega c. u.getViewingPlatform().setNominalViewingTransform(); nihutab vaataja veidi nullpunktist eemale, nii et keskele (vaikimisi asukoht) paigutatud oleksid nähtavad. u.addBranchGraph(juur); määrab kujunditepuu, millist loodud ruumis näitama hakatakse. Edasised käsud on juba traditsioonilised, et loodud süsteem ka silmale nähtavaks teha. Kui rakend käivitada veebilehel, siis võib main-meetodi kõige täiega ära jätta. Sel juhul tuleks aga hoolitseda, et 3D oleks ka brauseri JRE juurde installeeritud (Netscape puhul Windows 95 all näiteks kataloogi C:\Program Files\Netscape\Communicator\Program\java). Kuubi keeramine Keeramise korral tuleb juure ning kuubi vahele paigutada keerav sõlm. Transform3D suudab nii nihutada kui keerata. Transform3D keerd1=new Transform3D(); keerd1.rotX(Math.PI/4); TransformGroup keere1=new TransformGroup(keerd1); tulemusena loodakse sõlm (tüübist TransformGroup), mis panduna juure ja kuubi vahele keerab viimast nii palju kui sõlmes ette nähtud. BranchGroup juur = new BranchGroup(); juur.addChild(keere1); keere1.addChild(new ColorCube(0.5)); juur.compile(); Nagu analoogia põhjal oletada võib, on klassis Transform3D lisaks rotX-le kasutada ka meetodid rotY ning rotZ keeramiseks, lisaks transform() nihutamiseks ning matemaatikahuvilistele veel hulga vahendeid asukoha määramiseks. Kogu arvutamine käib – nagu pea mujalgi kolmemõõtmelises graafikas – maatriksite ja vektorite korrutamise abil. Liigutamine Järgneva näite tulemusena pannakse kuup ekraanil keerlema ning lähemale-kaugemale nihkuma. import java.applet.Applet; import java.awt.*; import javax.swing.*; import com.sun.j3d.utils.geometry.ColorCube; import com.sun.j3d.utils.universe.SimpleUniverse; import javax.media.j3d.*; import javax.vecmath.*; public class Kuup2a extends JApplet implements Runnable{ TransformGroup keere1; double nurk; public Kuup2a() { Canvas3D c = new Canvas3D(SimpleUniverse.getPreferredConfiguration()); getContentPane().add(c, BorderLayout.CENTER); BranchGroup juur = new BranchGroup(); Transform3D keerd1=new Transform3D(); keere1=new TransformGroup(keerd1); keere1.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); juur.addChild(keere1); keere1.addChild(new ColorCube(0.5)); juur.compile(); SimpleUniverse u = new SimpleUniverse(c); u.getViewingPlatform().setNominalViewingTransform(); u.addBranchGraph(juur); new Thread(this).start(); } public void run(){ while(true){ try{Thread.sleep(50);}catch(Exception e){} nurk+=0.1; double kaugus=-1+Math.sin(nurk/3); Transform3D t=new Transform3D(); t.rotX(nurk); t.setTranslation(new Vector3d(0, 0, kaugus)); keere1.setTransform(t); } } public static void main(String[] args) { Frame f=new Frame("Keerlev kuup"); f.add(new Kuup2a()); f.setSize(300, 300); f.setVisible(true); } } Võrreldes eelmise näitega on objektipuu loomisel juures üks käsk keere1.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); mille tulemusena lubatakse muundusgrupi väärtusi ka programmi töö käigus muuta. Muutmine ise on toodud eraldi lõime, meetodisse run, kus igal sammul suurendatakse nurka 0.1 radiaani võrra ning vastavalt sellele määratakse kuubi keere ning nihe. Kui soovida, et kuup kaugust vaatajast ei muuda, siis võib kaugusega seotud käsud välja jätta. Samuti nagu siin run-käsus tsüklina kuupi keerates võib ka mujal mitmesugustele andmetele vastavalt. Nii võib rahumeeli paluda kujundeid paigutada vastavalt kasutaja sisestatud või võrgust saabunud andmetele. Interpolaator Liikumisi tuleb kolmemõõtmelises graafikas küllalt palju ette. Nende sujuvamaks loomiseks on kasutaja vabastatud pidevast lõimede kirjutamisest ning ta võib koostada interpolaatoreid, mis hoolitsevad muundusgrupi kasutajale sobiva oleku eest. Interpolaatori "südameks" on Alpha, mis vastavalt kasutaja poolt etteantud parameetritele muudab enese sees arvu väärtusega nulli ja ühe vahel. Interpolaator vaatab iga natukese aja tagant seda väärtust ning vastavalt sellele seab muundusgrupi parameetrid. RotationInterpolatori puhul vastab Alpha väärtusele 0-null ning 1-täisring, muud suurused vahepeal. Esimene parameeter tähendab korduste arvu (-1 vastab igavesele pöörlemisele), teine aega millisekundites, mille jooksul suureneb väärtus nullist üheni. Siinses näites siis pööreldakse igavesti, kulutades täisringile kümme sekundit. Kuna algseisu ning täisringi puhul on kuup samas asendis, siis paistabki liikumine ühtlane, kuigi vahepeal toimub muundegrupi väärtuses täisringine nihe, kui Alpha hüppab väärtuselt 1 järsku 0 peale tagasi. Interpolaator on siin näites pandud objektihierarhiasse ühes kuubiga lehena muundegrupi järglaseks. Viimane omakorda kuulub juure alla. BranchGroup juur = new BranchGroup(); TransformGroup keere1=new TransformGroup(); keere1.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); RotationInterpolator keeraja1= new RotationInterpolator(new Alpha(-1, 10000), keere1); keeraja1.setSchedulingBounds(new BoundingSphere()); juur.addChild(keere1); keere1.addChild(keeraja1); keere1.addChild(new ColorCube(0.5)); juur.compile(); Interpolaatoreid on mitmesuguseid. PositionInterpolator suudab kujundeid (või nende komplekti) liigutada kahe punkti vahel. Millisel ajahetkel ese kus paigas asub, seda määrab jällegi isend tüübist Alpha. Temagi muutumisi saab täpsemalt määrata. Parameetriteta konstruktori puhul kasvab väärtus sekundi jooksul nullist üheni ning siis asub jälle otsast peale. Põhjalikuma määramise puhul – nagu järgnevast näitest näha – on Alpha parameetrite järjekord selline: ? korduste arv (-1=lõpmatus) ? lubatud suunad(kasvamine/kahanemine) ? ooteaeg enne käivitumist (kõik ajad millisekundites) ? ooteaeg iga ringi algul, kasvamiskiiruse suurenemise aeg ? üheni jõudmise aeg ? väärtusel 1 püsimise aeg ? vähenemiskiiruse suurenemise aeg ? nullini jõudmise aeg ? nullil püsimise aeg. PositionInterpolatori konstroktoris määratakse ära Alpha, muundusgrupp muutmiseks, liikumissuund ning pikkused palju algasendist edasi ning tagasi liikuda. Liikumissuuna võib Transform3D abil keerata täpselt selliseks nagu vaja. BranchGroup juur = new BranchGroup(); TransformGroup tg1=new TransformGroup(); tg1.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); Transform3D suund=new Transform3D(); suund.rotZ(Math.PI/2); //keerab ümber z-telje Alpha a=new Alpha(-1, Alpha.INCREASING_ENABLE | Alpha.DECREASING_ENABLE , 0, 2000, 2000, 2000, 0, 2000, 2000, 0); PositionInterpolator liigutaja1= new PositionInterpolator(a, tg1, suund, -0.6f, 0.5f); liigutaja1.setSchedulingBounds(new BoundingSphere()); juur.addChild(tg1); tg1.addChild(liigutaja1); tg1.addChild(new ColorCube(0.2)); juur.compile(); Sarnaselt töötab ScaleInterpolator, mille abil kujundi suurust muuta võib. BranchGroup juur = new BranchGroup(); TransformGroup tg1=new TransformGroup(); tg1.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); ScaleInterpolator ip1= new ScaleInterpolator(new Alpha(), tg1); ip1.setSchedulingBounds(new BoundingSphere()); juur.addChild(tg1); tg1.addChild(ip1); tg1.addChild(new ColorCube(0.2)); juur.compile(); Liikumistrajektoori võib punktide kaupa ette anda. Selleks tuleb kirjeldada punktide asukohad ning ajad, millal mingis punktis peab olema. Aegade väärtused tuleb määrata järjest, nullist üheni (ka Alpha annab sama vahemiku) ning aegu peab olema sama palju kui punkte. Siin näites algab liikumine koordinaatide alguspunktist. Kui Alpha on jõudnud 0,7-ni selleks ajaks asub kuup punktis (1, 0, 0) ehk on liikunud ühe ühiku võrra paremale. Järgneva 0,3 Alpha-ühiku jooksul nihutatakse kuup diagonaalis vasakule üles, nii, et ajal kui Alpha=1, on kuup punktis (0, 1, 0). Siis jälle tuldud teed tagasi. Liikumistee võib ette arvutada hulga pikema, nii et saab koostatud kujundi päris keerukat rada pidi käima panna. Kui keeramisrealt (suund.rotZ(Math.PI/2)) kommentaarimärgid eest ära võtta, siis liigub kuup kõigepealt üles ning sealt vasakule alla, sest siis on kogu liikumistee ümber Z-telje täisnurga jagu vasakule pööratud. TransformGroup tg1=new TransformGroup(); tg1.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); Transform3D suund=new Transform3D(); // suund.rotZ(Math.PI/2); //keerab liikumistrajektoori ümber z-telje Alpha a=new Alpha(-1, Alpha.INCREASING_ENABLE | Alpha.DECREASING_ENABLE , 0, 2000, 2000, 2000, 0, 2000, 2000, 0); float[] ajad={0, 0.7f, 1}; Point3f[] punktid={ new Point3f(0, 0, 0), new Point3f(1, 0, 0), new Point3f(0, 1, 0) }; PositionPathInterpolator liigutaja1= new PositionPathInterpolator(a, tg1, suund, ajad, punktid); liigutaja1.setSchedulingBounds(new BoundingSphere()); juur.addChild(tg1); tg1.addChild(liigutaja1); tg1.addChild(new ColorCube(0.2)); Kaks kuupi Esimesed näited olid koostatud ühe kuubiga vaid seepärast, et tulemus lihtsamini kätte tuleks ning kood lühem välja näeks. Täiesti samamoodi saab kujundeid ka juurde lisada. Kui need aga otse juure külge panna, siis satuvad nad otse üksteise peale/sisse ning mõni võib sootuks teise taha peitu jääda et teda sugugi näha ei ole. Kui aga vähemalt üks objekt eemale nihutada, siis on kummalgi oma koht ning neid võib rahumeeli ekraani peal vaadelda. BranchGroup juur = new BranchGroup(); Transform3D tr1=new Transform3D(); tr1.rotX(Math.PI*2/3); tr1.setTranslation(new Vector3d(0.5, 0, 0)); TransformGroup tg1=new TransformGroup(tr1); juur.addChild(tg1); tg1.addChild(new ColorCube(0.2)); juur.addChild(new ColorCube(0.1)); juur.compile(); Muud kujundid Lisaks värvilisele kuubile, millega algul hea katseid teha, leiab geomeetriapaketist veel silindri (Cylinder), koonuse (Cone), risttahuka (Box) ning kera (Sphere). Neile pole veel värvi antud, seetõttu tuleb see töö ise ära teha. Cylinder s1=new Cylinder(0.2f, 1.2f); //raadius, pikkus s1.setAppearance(new Appearance()); juur.addChild(s1); Nagu aimata võib, saab silindrile ette anda raadiuse ning kõrguse, koonusele samuti. Risttahukale kolme külje pikkused ning kerale raadiuse. Taust Soovides tagapinnaks oleva musta tausta millegi muu värvi vastu vahetada, tuleb lihtsalt kirjutada millist värvi selle asemele soovitakse. Ning nagu muud elemendid, tuleb seegi juure külge haakida. Background taust=new Background(new Color3f(Color.yellow)); //kollane taust taust.setApplicationBounds(new BoundingSphere(new Point3d(), 1000)); juur.addChild(taust); Vaatekoha muutmine Et ruumis ringi liikuda või lihtsalt pilti teise nurga alt vaadata, selleks võib oma vaateplatsi asukohta muuta. Järgmise paari reaga luuakse uus Transform3D, määratakse sellele parameetrid (0,6 ühikut üles, 3 ettepoole) ning palutakse vaataja silmad ehk kaamera selle koha peale paigutada. Kui ise liikuda ülespoole, siis paistab pilt selle jagu altpoolt. Transform3D t=new Transform3D(); t.setTranslation(new Vector3f(0, 0.6f, 3)); u.getViewingPlatform().getViewPlatformTransform().setTransform(t); Järgnevalt veidi pikem näide, kus lennutatake taevasse hulk kuupe juhuslikesse asukohtadesse tähtedeks ning seejärel võib oma asukohta muutes kui kosmoselaevaga mööda taevalaotust ringi liikuda. Iga kuubi tarvis luuakse oma muundegrupp ning selle abil nihutatakse kuup keskpunkti suhtes juhuslikult kuni +/- 50 ühiku võrra igas suunas. KeyNavigatorBehavior võimaldab vaateplatvormilt küsitud TransformGroupi muuta ning selle abil meile ruumis liikumis ette kujutada. import java.applet.Applet; import java.awt.*; import com.sun.j3d.utils.geometry.ColorCube; import com.sun.j3d.utils.universe.SimpleUniverse; import com.sun.j3d.utils.behaviors.keyboard.*; import javax.media.j3d.*; import javax.vecmath.*; public class Asukoht2 extends Applet { public Asukoht2() { setLayout(new BorderLayout()); Canvas3D c = new Canvas3D(SimpleUniverse.getPreferredConfiguration()); add(c, BorderLayout.CENTER); BranchGroup juur = new BranchGroup(); for(int i=1; i<1000; i++){ //luuakse hulk juhuslikke kuupe taevasse Transform3D t3=new Transform3D(); t3.setTranslation(new Vector3f((float)(100*Math.random()-50), (float)(100*Math.random()-50), (float)(100*Math.random()-50))); TransformGroup tg=new TransformGroup(t3); tg.addChild(new ColorCube(0.4)); juur.addChild(tg); } SimpleUniverse u = new SimpleUniverse(c); TransformGroup tg=u.getViewingPlatform().getViewPlatformTransform(); KeyNavigatorBehavior kb=new KeyNavigatorBehavior(tg); kb.setSchedulingBounds(new BoundingSphere(new Point3d(), 1000)); juur.addChild(kb); Transform3D t=new Transform3D(); //Määratakse vaatekoht t.setTranslation(new Vector3f(0, 0, -5)); u.getViewingPlatform().getViewPlatformTransform().setTransform(t); Background taust=new Background(new Color3f(Color.blue)); //sinine taust taust.setApplicationBounds(new BoundingSphere(new Point3d(), 1000)); juur.addChild(taust); juur.compile(); u.addBranchGraph(juur); } public static void main(String[] args) { Frame f=new Frame("Kuubid taevas"); f.add(new Asukoht2()); f.setSize(300, 300); f.setVisible(true); } } Kiri Lihtne kahemõõtmeline tekst õnnestub ekraanile manada küllalt kergesti. juur.addChild(new Text2D("Tervitus", new Color3f(Color.green), "Times", 55, Font.ITALIC)); Selle sama teksti pöörlema panek on aga natuke suurem ettevõtmine. Samas aga on tulemus küllaltki ilus ning mõnelgi juhul võib end ära tasuda. Endiselt tuleb lubada muundegrupi väärtust programmi töö ajal kirjutada, et saaksime teksti nurka muuta. Tagapoolsed ettevõtmised pinnaga hoolitsevad, et saaksime teksti mõlemat külge näha. Vaikimisi hoiab arvuti enese energiat kokku ning ei soostu kirja tagapoolt näitama. TransformGroup keere1=new TransformGroup(); keere1.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); RotationInterpolator keeraja1= new RotationInterpolator(new Alpha(-1, 10000), keere1); keeraja1.setSchedulingBounds(new BoundingSphere()); juur.addChild(keere1); keere1.addChild(keeraja1); Text2D tekst=new Text2D("Tervitus", new Color3f(Color.green), "Times", 55, Font.ITALIC); PolygonAttributes pind=new PolygonAttributes(); pind.setCullFace(PolygonAttributes.CULL_NONE); //et ka teine pool näha oleks tekst.getAppearance().setPolygonAttributes(pind); keere1.addChild(tekst); juur.compile(); Ka kolmemõõtmelise kirja puhul tuleb hoolitseda, et seda ekraanil näitama soostutaks. Text3D iseenesest kirjeldab vaid kuju ehk geomeetria. Nägemiseks peab looma kujundi (Shape3D) ning seadma talle geomeetria ning materjali. Et materjal ruumis paistaks, selleks lubame seda valgustada (setLightingEnable) ning paigutame ruumi tausta/hajuvalguse allika. Nii ilmub õrn kolmemõõtmeline tekst ekraanile. Text3D t=new Text3D( new Font3D(new Font("Helvetica", Font.PLAIN, 1), new FontExtrusion()), "Tere" , new Point3f(-1, 0.6f, -1.5f) ); Material m=new Material(); m.setLightingEnable(true); Appearance a=new Appearance(); a.setMaterial(m); Shape3D s=new Shape3D(); s.setGeometry(t); s.setAppearance(a); juur.addChild(s); Color3f valgusvarv=new Color3f(Color.white); AmbientLight al=new AmbientLight(valgusvarv); al.setInfluencingBounds(new BoundingSphere()); juur.addChild(al); juur.compile(); Tasapind Omale sobivatest tasapindadest saab kokku ehitada kõik meile sobivad kujundid. Ka kera pole siinse programmi jaoks muud kui piisavalt tihedasti üksteise kõrvale pandud kolmnurkade kogum. QuadArray abil saab määrata loodava pinna nurgad ning siis paluda nende vahele pind koostada. Edasine toiming sama kui kolmemõõtmelise teksti puhul: geomeetria ja materjal ühendada kujundiks ning selle nägemiseks panna ta lavagraafi(puusse) ja lasta valgus peale. QuadArray nurgad=new QuadArray(4, GeometryArray.COORDINATES | GeometryArray.COLOR_3 | GeometryArray.NORMALS); nurgad.setCoordinate(0, new Point3f(-0.5f, 0, 0)); nurgad.setCoordinate(1, new Point3f( 0.5f, 0, 0)); nurgad.setCoordinate(2, new Point3f( 0, 0.2f, 0)); nurgad.setCoordinate(3, new Point3f( 0, 0.4f, 0)); Shape3D kujund=new Shape3D(); kujund.setGeometry(nurgad); Appearance a=new Appearance(); a.setMaterial(new Material()); kujund.setAppearance(a); juur.addChild(kujund); AmbientLight taustavalgus=new AmbientLight(); taustavalgus.setInfluencingBounds(new BoundingSphere()); juur.addChild(taustavalgus); juur.compile(); Joonemassiiv Nii pindadest kui joontest võib oma tarbeks kujundi luua. Järgnevas näites luuakse kahest joonest L-täht mida võib edaspidi kasutada nii nagu tavalist kujundit, nagu näiteks kuupi või koonust. import javax.media.j3d.*; import javax.vecmath.*; public class SuurL extends Shape3D{ Point3d p1=new Point3d(0, 0, 0), p2=new Point3d(0, 1, 0), p3=new Point3d(0.5, 0, 0); Point3d[] jooned={ p1, p2, p1, p3 }; public SuurL(){ LineArray joonemassiiv=new LineArray(4, LineArray.COORDINATES); joonemassiiv.setCoordinates(0, jooned); setGeometry(joonemassiiv); setAppearance(new Appearance()); } } Selline klass tuleb salvestada eraldi faili ning edaspidi kui tarvist seda kasutada, tuleb hoolitseda, et see SuurL asuks samas kataloogis või sisseloetavas paketis ning loodud tähe ekraanile manamiseks piisab vaid käsust juur.addChild(new SuurL());