2006.06_Java 3D i Python _[Programowanie Grafiki].pdf

(379 KB) Pobierz
441729291 UNPDF
Programowanie
graiki
Janusz Gołdasz
Java 3D i Python
złożonego z irm: Intel, SGI, Apple oraz Sun
w połowie lat 90-tych. W obecnym czasie
każdy z tych producentów posiadał już własne śro-
dowisko graiczne 3D, lecz były one ograniczone
a sposób obsługi dosyć złożony. Zadaniem kon-
sorcjum było stworzenie wersji dla powszechne-
go odbiorcy takiego środowiska na platformę Java
i tym samym rozszerzenie jej możliwości. W wyni-
ku wspólnych prac konsorcjum powstała Java 3D.
Od 2004 Java 3D stała się oprogramowaniem ty-
pu open source rozwijanym w dalszym ciągu przez
Sun-a i rzesze developerów ochotników. W równym
stopniu wspierany jest zarówno OpenGL jak i Di-
rectX.
Nie należy mylić Javy 3D z mobilną platformą
Sun’a o skrótowej nazwie M3G dla J2ME do roz-
wijania trójwymiarowych aplikacji graicznych na
urządzenia w rodzaju: telefony komórkowe, PDA,
itp.
Rysunek 1. Pierwsza aplikacja: Hello3D
tów, budową sceny zawarte są w kilku pakietach
użytkowych, np. com.sun.j3d.utils , a w javax.vec-
math znajdują się klasy wspomagające operacje na
współrzędnych, wektorach i macierzach. Ogólnie,
mamy do dyspozycji klasy służące tworzeniu geo-
metrii (brak krzywych i powierzchni typu NURBS),
animacji, detekcji kolizji, oświetleniu i teksturowa-
niu. Java 3D, w przeciwieństwie do innych środo-
wisk typu OpenSceneGraph czy Open Inventor,
nie posiada natywnego formatu do zapisu/odczytu
sceny, w związku z czym, chcąc nie chcąc, musimy
w sytuacji, gdy np. konieczny jest import sceny do
naszej aplikacji polegać na odpowiednich konwer-
terach dla 3ds Max, VRML, itp. Najczęściej spoty-
ka się konwertery formatów .obj i .vrml , ale wspie-
rany jest także w pewnym ograniczonym zakresie
.vtk (Virtual Toolkit), .lwo (LightWave) czy też po-
pularny .dxf .
Interaktywność , w ogólnym tego słowa zna-
czeniu, sprowadza się do możliwości zapro-
gramowania reakcji określonego obiektu na ze-
wnętrzne bodźce. W Java 3D przyjęto zasadę, iż
reakcje te wykonywane są zawsze wtedy, gdy bo-
dziec ma miejsce w określonych granicach wokół
obiektu. Zaletą takiego rozwiązania jest to, iż apli-
kacje jest w stanie zignorować zdarzenie mające
miejsce poza zdeiniowanym dla danego obiek-
tu zakresem. Deiniowanie zakresów zdarzeń jest
konieczne do określenie przez aplikacje, na któ-
re bodźce powinna zareagować. Reakcje można
powiązać z dowolną liczbą zdarzeń, np. zbliżenie
do określonego obiektu może powodować zmianę
w kolorze, obrót, etc. Co więcej, wiele reakcji
można kontrolować przy użyciu tzw. interpola-
torów (np. przemieszczanie się obiektu wzdłuż
określonej trajektorii).
Java 3D w obecnej postaci oferuje przenośną
platformę działająca w oparciu o maszynę wirtu-
alną Java. Jak prawie każde środowisko graicz-
ne Java 3D pozwala na tworzenie graiki trójwy-
miarowej w oparciu o hierarchiczną strukturę grafu,
w którego węzłach znajdować się będą wszystkie
elementy wirtualnego świata (tj. geometria obiek-
tów, przekształcenia, kamery, oświetlenia, anima-
tory, etc.) konieczne do należytej reprezentacji pro-
jektowanej sceny.
Celem artykułu jest prezentacja niektórych możli-
wości, jakie może dać Java 3D programistom tworzą-
cym przestrzenne aplikacje graiczne (niekoniecznie
przy użyciu tylko i wyłącznie samej Javy !). Zaczyna-
my od krótkiej charakterystyki kluczowych pakietów
biblioteki, aby następnie przejść do napisania prostej
aplikacji typu Hello3D . W dalszym ciągu artykułu sku-
pimy się na integracji Javy 3D ze Swing oraz gene-
rowaniu sceny przy użyciu skryptów Pythona, a ści-
ślej mówiąc, jego implementacji na maszynę wirtual-
ną Javy (Jython) i integracji Java 3D z tym środowi-
skiem.
Java 3D – opis
API Java 3D stanowi kilka pakietów, z których naj-
ważniejsze to jądro: javax.media.j3d.org . Klasy
związane z konstrukcją i opisem geometrii obiek-
Autor jest pracownikiem Centrum Technicznego irmy
Delphi Poland w Krakowie.
Kontakt z autorem: janusz.goldasz@delphi.com
42
www.sdjournal.org Software Developer’s Journal 6/2006
J ava 3D powstała przy udziale konsorcjum
441729291.021.png 441729291.022.png 441729291.023.png
Java 3D i Python
Listing 1. Pierwsze kroki – Hello3D
import java . applet . Applet ;
import java . awt . BorderLayout ;
import java . awt . event . *;
import java . awt . GraphicsConiguration ;
import com . sun . j3d . utils . applet . MainFrame ;
import com . sun . j3d . utils . geometry . *;
import com . sun . j3d . utils . universe . *;
import javax . media . j3d . *;
import javax . vecmath . *;
import com . sun . j3d . utils . behaviors . mouse . *;
import com . sun . j3d . utils . behaviors . vp . *;
import com . sun . j3d . loaders . objectile . ObjectFile ;
import com . sun . j3d . loaders . ParsingErrorException ;
import com . sun . j3d . loaders . IncorrectFormatException ;
import com . sun . j3d . loaders . Scene ;
import java . io . *;
public class Hello3D extends Applet {
private SimpleUniverse universe = null ;
public BranchGroup createSceneGraph () {
BranchGroup objRoot = new BranchGroup ();
BoundingSphere bounds = new BoundingSphere (
new Point3d ( 0 , 0 , 0 ) , 100 );
Background background = new Background ();
background . setColor ( new Color3f ( 0.0f , 0.0f , 1.0f ));
background . setApplicationBounds ( bounds );
objRoot . addChild ( background );
Color3f lightColor1 =
new Color3f ( 0.7f , 0.7f , 0.7f );
Vector3f lightDirection1 =
new Vector3f ( 1.0f , . 0f , . 0f );
Color3f ambientLightColor =
new Color3f ( 0.2f , 0.2f , 0.2f );
AmbientLight ambientLight =
new AmbientLight ( ambientLightColor );
ambientLight . setInluencingBounds ( bounds );
DirectionalLight dirLight =
new DirectionalLight (
lightColor1 , lightDirection1 );
dirLight . setInluencingBounds ( bounds );
objRoot . addChild ( ambientLight );
objRoot . addChild ( dirLight );
Transform3D sphereTransform = new Transform3D ();
sphereTransform . set (
new Vector3f ( 0.5f , 0.0f , - 0.25f ));
sphereTransform . setScale ( 0.5f );
TransformGroup objTransform1 =
new TransformGroup ();
objTrans1 . setTransform ( sphereTransform );
Transform3D cubeTransform = new Transform3D ();
cubeTransform . set ( new Vector3f (- 0.5f , 0f , 0.25f ));
cubeTransform . setScale ( 0.5f );
TransformGroup objTransform2 =
new TransformGroup ();
objTransform2 . setTransform ( cubeTransform );
objTransform2 . setCapability (
TransformGroup . ALLOW_TRANSFORM_WRITE );
objTransform2 . setCapability (
TransformGroup . ALLOW_TRANSFORM_READ );
objRoot . addChild ( objTransform1 );
objRoot . addChild ( objTransform2 );
Sphere sphere = new Sphere ( 0.5f );
ColorCube cube = new ColorCube ( 0.5f );
Appearance appearance = new Appearance ();
Color3f sphereColor =
new Color3f ( 1.0f , 0.2f , 0.4f );
app . setMaterial ( new Material ( sphereColor ,
new Color3f ( 0f , 0f , 0f ) , sphereColor ,
new Color3f ( 1f , 1f , 1f ) , 10 ));
sphere . setAppearance ( appearance );
objTransform1 . addChild ( sphere );
objTransform2 . addChild ( cube );
MouseRotate rotate =
new MouseRotate ( objTransform2 );
objRoot . addChild ( rotate );
rotate . setSchedulingBounds ( bounds );
MouseZoom zoom = new MouseZoom ( objTransform2 );
objRoot . addChild ( zoom );
zoom . setSchedulingBounds ( bounds );
int lags = ObjectFile . RESIZE ;
ObjectFile ile = new ObjectFile ( lags );
Scene model = null ;
model = ile . load ( "model.obj" );
Transform3D mdlTransform = new Transform3D ();
mdlTransform . set ( new Vector3f ( 0f , 0f , 0f ));
mdlTransform . setScale ( 0.5f );
TransformGroup objTransform3 =
new TransformGroup ();
objTransform3 . setTransform ( mdlTransform );
objTransform3 . addChild ( model . getSceneGroup ());
objRoot . addChild ( objTransform3 );
objRoot . compile ();
return objRoot ;
}
public Hello3D () {
}
public void init () {
setLayout ( new BorderLayout ());
GraphicsConiguration conig =
SimpleUniverse . getPreferredConiguration ();
Canvas3D c = new Canvas3D ( conig );
add ( "Center" , c );
BranchGroup scene = createSceneGraph ();
universe = new SimpleUniverse ( c );
universe . getViewingPlatform () .
setNominalViewingTransform ();
universe . addBranchGraph ( scene );
}
public void destroy () {
u . cleanup ();
}
public static void main ( String [] args ) {
new MainFrame ( new Hello3D () , 256 , 256 );
}
}
Software Developer’s Journal 6/2006
www.sdjournal.org
43
441729291.024.png 441729291.001.png
Programowanie
graiki
Listing 2. Przykład użycia obiektu Canvas3D ze Swing
Listing 3. Rysunkowa para JTabbedPane – Canvas3D
class Panel3D extends JPanel {
public Panel3D () {
this . add ( new Canvas3D ( conig ));
}
}
class Tabbed extends JTabbedPane {
public Tabbed () {
// …
// Gotowe: Java 3D wewnątrz Swing
this . addPage ( new Panel3D ());
//
}
}
Pierwsze kroki
Tworzenie aplikacji w oparciu o Java 3D pokażemy na ra-
czej typowym dla tej biblioteki przykładzie zawierającym
kilka najczęściej spotykanych elementów (prymitywy, kom-
ponowanie sceny, wczytanie modelu z zewnętrznego pli-
ku, reakcje) – na ekranie (Rysunek 1) pojawią się kula, sze-
ścian i (wczytany z pliku .obj ) obiekt 3D. Kula i obiekt pozo-
staną nieruchome, a użytkownik może manipulować jedy-
nie sześcianem.
Na początek przyjrzyjmy się implementacji klasy Hello-
3D w Listingu 1. Nasza aplikacja to typowy applet , w związ-
ku z czym konieczne jest dziedziczenie po klasie Applet
i implementacja metody init() . Do rysowania wykorzystuje-
my odpowiednio skonigurowane okno klasy Canvas3D – kla-
sa Canvas3D dziedziczy po awt.Canvas . Kluczowa dla zawar-
tości trójwymiarowej sceny jest stworzona przez nas metoda
createScene() , zwracająca korzeń sceny ( scene ) klasy Branch-
Group ( javax.media.j3d.BranchGroup ). Chcąc stworzoną scenę
wyświetlić, w dalszej części metody musimy dołączyć korzeń
do naszego wszechświata ( universe ). W metodzie createSce-
ne() , na początek musimy zainicjować korzeń drzewa ( root ),
do którego będziemy dokładać kolejne elementy sceny. Ko-
lejno ustawiamy: tło ( background ), światło otoczenia ( ambien-
tLight ) i kierunkowe ( dirLight ). Granice sceny ustalamy two-
rząc instancję klasy BoundingSphere ( bounds ). Chcąc manipu-
lować jedynie sześcianem, musimy stworzyć dla niego osob-
ną gałąź (grupę) i w rezultacie do korzenia dołączyć trzy od-
dzielne instancje klasy TransformGroup ( objTransform1 dla ku-
li, objTransform2 dla sześcianu i objTransform3 dla sceny, któ-
rą wczytywać będziemy z pliku) – tylko objTransform2 w koń-
cowym efekcie będzie posiadać mechanizm obsługi zda-
rzeń myszki. Tak kulę jak i sześcian przesuwamy i skaluje-
my używając metod set(Vector3f x) i setScale(loat scale)
klasy Transform3D . W przypadku kuli i modelu działamy od-
powiednio na instancjach sphereTransform oraz mdlTransform ,
dla sześcianu natomiast na instancji cubeTransform (w gałęzi
objTransform2 ). Dodatkowo, kula jest kolorowana ( sphere.se-
tAppearance(appearance) ). Do manipulowania sześcianem
myszką potrzebne są nam jeszcze instancje klasy MouseZoom
(zoom) i MouseRotate (rotate) . Obydwie przyjmują objTrans-
form2 . Dla wczytania zewnętrznego modelu (sceny) kluczo-
wy jest następujący fragment
ObjectFile ile = new ObjectFile(lags);
Scene model = null;
model = ile.load("model.obj");
Tak wczytany model wystarczy tylko umieścić w przeznaczo-
nej dla niego gałęzi i dołączeniu jej do korzenia:
objTrans3.addChild(model.getSceneGroup());
objRoot.addChild(objTrans3);
Musimy jeszcze tylko pamiętać o bieżącym modyikowaniu
treści drugiej grupy
objTransform2.setCapability(
TransformGroup.ALLOW_TRANSFORM_WRITE)
objTransform2.setCapability(
TransformGroup.ALLOW_TRANSFORM_READ);
dołączeniu wszystkich potrzebnych elementów do korzenia
i pierwsza aplikacja jest gotowa.
Rysunek 2. Przykład importu do Java 3D powierzchni
wygenerowanej w Blenderze
Rysunek 3. Przykład połączenia GUI z Java 3D – symulator
cząsteczek
44
www.sdjournal.org Software Developer’s Journal 6/2006
441729291.002.png 441729291.003.png 441729291.004.png 441729291.005.png 441729291.006.png 441729291.007.png 441729291.008.png 441729291.009.png 441729291.010.png 441729291.011.png
 
