42
Programowanie
grafiki
www.sdjournal.org
Software Developer’s Journal 6/2006
Java 3D i Python
J
ava 3D powstała przy udziale konsorcjum
złożonego z firm: 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 graficzne 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 graficznych na
urządzenia w rodzaju: telefony komórkowe, PDA,
itp.
Java 3D w obecnej postaci oferuje przenośną
platformę działająca w oparciu o maszynę wirtu-
alną Java. Jak prawie każde środowisko graficz-
ne Java 3D pozwala na tworzenie grafiki 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 graficzne (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-
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 zdefiniowanym dla danego obiek-
tu zakresem. Definiowanie 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).
Janusz Gołdasz
Autor jest pracownikiem Centrum Technicznego firmy
Delphi Poland w Krakowie.
Kontakt z autorem: janusz.goldasz@delphi.com
Rysunek 1.
Pierwsza aplikacja: Hello3D
Java 3D i Python
43
www.sdjournal.org
Software Developer’s Journal 6/2006
Listing 1.
Pierwsze kroki – Hello3D
import
java
.
applet
.
Applet
;
import
java
.
awt
.
BorderLayout
;
import
java
.
awt
.
event
.
*;
import
java
.
awt
.
GraphicsConfiguration
;
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
.
objectfile
.
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
.
setInfluencingBounds
(
bounds
);
DirectionalLight
dirLight
=
new
DirectionalLight
(
lightColor1
,
lightDirection1
);
dirLight
.
setInfluencingBounds
(
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
flags
=
ObjectFile
.
RESIZE
;
ObjectFile
file
=
new
ObjectFile
(
flags
);
Scene
model
=
null
;
model
=
file
.
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
());
GraphicsConfiguration
config
=
SimpleUniverse
.
getPreferredConfiguration
();
Canvas3D
c
=
new
Canvas3D
(
config
);
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
);
}
}
44
Programowanie
grafiki
www.sdjournal.org
Software Developer’s Journal 6/2006
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 skonfigurowane 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(float 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 file = new ObjectFile(flags);
Scene model = null;
model = file.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 modyfikowaniu
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
Listing 2.
Przykład użycia obiektu Canvas3D ze Swing
class
Panel3D
extends
JPanel
{
public
Panel3D
()
{
this
.
add
(
new
Canvas3D
(
config
));
}
}
Listing 3.
Rysunkowa para JTabbedPane – Canvas3D
class
Tabbed
extends
JTabbedPane
{
public
Tabbed
()
{
// …
// Gotowe: Java 3D wewnątrz Swing
this
.
addPage
(
new
Panel3D
());
//
}
}
Java 3D i Python
45
www.sdjournal.org
Software Developer’s Journal 6/2006
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
mtllib model.mtl
usemtl 20
Pierwsza z nich określa zewnętrzną bibliotekę, z której mo-
żemy pobrać odpowiednie tworzywa, a druga indeks two-
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 specyfikacji 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.
Okienka
Integracja Javy 3D ze Swing nie jest bezproblemowa. Po-
prawnie Java 3D pracuje jedynie ze starszymi kontenera-
Listing 4.
Java 3D w Jythonie
import
java
.
awt
.
GraphicsConfiguration
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
*
# Tworzymy applet…
class
Hello3D
(
Applet
):
def
init
(
self
):
self
.
setLayout
(
BorderLayout
())
self
.
_bounds
=
BoundingSphere
(
Point3d
(
0.0
,
0.0
,
0.0
)
,
100.0
)
config
=
SimpleUniverse
.
getPreferredConfiguration
()
c
=
Canvas3D
(
config
)
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
.
setInfluencingBounds
(
self
.
_bounds
)
dirLight
=
DirectionalLight
(
lightColor1
,
lightDir1
)
dirLight
.
setInfluencingBounds
(
self
.
_bounds
)
objRoot
.
addChild
(
ambientLight
)
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
# Testujemy applet…
if
__name__
==
'
__main__
'
:
import
pawt
pawt
.
test
(
Hello3D
())
# Koniec
46
Programowanie
grafiki
www.sdjournal.org
Software Developer’s Journal 6/2006
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.
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-
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
from com.sun.j3d.utils.behaviors.mouse import *
Listing 5.
Wykorzystanie klasy PythonInterpreter
// Import klas Java 3D…
import
org
.
python
.
core
.
PyObject
;
// Jython
import
org
.
python
.
util
.
PythonInterpreter
;
// interpreter
// 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
);
}
// 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
);
mainGroup
.
addChild
(
embedded
);
// Python: KONIEC
objRoot
.
addChild
(
mainGroup
);
return
objRoot
;
}
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
.
execfile
(
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
]);
}
}
47
Java 3D i Python
www.sdjournal.org
Software Developer’s Journal 6/2006
Struktura appletu (w Pythonie !) jest w zasadzie identycz-
na z pierwszym przykładem (Java). Do naszego przykła-
du stosują się, oczywiście wszystkie charakterystycz-
ne cechy Pythona, a więc oszczędność kodu, typowanie,
etc.
Wskazówka dla osób nie mających styczności z Pytho-
nem: słowo
self
jest odpowiednikiem
this
Javy, ale w Pytho-
nie
self
musi być użyte jawnie w treści klasy.
W aspekcie korzystania z Pythona, pojawia się też moż-
liwość rozszerzania funkcjonalności naszej aplikacji skryp-
tami Pythona (Jythona), co w wielu przypadkach jest rze-
czą nie do pogardzenia. Ogólnie rzecz ujmując, para Ja-
va-Jython daje dwie możliwości: kompilacja kodu Pythona
do postaci klasy Javy i wywołanie go w tworzonej aplikacji
w postaci klasy lub wykorzystanie interpretera poleceń Py-
thona (klasa
PythonInterpreter
) do wywołania zewnętrzne-
go skryptu generującego np. geometrię. Jak sprawdziliśmy
w praktyce, pierwsze rozwiązanie nie zawsze działa, nato-
miast korzystanie z interpretera jest o wiele bardziej nieza-
wodne i czytelne.
Integrację Java (i Java 3D) z Python’em (Jython’em) po-
każemy na przykładzie nieskomplikowanej aplikacji okien-
kowej (zob. Rysunek 4), skonstruowanej w opisany w Li-
stingach 2 i 3 sposób. Tym razem jednak zamiast skazywać
się tylko i wyłącznie na towarzystwo Javy w treści meto-
dy
createSceneGraph()
pojawi się wywołanie skryptu Jytho-
na o określonej zawartości (Listingi 5 i 6). W Listingu 5 brak
części powtarzających się fragmentów kodu. Najważniej-
sza zmiana w treści klasy to nowa metoda o nazwie
eme-
beddedWorld(String source)
, której parametrem jest nazwa
zewnętrznego skryptu Jythona
source
– aplikację wywołu-
jemy z parametrem w postaci nazwy skryptu. Do zinterpre-
towania treści skryptu w sposób zrozumiały dla naszej apli-
kacji konieczne jest utworzenie obiektu klasy
PythonInter-
preter
(Klasa ta posiada metodę
execfile()
, przyjmującą in-
stancję klasy
FileInputStream
(skrypt), której wykonanie po-
woduje przetworzenie treści skryptu oraz metodę
get()
, któ-
rej pierwszym parametrem jest nazwa pobieranego obiek-
tu Pythona (
scene
), a drugim odpowiednia klasa Java 3D.
W naszym przypadku jest to klasa
BranchGroup
– konieczne
jest rzutowanie do klasy
BranchGroup
.
Oprogramowanie
Wszystkie omówione w niniejszym artykule przykłady po-
wstały przy użyciu Java 3D w wersji 1.3.1, Java 2 SE, Jy-
thon 2.1 oraz środowiska Eclipse w wersji 3.1 z wtyczką Py-
Dev (Python/Jython). Wykorzystany w pierwszym przykła-
dzie model jest częścią dystrybucji Java 3D. Minimalne za-
soby potrzebne do utworzenia opisanych aplikacji są jed-
nak znacznie mniejsze i mogą ograniczać się do koniecz-
ności zainstalowania Javy (J2SE) wraz z Java 3D oraz Jy-
thona i tworzeniu opisanych aplikacji przy pomocy dowol-
nego edytora tekstowego.
Podsumowanie
Java 3D jest, pomimo pewnych swoich niedociągnięć (kiepska
współpraca ze Swing, brak bezpośredniego dostępu do funk-
cji OpenGL, prosty model oświetlenia) i wieku, stosunkowo
atrakcyjnym rozwiązaniem dla programistów WWW i gier Ja-
va. W dalszym ciągu posiada duży potencjał i możliwości po-
zwalające programistom na umieszczanie niewielkim nakła-
dem pracy elementów grafiki 3D w appletach i aplikacjach Ja-
va, choć obecnie popularniejszym rozwiązaniem, np. w sferze
gier Java, wydają się być dedykowane rozwiązania, np. oparta
na JOGL biblioteka Xijth3D. Java 3D chyba jednak najbardziej
wpasowuje się w model przeznaczony dla średnio-wymagają-
cego użytkownika. n
W Sieci
• niezależne forum developerów Java 3D, JOGL
http://www.j3d.org
• strona domowa Javy
http://www.javasoft.com
• strona domowa Eclipse IDE
http://www.eclipse.org
• strona domowa programu Blender
http://www.blender.org
Rysunek 4.
Jython zanurzony
Listing 6.
Fragment importowanego skryptu Jython’a
(brak listy modułów Java 3D)
# Lista modułów Java 3D…
# funkcja createScene()
def
createScene
():
# Tradycyjnie, korzen
objRoot
=
BranchGroup
()
bounds
=
BoundingSphere
(
Point3d
(
0.0
,
0.0
,
0.0
)
,
100.0
)
background
=
Background
()
background
.
setColor
(
Color3f
(
0
,
0
,
1
))
background
.
setApplicationBounds
(
bounds
)
objRoot
.
addChild
(
background
)
objTransform
=
TransformGroup
()
objTransform
.
setCapability
(
TransformGroup
.
ALLOW_
TRANSFORM_READ
)
objRoot
.
addChild
(
objTransform
)
objTransform
.
addChild
(
ColorCube
(
0.4
))
return
objRoot
# koniec funkcji
# Wywołujemy createScene() i wynik przypisujemy
# do zwracanej zmiennej scene
scene
=
createScene
()
# Koniec