Java 3D i Python
Listing 4. Java 3D w Jythonie
import java . awt . GraphicsConiguration
from java . applet import Applet
from java . awt import BorderLayout
from java . awt . event import *
from com . sun . j3d . utils . geometry import *
from com . sun . j3d . utils . universe import *
from javax . media . j3d import *
from javax . vecmath import *
from com . sun . j3d . utils . behaviors . mouse import *
from com . sun . j3d . utils . behaviors . vp import *
objRoot . addChild ( dirLight )
sphereTransform = Transform3D ()
sphereTransform . set ( Vector3f ( 0 ., 0.5 , - 0.25 ))
sphereTransform . setScale ( 0.5 )
objTransform1 = TransformGroup ()
objTransform1 . setTransform ( sphereTransform )
cubeTransform = Transform3D ()
cubeTransform . set ( Vector3f ( 0 ., - 0.5 , 0.25 ))
cubeTransform . setScale ( 0.5 )
objTransform2 = TransformGroup ()
objTransform2 . setCapability (
TransformGroup . ALLOW_TRANSFORM_WRITE )
objTransform2 . setCapability (
TransformGroup . ALLOW_TRANSFORM_READ )
objTransform2 . setTransform ( cubeTransform )
objRoot . addChild ( objTransform1 )
objRoot . addChild ( objTransform2 )
sphere = Sphere ( 0.5 )
cube = ColorCube ( 0.5 )
appearance = Appearance ()
objColor = Color3f ( 1.0 , 0.2 , 0.4 )
appearance . setMaterial ( Material (
objColor , Color3f ( 0 , 0 , 0 ) , objColor ,
Color3f ( 1 , 1 , 1 ) , 10 ))
sphere . setAppearance ( appearance )
objTransform1 . addChild ( sphere )
objTransform2 . addChild ( cube )
rotate = MouseRotate ( objTransform2 )
objRoot . addChild ( rotate )
rotate . setSchedulingBounds ( self . _bounds )
zoom = MouseZoom ( objTransform2 )
objRoot . addChild ( zoom )
zoom . setSchedulingBounds ( self . _bounds )
objRoot . compile ()
return objRoot
# koniec metody createScene
# Tworzymy applet…
class Hello3D ( Applet ):
def init ( self ):
self . setLayout ( BorderLayout ())
self . _bounds =
BoundingSphere ( Point3d ( 0.0 , 0.0 , 0.0 ) , 100.0 )
conig = SimpleUniverse . getPreferredConiguration ()
c = Canvas3D ( conig )
self . add ( "Center" , c )
self . _scene = self . createScene ()
self . _universe = SimpleUniverse ( c )
self . _viewingPlatform =
self . _universe . getViewingPlatform ();
self . _viewingPlatform . setNominalViewingTransform ();
self . _universe . addBranchGraph ( self . _ scene )
# Kolej na tworzenie sceny…
def createScene ( self ):
# korzeń
objRoot = BranchGroup ()
background = Background ()
background . setColor ( Color3f ( 0 , 0 , 1 ))
background . setApplicationBounds ( self . _bounds )
objRoot . addChild ( background )
lightColor1 = Color3f ( 0.7 , 0.7 , 0.7 )
lightDirection1 = Vector3f ( 1.0 , . 0 , . 0 )
ambientColor = Color3f ( 0.2 , 0.2 , 0.2 )
ambientLight = AmbientLight ( alColor )
ambientLight . setInluencingBounds ( self . _bounds )
dirLight = DirectionalLight ( lightColor1 , lightDir1 )
dirLight . setInluencingBounds ( self . _bounds )
objRoot . addChild ( ambientLight )
# Testujemy applet…
if __name__ == ' __main__ ' :
import pawt
pawt . test ( Hello3D ())
# Koniec
Zwróćmy uwagę, że informacja o tworzywie wczyta-
nego modelu może znajdować się w zewnętrznej biblio-
tece materiałowej. W przypadku plików .obj jest to zwykle
tekstowy plik z rozszerzeniem .mtl generowany przy oka-
zji tworzenia samego modelu. Aby móc z niego skorzystać,
wystarczy w naszym modelu (w formacie .obj ) umieścić
dwie instrukcje
rzywa, którym kolorujemy albo cały model albo wybra-
ną grupę. W tym wypadku spada z nas np. cała odpowie-
dzialność za kolor obiektu, przezroczystość, itp. Trzeba pa-
miętać, że większość istniejących w konwerter plików .obj ,
a także .lwo zwykle nie obsługuje całości specyikacji okre-
ślonego formatu. W ekstremalnych sytuacjach konieczna
jest edycja pliku i usunięcie kłopotliwych elementów. Z ist-
niejących modelerów open source dobre wyniki dają Blen-
der oraz Wings – zob. Rysunek 2.
mtllib model.mtl
usemtl 20
Okienka
Integracja Javy 3D ze Swing nie jest bezproblemowa. Po-
prawnie Java 3D pracuje jedynie ze starszymi kontenera-
Pierwsza z nich określa zewnętrzną bibliotekę, z której mo-
żemy pobrać odpowiednie tworzywa, a druga indeks two-
Software Developer’s Journal 6/2006
www.sdjournal.org
45
441729291.012.png 441729291.013.png
Programowanie
graiki
Listing 5. Wykorzystanie klasy PythonInterpreter
// Import klas Java 3D…
import org . python . core . PyObject ; // Jython
import org . python . util . PythonInterpreter ; // interpreter
mainGroup . addChild ( embedded );
// Python: KONIEC
objRoot . addChild ( mainGroup );
return objRoot ;
}
// Klasa dziedziczy po JFrame - Swing
public class embDemo extends JFrame {
// pola prywatne, etc.
public embDemo ( String source ) {
// …
// Dodajemy do ramki nasz panel 3D
this . getContentPane () . add ( new Tabbed ( source ));
// …
}
// Panel dziedziczy po klasie JPanel
class Panel3D extends JPanel {
public Panel3D ( String source ) {
// …
// Scena i wirtualny wszechświat
BranchGroup scene ;
SimpleUniverse sUniverse = new SimpleUniverse ( can
vas3D );
scene = createSceneGraph ( source );
sUniverse . addBranchGraph ( scene );
//…
this . setLayout ( new BorderLayout ());
this . setOpaque ( false );
this . add ( "Center" , canvas3D );
}
private BranchGroup embeddedWorld ( String source ) {
Object input = null ;
BranchGroup root = new BranchGroup ();
// Inicjujemy interpreter…
PythonInterpreter interp = new PythonInterpreter ();
// ...i wczytujemy do niego treść skryptu
try {
DataInputStream in = new DataInputStream (
new FileInputStream ( source ));
interp . execile ( in );
// Pobieramy określony obiekt - scene
input = interp . get ( "scene" , BranchGroup . class );
} catch ( FileNotFoundException E ) {
E . printStackTrace ( System . err );
}
// Rzutujemy - input jest instancją klasy Object
return ( BranchGroup ) input ;
}
}
// Dziedziczymy po klasie JTabbedPane
class Tabbed extends JTabbedPane {
// source to nazwa skryptu Pythona
public Tabbed ( String source ) {
// Zakładka z panelem 3D
this . addTab ( "View" , new Panel3D ( source ));
}
}
public static void main ( String [] args ) {
new embDemo ( args [ 0 ]);
}
}
// Metoda createSceneGraph() jest inna niż zwykle..
private BranchGroup createSceneGraph ( String source ) {
BranchGroup objRoot = new BranchGroup ();
TransformGroup mainGroup = new TransformGroup ();
// …
// Python: uruchamiamy interpreter
// Tutaj embeddedWorld zwraca wczytaną
// z zewnątrz scenę
BranchGroup embedded = embeddedWorld ( source );
mi AWT. Powszechnym problem jest przerysowywanie i za-
krywanie kontrolek Swingowych przez Javę 3D ( Canvas3D ),
co powoduje, że użycie większości kontrolek Swingowych
jest praktycznie niemożliwe. Najlepsze wyniki, jak pokaże-
my na przykładzie, daje para JTabbedPane Canvas3D i dzie-
dziczenie po klasie JPanel , np. w sposób pokazany w Listin-
gu 2 i 3.
Teraz wystarczy już tylko Rysunkowa para JTabbedPane
Canvas3D , przedstawiona na Listigu 3.
Dzięki takiemu rozwiązaniu można w łatwy sposób two-
rzyć rozbudowane aplikacje z typowym dla Swingowych
aplikacji interfejsem użytkownika, jak np. ta zilustrowana na
Rysunku 3 przedstawiając symulator cząsteczek.
plementacją Pythona o nazwie Jython na maszynę wirtual-
ną Javy. Zalet tworzenia aplikacji przy użyciu języków skryp-
towych w rodzaju Pythona jest wiele – np. obecność typów
danych wysokiego poziomu (listy, słowniki) przyspiesza-
jąca tworzenie aplikacji, dynamiczne typowanie, oszczęd-
ność kodu, interpretowanie zamiast kompilacji. Jython
w wersji 2.1 jest zgodny z J2SE, dzięki czemu korzystanie
w nim ze środowiska Java 3D w obecnej wersji (1.3.1) jest
bezproblemowe – Jython jest w 100% napisany w Javie, co
daje możliwość importowania klas Javy do tworzonych przez
nas skryptów. Dla porównania z poprzednim kodem, poni-
żej przykład skryptu/appletu stworzonego w Pythonie. Apli-
kacja jest identyczna z opisaną na wstępie aplikacją Hello-
3D poza fragmentem dotyczącym wczytania elementu sceny
z zewnątrz. Odpowiednie moduły Javy i Javy 3D importuje-
my, używając typowej konstrukcji Pythona
Java 3D a Jython
Ciekawą alternatywą dla programistów używających obiek-
towe języki skryptowe wysokiego poziomu (np. Python) jest
możliwość integracji Javy 3D z Pythonem, a konkretnie z im-
from com.sun.j3d.utils.behaviors.mouse import *
46
www.sdjournal.org Software Developer’s Journal 6/2006
441729291.014.png 441729291.015.png 441729291.016.png 441729291.017.png 441729291.018.png 441729291.019.png 441729291.020.png
 
Zgłoś jeśli naruszono regulamin